Skip to content

Instantly share code, notes, and snippets.

@LiangGou
Last active May 2, 2016 04:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LiangGou/66a0d5d537e01b0658a693d2a9b85868 to your computer and use it in GitHub Desktop.
Save LiangGou/66a0d5d537e01b0658a693d2a9b85868 to your computer and use it in GitHub Desktop.
Small multiples with brush

Small multiples with brush

<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
padding: 10px;
}
.frame {
shape-rendering: crispEdges;
}
.frame {
fill: none;
stroke: #aaa;
stroke-opacity: 0.75;
stroke-width: 0.5;
}
.brush-mask {
fill: #fff;
stroke-opacity: 0;
fill-opacity: 0.8;
shape-rendering: crispEdges;
}
.feature-cell {}
.feature-line {
stroke-opacity: 1;
stroke-width: 1;
fill-opacity: 0;
}
.feature-area {
fill-opacity: 0.75;
stroke-width: 0;
}
.feature-rect {
fill-opacity: 0.75;
stroke-width: 0;
}
.feature-area:hover {
fill-opacity: 1;
}
.feature-rect:hover {
fill-opacity: 1;
stroke-width: 0.5;
stroke: #666;
}
.brush .extent {
opacity: 0;
shape-rendering: crispEdges;
}
</style>
<body>
<input name="changeType" type="button" value="UpdateType" onclick="changeType()" />
<input name="btnNewData" type="button" value="UpdateData" onclick="changeData()" />
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
size = 150,
cw = 100, //cell width
ch = 15, //cell hight
padding = 2,
curPlotType = "area", // or "bar"
nCol = 6; //Math.floor((width - padding) / size);
var color = d3.scale.category10();
var x = d3.scale.linear()
.range([padding / 2, cw - padding / 2]);
var y = d3.scale.linear()
.range([ch - padding / 2, padding / 2]);
var farea = d3.svg.area()
.interpolate("basis")
.x(function(d, i) {
return x(i);
})
.y0(ch - padding / 2)
.y1(function(d) {
return y(d);
});
var farea0 = d3.svg.area()
.interpolate("basis")
.x(function(d, i) {
return x(i);
})
.y0(ch - padding / 2)
.y1(function(d) {
return y(0);
});
var fline = d3.svg.line()
.interpolate("basis")
.x(function(d, i) {
return x(i);
})
.y(function(d) {
return y(d);
});
var fline0 = d3.svg.line()
.interpolate("basis")
.x(function(d, i) {
return x(i);
})
.y(function(d) {
return y(0);
});
function generateData(nFeatures, mBins) {
return d3.range(nFeatures).map(function(i) {
return {
"name": "feature" + i,
"col": i % nCol,
"row": Math.floor(i / nCol),
"bins": d3.range(mBins).map(function(j) {
return Math.random();
})
};
});
}
var fdata = generateData(100, 12);
//console.log("fdata", fdata);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", ch * (Math.floor(fdata.length / nCol) + 1) + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
renderAll();
function renderAll() {
var cells = svg.selectAll(".feature-cell")
.data(fdata);
//new data
cells.enter().append("g")
.attr("class", "feature-cell")
.attr("transform", function(d) {
return "translate(" + d.col * cw + "," + d.row * ch + ")";
})
.each(featurePlot);
//update
cells.each(updatePlot);
//remove
cells.exit().remove();
function featurePlot(f) {
var _cell = d3.select(this);
x.domain([0, f.bins.length - 1]);
y.domain([0, 1]);
var brush = d3.svg.brush()
.x(x)
.on("brush", brushed)
.on("brushend", brushend);
_cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", cw - padding)
.attr("height", ch - padding);
_cell.append("path")
.datum(f.bins)
.attr("class", "feature-area")
.style("fill", function() {
return color(f.name);
})
//.style("stroke", function() {return color(f.name);})
.attr("d", farea0)
.transition()
.duration(750)
.attr("d", farea);
_cell.append("path")
.datum(f.bins)
.attr("class", "feature-line")
.style("stroke", function() {
return color(f.name);
})
.attr("d", fline0)
.transition()
.duration(750)
.attr("d", fline);
var n = f.bins.length,
bw = ((cw - padding) / (n + 1)).toFixed(1); //bar width based on the # of bins and cell width; (n+1) is the calculation adjustment by considering the bar width (the last bar takes an extra bar width);
_cell.selectAll(".feature-rect")
.data(f.bins)
.enter()
.append("rect")
.attr("class", "feature-rect")
.attr("x", function(d, i) {
// n/(n+1) is the calculation adjustment by considering the bar width (the last bar takes an extra bar width);
return x(i * n / (n + 1));
})
.attr("y", y(0))
.attr("height", 0)
.attr("width", bw)
.style("fill", function(i) {
return color(f.name);
});
//
_cell.append("rect")
.attr("id", "brush_mask_left")
.attr("class", "brush-mask")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", 0)
.attr("height", ch - padding);
_cell.append("rect")
.attr("id", "brush_mask_right")
.attr("class", "brush-mask")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", 0)
.attr("height", ch - padding);
_cell.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("height", y(0));
function brushed() {
var ext = brush.extent();
if (!brush.empty()) {
d3.select(this.parentNode).select("#brush_mask_left").attr("width", x(ext[0]));
d3.select(this.parentNode).select("#brush_mask_right").attr("x", x(ext[1])).attr("width", x.range()[1] - x(ext[1]));
} else {
d3.select(this.parentNode).select("#brush_mask_left").attr("x", 0).attr("width", 0);
d3.select(this.parentNode).select("#brush_mask_right").attr("x", 0).attr("width", 0);
}
}
function brushend(){
//update data after brush end
var ext = brush.extent();
var f = d3.select(this.parentNode).datum();
if (!brush.empty()) {
var tmp = generateData(100, 12);
tmp[f.row * nCol + f.col] = fdata[f.row * nCol + f.col];
fdata = tmp;
renderAll();
}
}
}
function updatePlot(f) {
var _cell = d3.select(this);
x.domain([0, f.bins.length - 1]);
y.domain([0, 1]);
if (curPlotType == "area") {
_cell.select(".feature-area")
.datum(f.bins)
.transition()
.duration(750)
.attr("d", farea);
_cell.select(".feature-line")
.datum(f.bins)
.transition()
.duration(750)
.attr("d", fline);
}
if (curPlotType == "bar") {
var n = f.bins.length,
bw = ((cw - padding) / (n + 1)).toFixed(1); //bar width based on the # of bins and cell width; (n+1) is the calculation adjustment by considering the bar width (the last bar takes an extra bar width);
var bars = _cell.selectAll(".feature-rect")
.data(f.bins);
bars.transition()
.duration(750)
.attr("x", function(d, i) {
// n/(n+1) is the calculation adjustment by considering the bar width (the last bar takes an extra bar width);
return x(i * n / (n + 1));
})
.attr("y", function(d, i) {
return y(0) - y(d);
})
.attr("height", function(d, i) {
return y(d);
})
.attr("width", bw);
bars.exit().remove();
}
}
d3.select(self.frameElement).style("height", ch * (Math.floor(fdata.length / nCol) + 1) + padding + 20 + "px");
}
function changeType() {
if (curPlotType == "area") {
d3.selectAll(".feature-area")
.transition()
.duration(750)
.attr("d", farea0);
d3.selectAll(".feature-line")
.transition()
.duration(750)
.attr("d", fline0);
d3.selectAll(".feature-rect")
.transition()
.delay(500)
.duration(750)
.attr("y", function(d, i) {
return y(0) - y(d);
})
.attr("height", function(d, i) {
return y(d);
});
curPlotType = "bar";
} else
if (curPlotType == "bar") {
d3.selectAll(".feature-rect")
.transition()
.duration(750)
.attr("y", y(0))
.attr("height", 0);
d3.selectAll(".feature-area")
.transition()
.delay(500)
.duration(750)
.attr("d", farea);
d3.selectAll(".feature-line")
.transition()
.delay(500)
.duration(750)
.attr("d", fline);
curPlotType = "area";
}
}
function changeData() {
fdata = generateData(100, 12);
renderAll();
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment