Skip to content

Instantly share code, notes, and snippets.

@tomsdev
Created April 21, 2013 00:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tomsdev/5428018 to your computer and use it in GitHub Desktop.
Save tomsdev/5428018 to your computer and use it in GitHub Desktop.
MeteorJS sample that refresh only the parts of a d3js graph that have changed
.bar {fill: #1D8300;}
.label {fill: #FFF; font-size:10px; font-family:sans-serif;}
fieldset {margin-bottom:20px}
<head>
<title>testd3js</title>
</head>
<body>
{{> editor}}
{{> chart}}
</body>
<template name="editor">
<div class="editor">
<fieldset>
<input type="text" name="width" placeholder="width">
<input type="text" name="height" placeholder="height">
<input type="text" name="data" placeholder="data e.g. 10,40,20">
</fieldset>
</div>
</template>
<template name="chart">
<div class="chart">
{{#constant}}
<svg width="500" height="500">
</svg>
{{/constant}}
</div>
</template>
if (Meteor.isClient) {
// HACK : A custom reactive data source that works similarly
// to the Session object but can store Function type !
Session = {
keys: {},
deps: {},
get: function (key) {
this.ensureDeps(key);
this.deps[key].depend();
return this.keys[key];
},
set: function (key, value) {
this.keys[key] = value;
this.ensureDeps(key);
this.deps[key].changed();
},
ensureDeps: function (key) {
if (!this.deps[key])
this.deps[key] = new Deps.Dependency();
}
};
Meteor.startup(function () {
// default reactive values of the chart
Session.set("width", 200);
Session.set("height", 200);
Session.set("data", [{name:"Foo", value:10}, {name:"Bar", value:20}, {name:"Baz", value:60}]);
});
// class that construct a bar chart
function BarChart(node) {
console.log("BarChart");
var svg = d3.select(node);
this.barContainer = svg.append("g");
this.labelContainer = svg.append("g");
// autorun on methods that will refresh different part of the graph
this.autoruns = [];
this.autoruns.push(Deps.autorun(this.updateScaleY.bind(this)));
this.autoruns.push(Deps.autorun(this.updateScaleX.bind(this)));
this.autoruns.push(Deps.autorun(this.updateBars.bind(this)));
this.autoruns.push(Deps.autorun(this.updateLabels.bind(this)));
}
// destroy each autorun
BarChart.prototype.destroy = function () {
console.log("destroy");
var autorun;
while (autorun = this.autoruns.pop()) {
autorun.stop();
}
};
BarChart.prototype.updateScaleY = function () {
console.log("updateScaleY");
// get reactive dependencies
var height = Session.get("height");
var data = Session.get("data");
// construct a new d3js scale function
var val = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.value; })])
.range([height, 0]);
// store it as a reactive function
Session.set("scaleY", val);
};
// construct a new d3js scale function
// and store it as a reactive function
BarChart.prototype.updateScaleX = function () {
console.log("updateScaleX");
// get reactive dependencies
var width = Session.get("width");
var data = Session.get("data");
// construct a new d3js scale function
var val = d3.scale.ordinal()
.domain(d3.range(0, data.length))
.rangeRoundBands([0, width], .1);
// store it as a reactive function
Session.set("scaleX", val);
};
BarChart.prototype.updateBars = function () {
console.log("updateBars");
// get reactive dependencies
var data = Session.get("data");
var height = Session.get("height");
// get reactive function dependencies
var scaleX = Session.get("scaleX");
var scaleY = Session.get("scaleY");
// d3js code to create/update/remove the bars
var bars = this.barContainer.selectAll(".bar").data(data);
bars.enter().append("rect")
.attr("class", "bar");
bars
.attr("x", function(d, i) { return scaleX(i); })
.attr("width", scaleX.rangeBand())
.attr("y", function(d) { return scaleY(d.value); })
.attr("height", function(d) { return height - scaleY(d.value); });
bars.exit().remove();
};
BarChart.prototype.updateLabels = function () {
console.log("updateLabels");
// get reactive dependencies
var data = Session.get("data");
// get reactive function dependencies
var scaleX = Session.get("scaleX");
var scaleY = Session.get("scaleY");
// d3js code to create/update/remove the labels
var labels = this.labelContainer.selectAll(".label").data(data);
labels.enter().append("text")
.attr("class", "label");
labels.attr("x", function(d, i) { return scaleX(i) + scaleX.rangeBand() / 2 - 8; })
.text(function (d,i) { return d.name; })
.attr("y", function(d) { return scaleY(d.value) + 10; });
labels.exit().remove();
};
// form events that update reactive values of the chart
Template.editor.events({
'change input[name=width]' : function (event) {
var val = +event.target.value;
Session.set("width", val);
},
'change input[name=height]' : function (event) {
var val = +event.target.value;
Session.set("height", val);
},
'change input[name=data]' : function (event) {
var val = event.target.value;
var data = val
.split(',')
.map(function(d, i){
return {
name: "#"+(i+1),
value: parseInt(d)
};
});
Session.set("data", data);
}
});
Template.chart.rendered = function () {
console.log("rendered");
this.node = this.find("svg");
if (! this.instance) {
// create the bar chart once
this.instance = new BarChart(this.node);
}
};
Template.chart.destroyed = function () {
this.instance && this.instance.destroy();
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment