Reputation: 1070
I have made a plot with d3js and because the plot function goes outside the plot I wanted to clip it. At the same time I also thought about zooming. Therefor I started implementing the following example into my code; http://bl.ocks.org/mbostock/3892919
Unfortunately however, it seems that my implementation is incorrect. My x- and y-axis labels seem to disappear + the background has turned black + the actual plot is missing until you try to zoom :S.
Solution how to fix, i.e., correctly implement this is asked. Further I am also open for improvements of my code.
Jsfiddle of the version with zoom: http://jsfiddle.net/n3Lndkum/12/ Jsfiddle of the last working version: http://jsfiddle.net/n3Lndkum/9/
Embedded version:
var margin = {top: 20, right: 20, bottom: 50, left: 50},
width = 250 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom,
padding = 50;
var x = d3.scale.linear()
.range([0, width])
.domain([0, 10]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 10]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([0.1, 32])
.on("zoom", zoomed);
var data = [];
for (var k = -100; k < 101; k++) {
data.push({x: k/10, y: 0.5*k*k/100});
}
var line = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); })
.interpolate("linear");
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 + ")")
.call(zoom);
svg.append("rect")
.attr("width", width)
.attr("height", height);
// Add x axis
svg.append("g")
.attr("class","x axis")
.attr("transform","translate(0," + height + ")")
.call(xAxis);
// Add y axis
svg.append("g")
.attr("class","y axis")
.call(yAxis);
/* append additional X axis */
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + [0, 0] + ")")
.call(xAxis.innerTickSize(6).tickPadding(-20).tickFormat(""));
/* append additional y axis */
svg.append("g")
.attr("class","y axis")
.attr("transform", "translate(" + [width, 0] + ")")
.call(yAxis.innerTickSize(6).tickPadding(-20).tickFormat(""));
// Add x axis label
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + margin.bottom) + ")")
.style("font-size","15")
.style("text-anchor", "middle")
.text("x axis");
// Add y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y",0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("font-size","15")
.style("text-anchor", "middle")
.text("y axis");
// Add x grid
svg.append("g")
.attr("class","grid")
.attr("transform","translate(0," + height + ")")
.call(xAxis
.tickSize(-height,-height,0)
.tickFormat("")
);
// Add y grid
svg.append("g")
.attr("class","grid")
.call(yAxis
.tickSize(-width,-width,0)
.tickFormat("")
);
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
// Add data
svg.append("path")
.attr("class","line")
.attr("d",line(data));
}
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Upvotes: 1
Views: 97
Reputation: 108512
Few things here:
1.) You aren't applying a clip-path to the line. This is why it overruns the grid.
2.) Your grid is black because you've appended a rect
to it (so it can receive the pan/zoom mouse events) but you aren't setting it's fill to something (it's black by default). Note, you can't set it's fill to none since then it won't get the mouse events.
3.) You shouldn't share the axis definition like you are doing:
.call(xAxis.innerTickSize(6).tickPadding(-20).tickFormat(""));
The pan and zoom functionality is going to need to redraw the axis(es) and grid(s), keep a reference to each one you create.
4.) In your zoom event, don't re-append a new line. Select the existing one and update it's data:
d3.select(".line")
.attr("d", line(data));
Putting this all together:
<!DOCTYPE html>
<meta charset="utf-8">
<title>Zoom + Pan</title>
<style>
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
rect {
fill: transparent;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
clip-path: url(#clip);
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, 10])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 10])
.range([height, 0]);
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 32])
.on("zoom", zoomed);
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 + ")")
.call(zoom);
var xAxis1 = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var yAxis1 = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var xAxis2 = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(5)
.innerTickSize(6)
.tickPadding(-20)
.tickFormat("");
var yAxis2 = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(6)
.tickPadding(-20)
.tickFormat("");
var xGrid = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height, -height, 0)
.tickFormat("");
var yGrid = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, -width, 0)
.tickFormat("");
// Add x grid
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(xGrid);
// Add y grid
svg.append("g")
.attr("class", "y grid")
.call(yGrid);
svg.append("g")
.attr("class", "x1 axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis1);
svg.append("g")
.attr("class", "y1 axis")
.call(yAxis1);
/* append additional X axis */
svg.append("g")
.attr("class", "x2 axis")
.attr("transform", "translate(" + [0, 0] + ")")
.call(xAxis2);
/* append additional y axis */
svg.append("g")
.attr("class", "y2 axis")
.attr("transform", "translate(" + [width, 0] + ")")
.call(yAxis2);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height);
var data = [];
for (var k = -100; k < 101; k++) {
data.push({
x: k / 10,
y: 0.5 * k * k / 100
});
}
var line = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
})
.interpolate("linear");
svg.append("path")
.attr("class", "line")
.attr("d", line(data))
function zoomed() {
svg.select(".x1.axis").call(xAxis1);
svg.select(".y1.axis").call(yAxis1);
svg.select(".x2.axis").call(xAxis2);
svg.select(".y2.axis").call(yAxis2);
svg.select(".x.grid").call(xGrid);
svg.select(".y.grid").call(yGrid);
d3.select(".line")
.attr("d", line(data));
}
</script>
Upvotes: 2