Line chart that displays three dimensions of data taken from an accelerometer. This example is designed to implement zooming, while still maintaining the highlighting and tooltips from Signal Plot II.
The chart uses d3.zoom to implement zooming and panning for the x-axis. Adding the zoom can be done with relatively few steps:
// setup zoom
var zoom = d3.zoom()
.scaleExtent([1, 50])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
The callback function zoomed will also need to be defined. Below shows the callback for rescaling only in the x dimension, as well as redrawing the x-axis:
function zoomed() {
var t = d3.event.transform;
x.domain(t.rescaleX(x0).domain());
dims.forEach(dim => {
var selector = ".line--" + dim;
svg.select(selector)
.attr("d", lines[dim]);
});
svg.select(".axis--x").call(d3.axisBottom(x));
}
Note how the x0 scale is also needed for a point of reference:
var x0 = d3.scaleLinear().range([0, width]);
...
x0.domain(x.domain());
// create zoom portal in background
var rect = svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
Note: Make sure that this section goes after the zoom behavior definition, but before drawing any of the lines or axes. This will position the zoom portal in the back, allowing the highlighting and tooltip events to still propagate.
// Create clip-path
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
Make sure that zoom portal accepts pointer-events, and that the lines use the clip-path.
.zoom {
fill: none;
cursor: move;
pointer-events: all;
}
.line {
fill: none;
opacity: 0.9;
stroke-width: 1.5px;
clip-path: url(#clip);
}
This little snippet sets the initial zoom to x2 and sets the position to the start of the data:
// Set initial zoom/position
zoom.scaleBy(rect, 2);
zoom.translateBy(rect, width);
Note: The order matters here: scale then translate.
Highlighting follows a pattern inspired by lwthatcher's block: Multi y-axis with mouseover, where when a line is moused-over the line is highlighted, while other lines are dimmed.
The tooltips are implemented based off of d3noob's block: Simple tooltips in v4, with some additional css styling changes.
Built with blockbuilder.org
forked from lwthatcher's block: Signal Plot I
forked from lwthatcher's block: Signal Plot II (highlighting + tooltips)
forked from lwthatcher's block: Signal Plot III (zoom)
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.line {
fill: none;
opacity: 0.9;
stroke-width: 1.5px;
clip-path: url(#clip);
}
.line--x {
stroke: steelblue;
}
.line--y {
stroke: coral;
}
.line--z {
stroke: purple;
}
.line--fade {
stroke-width: 1.4px;
opacity: 0.5;
}
.line--hover {
stroke-width: 2px;
opacity: 1.0;
}
div.tooltip {
position: absolute;
text-align: center;
pointer-events: none;
line-height: 1;
font-weight: bold;
padding: 8px;
background: rgba(0, 0, 0, 0.6);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
div.tooltip:after {
box-sizing: border-box;
display: inline;
font-size: 11px;
top: 95%;
left: 50%;
line-height: 1;
color: rgba(0, 0, 0, 0.6);
content: "\25BC";
position: absolute;
text-align: center;
}
.zoom {
fill: none;
cursor: move;
pointer-events: all;
}
</style>
</head>
<body>
<script>
// set the dimensions and margins of the graph
var margin = {top: 20, right: 40, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// the different dimensions in the data
var dims = ['x', 'y', 'z'];
// set x-range
var x = d3.scaleLinear().range([0, width]);
var x0 = d3.scaleLinear().range([0, width]);
// set the y-ranges
var Y = {}
dims.forEach(dim => {
Y[dim] = d3.scaleLinear().range([height, 0]);
});
// define the line functions for each dimension
var lines = {}
dims.forEach(dim => {
lines[dim] = d3.line()
.x(d => x(d.tick))
.y(d => Y[dim](d[dim]));
});
// tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// create the svg object
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Create clip-path
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
// process the data
d3.csv("accel-data.csv",(error, data) => {
if (error) throw error;
// Parse the data
data.forEach(d => {
d.tick = +d.tick;
d.x = +d.x;
d.y = +d.y;
d.z = +d.z;
});
// Set x-domain scales
x.domain(d3.extent(data, d => d.tick));
x0.domain(x.domain());
// Set y-domain scales
for(var dim of dims) {
Y[dim].domain(d3.extent(data, d => d[dim]));
}
// setup zoom
var zoom = d3.zoom()
.scaleExtent([1, 50])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
// create zoom portal in background
var rect = svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
// Draw Paths
for (var dim of dims) {
svg.append("path")
.data([data])
.attr("class", "line line--"+dim)
.attr("name", dim)
.attr("d", d => lines[dim](d))
.on("mouseover", mouseover)
.on("mouseout", mouseout);
}
// Add the X Axis
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(Y['x']));
// Set initial zoom/position
zoom.scaleBy(rect, 2);
zoom.translateBy(rect, width);
// ===Functions===
function mouseover(d) {
var name = d3.select(this).attr("name");
// highlight this line, fade other lines
d3.selectAll(".line").classed("line--hover", (d, i) => {
return (name === dims[i]);
}).classed("line--fade", (d, i) => {
return (name !== dims[i]);
});
// display tooltip
div.transition()
.duration(200)
.style("opacity", 1);
div.html("accelerometer - " + name)
.style("left", (d3.event.pageX - 70) + "px")
.style("top", (d3.event.pageY - 40) + "px");
}
function mouseout(d) {
// turn off hover and fade effects
d3.selectAll(".line")
.classed("line--hover", false)
.classed("line--fade", false);
// hide tooltip
div.transition()
.duration(500)
.style("opacity", 0);
}
function zoomed() {
var t = d3.event.transform;
x.domain(t.rescaleX(x0).domain());
dims.forEach(dim => {
var selector = ".line--" + dim;
svg.select(selector)
.attr("d", lines[dim]);
});
svg.select(".axis--x").call(d3.axisBottom(x));
}
});
</script>
</body>
https://d3js.org/d3.v4.min.js