简体   繁体   中英

Unable to Zoom in Stacked Bar Chart d3.js V4

I trying to build a stacked barchart with brush and zoom using d3.js v4.I was able to render the chart as well as the brush. However, when trying to zoom in only the axis changes and actual graph remains the same. I am attaching the snippet here.

Reference : https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172

Thanks for your help in advance.

 <!DOCTYPE html> <meta charset="utf-8"> <style> .zoom { cursor: move; fill: none; pointer-events: all; } .axis { stroke-width: 0.5px; stroke: #888; font: 10px avenir next, sans-serif; } .axis > path { stroke: #888; } </style> <svg width="960" height="500"></svg> <script src="https://d3js.org/d3.v4.min.js"></script> <script> var margin = {top: 20, right: 20, bottom: 90, left: 50}, margin2 = {top: 230, right: 20, bottom: 30, left: 50}, width = 960 - margin.left - margin.right, height = 300 - margin.top - margin.bottom, height2 = 300 - margin2.top - margin2.bottom; var parseTime = d3.timeParse("%Y-%m-%d"); var x = d3.scaleTime().range([0, width]), x2 = d3.scaleTime().range([0, width]), y = d3.scaleLinear().range([height, 0]), y2 = d3.scaleLinear().range([height2, 0]); z = d3.scaleOrdinal().range(["#a54300","#ec983d", "#ecc43d", "#f9ec86","#cbe989"]); var xAxis = d3.axisBottom(x), xAxis2 = d3.axisBottom(x2), yAxis = d3.axisLeft(y); var brush = d3.brushX() .extent([[0, 0], [width, height2]]) .on("brush", brushed); var zoom = d3.zoom() .scaleExtent([1, Infinity]) .translateExtent([[0, 0], [width, height]]) .extent([[0, 0], [width, height]]) .on("zoom", zoomed); var svg = d3.select("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); var focus = svg.append("g") .attr("class", "focus") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var context = svg.append("g") .attr("class", "context") .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")"); d3.csv("https://gist.githubusercontent.com/Udayshan07/320ea33bacc3e04f9bd77538007527a0/raw/06e65434349e63ed6cf85c4d3e9cb383e888010c/sp500.csv", function(d, i, columns) { for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]]; d.total = t; return d; }, function(error, data) { if (error) throw error; data.forEach(function(d) { d.Week_Num = parseTime(d.Week_Num); }); var xMin = d3.min(data, function(d) { return d.Week_Num; }); var yMax = Math.max(20, d3.max(data, function(d) { return d.total; })); var keys = data.columns.slice(1); var data = d3.stack().keys(keys)(data); x.domain([xMin, new Date()]); y.domain([0, yMax]); x2.domain(x.domain()); y2.domain(y.domain()) z.domain(keys); var messages = focus.append("g"); messages.selectAll("message") .data(data) .enter().append("g") .attr("fill", function(d) { return z(d.key); }) .selectAll("rect") .data(function(d) { return d; }) .enter().append("rect") .attr("x", function(d) { return x(d.data.Week_Num); }) .attr("y", function(d) { return y(d[1]); }) .attr("height", function(d) { return y(d[0]) - y(d[1]); }) .attr("width", 15) .on("mouseover", function() { tooltip.style("display", null); }) .on("mouseout", function() { tooltip.style("display", "none"); }) .on("mousemove", function(d) { console.log(d); var xPosition = d3.mouse(this)[0] - 5; var yPosition = d3.mouse(this)[1] - 5; tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")"); tooltip.select("text").text(d[1]-d[0]); }); focus.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height + ")") .call(xAxis.ticks(10)) .selectAll("text") .style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em") focus.append("g") .attr("class", "axis axis--y") .call(yAxis); svg.append("rect") .attr("class", "zoom") .attr("width", width) .attr("height", height) .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .call(zoom); context.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height2 + ")") .call(xAxis2) .selectAll("text") .style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em") .attr("transform", "rotate(-65)"); context.append("g") .attr("class", "brush") .call(brush) .call(brush.move, x.range()); }); function brushed() { //console.log('Brush 1'); if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom // console.log('Brush 2'); var s = d3.event.selection || x2.range(); x.domain(s.map(x2.invert, x2)); focus.selectAll(".message") .attr("x", function(d) { return x(d.Week_Num); }) .attr("y", function(d) { colsole.log("Brush" + y(d[1])); return y(d[1]); }); focus.select(".axis--x").call(xAxis); svg.select(".zoom").call(zoom.transform, d3.zoomIdentity .scale(width / (s[1] - s[0])) .translate(-s[0], 0)); } function zoomed() { // console.log('Zoom 1'); if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush // console.log('Zoom 2'); var t = d3.event.transform; x.domain(t.rescaleX(x2).domain()); focus.selectAll(".message") .attr("x", function(d) { return x(d.Week_Num); }) .attr("y", function(d) { colsole.log("Zoom" + y(d[1])); return y(d[1]);}); focus.select(".axis--x").call(xAxis); context.select(".brush").call(brush.move, x.range().map(t.invertX, t)); } var tooltip = svg.append("g") .attr("class", "tooltip") .style("display", "none"); tooltip.append("rect") .attr("width", 60) .attr("height", 20) .attr("fill", "white") .style("opacity", 0.5); tooltip.append("text") .attr("x", 30) .attr("dy", "1.2em") .style("text-anchor", "middle") .attr("font-size", "12px") .attr("font-weight", "bold"); </script> 

In relation to the question you have two primary issues:

  1. You are not selecting anything on zoom/drag events:

In the zoomed/drag functions:

 focus.selectAll(".message")
    .attr("x", function(d) { return x(d.Week_Num); })
    .attr("y", function(d) { colsole.log("Brush" + y(d[1])); return  y(d[1]); });

No items have the class message so this is an empty selection - which is why the command colsole.log doesn't trip an error, it's never run. You could just select all rect s in the focus selection instead (or give each rect the message class). Also, you don't need to re-position the y value, you aren't rescaling the y value, so it can stay the same, I've removed it in my snippet below

Once that is changed, we run into problem 2.

  1. You've changed the value you are passing the x scale. When you initially give an x attribute to each rect you use: x(d.data.Week_Num); , but in the drag/zoom functions you use: x(d.Week_Num); , this is easily fixed enough. This gives us:

 var margin = {top: 20, right: 20, bottom: 90, left: 50}, margin2 = {top: 230, right: 20, bottom: 30, left: 50}, width = 960 - margin.left - margin.right, height = 300 - margin.top - margin.bottom, height2 = 300 - margin2.top - margin2.bottom; var parseTime = d3.timeParse("%Y-%m-%d"); var x = d3.scaleTime().range([0, width]), x2 = d3.scaleTime().range([0, width]), y = d3.scaleLinear().range([height, 0]), y2 = d3.scaleLinear().range([height2, 0]); z = d3.scaleOrdinal().range(["#a54300","#ec983d", "#ecc43d", "#f9ec86","#cbe989"]); var xAxis = d3.axisBottom(x), xAxis2 = d3.axisBottom(x2), yAxis = d3.axisLeft(y); var brush = d3.brushX() .extent([[0, 0], [width, height2]]) .on("brush", brushed); var zoom = d3.zoom() .scaleExtent([1, Infinity]) .translateExtent([[0, 0], [width, height]]) .extent([[0, 0], [width, height]]) .on("zoom", zoomed); var svg = d3.select("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); var focus = svg.append("g") .attr("class", "focus") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var context = svg.append("g") .attr("class", "context") .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")"); d3.csv("https://gist.githubusercontent.com/Udayshan07/320ea33bacc3e04f9bd77538007527a0/raw/06e65434349e63ed6cf85c4d3e9cb383e888010c/sp500.csv", function(d, i, columns) { for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]]; d.total = t; return d; }, function(error, data) { if (error) throw error; data.forEach(function(d) { d.Week_Num = parseTime(d.Week_Num); }); var xMin = d3.min(data, function(d) { return d.Week_Num; }); var yMax = Math.max(20, d3.max(data, function(d) { return d.total; })); var keys = data.columns.slice(1); var data = d3.stack().keys(keys)(data); x.domain([xMin, new Date()]); y.domain([0, yMax]); x2.domain(x.domain()); y2.domain(y.domain()) z.domain(keys); var messages = focus.append("g"); messages.selectAll("message") .data(data) .enter().append("g") .attr("fill", function(d) { return z(d.key); }) .selectAll("rect") .data(function(d) { return d; }) .enter().append("rect") .attr("x", function(d) { return x(d.data.Week_Num); }) .attr("y", function(d) { return y(d[1]); }) .attr("height", function(d) { return y(d[0]) - y(d[1]); }) .attr("width", 15) .on("mouseover", function() { tooltip.style("display", null); }) .on("mouseout", function() { tooltip.style("display", "none"); }) .on("mousemove", function(d) { console.log(d); var xPosition = d3.mouse(this)[0] - 5; var yPosition = d3.mouse(this)[1] - 5; tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")"); tooltip.select("text").text(d[1]-d[0]); }); focus.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height + ")") .call(xAxis.ticks(10)) .selectAll("text") .style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em") focus.append("g") .attr("class", "axis axis--y") .call(yAxis); svg.append("rect") .attr("class", "zoom") .attr("width", width) .attr("height", height) .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .call(zoom); context.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height2 + ")") .call(xAxis2) .selectAll("text") .style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em") .attr("transform", "rotate(-65)"); context.append("g") .attr("class", "brush") .call(brush) .call(brush.move, x.range()); }); function brushed() { if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom var s = d3.event.selection || x2.range(); x.domain(s.map(x2.invert, x2)); focus.selectAll("rect") .attr("x", function(d) { return x(d.data.Week_Num); }) focus.select(".axis--x").call(xAxis); svg.select(".zoom").call(zoom.transform, d3.zoomIdentity .scale(width / (s[1] - s[0])) .translate(-s[0], 0)); } function zoomed() { if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; var t = d3.event.transform; x.domain(t.rescaleX(x2).domain()); focus.selectAll("rect") .attr("x", function(d) { return x(d.data.Week_Num); }); focus.select(".axis--x").call(xAxis); context.select(".brush").call(brush.move, x.range().map(t.invertX, t)); } var tooltip = svg.append("g") .attr("class", "tooltip") .style("display", "none"); tooltip.append("rect") .attr("width", 60) .attr("height", 20) .attr("fill", "white") .style("opacity", 0.5); tooltip.append("text") .attr("x", 30) .attr("dy", "1.2em") .style("text-anchor", "middle") .attr("font-size", "12px") .attr("font-weight", "bold"); 
  .zoom { cursor: move; fill: none; pointer-events: all; } .axis { stroke-width: 0.5px; stroke: #888; font: 10px avenir next, sans-serif; } .axis > path { stroke: #888; } 
 <svg width="960" height="500"></svg> <script src="https://d3js.org/d3.v4.min.js"></script> 

This question doesn't address the need for a clip path so I haven't included it in my answer, but there are a considerable number of questions on that topic that should help, such as this one from a couple days ago.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM