d3 zoom pan to specific region of line graph

余生长醉 提交于 2020-12-15 07:17:27

问题


I see a lot of tutorials and resources for zooming and panning based on click events, but I have a line graph that I want the user to be able to zoom into based on clicking buttons which align with certain data ranges.

[Specifically: the graph is initiated using all data, but when the user clicks on either 1, 2, or 3 use some effect that shows they're "zooming" into a subset of the graph]

I tried using a transform in the code below but it looks a little wonky. I think I want something closer to an automatic zoom/pan to convey that the buttons are "flying" you to certain region within the graph.

I've attached my code below but was wondering how to achieve this effect? Any help appreciated!

function getFilteredData(data, group) {
  console.log(group)
  if (group === 4) {
    return data
  } else {
    return data.filter(function (point) { return point.group === parseInt(group); });
  }
}

var data3 = [
  { group: 1, ser1: 1, ser2: 3 },
  { group: 1, ser1: 2, ser2: 5 },
  { group: 1, ser1: 3, ser2: 9 },
  { group: 1, ser1: 4, ser2: 3 },
  { group: 1, ser1: 5, ser2: 5 },
  { group: 1, ser1: 6, ser2: 9 },
  { group: 2, ser1: 7, ser2: 10 },
  { group: 2, ser1: 8, ser2: 9 },
  { group: 2, ser1: 9, ser2: 10 },
  { group: 2, ser1: 10, ser2: 20 },
  { group: 2, ser1: 11, ser2: 10 },
  { group: 2, ser1: 12, ser2: 12 },
  { group: 3, ser1: 13, ser2: 20 },
  { group: 3, ser1: 14, ser2: 12 },
  { group: 3, ser1: 15, ser2: 4 },
  { group: 3, ser1: 16, ser2: 22 },
  { group: 3, ser1: 17, ser2: 2 },
  { group: 3, ser1: 18, ser2: 4 },
]

var studio = [
  { x: 0, y: 8 },
  { x: 18, y: 8 }
]

var line = d3.line()
  .x(d => x(d.x))
  .y(d => y(d.y))

// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 50 },
  width = 700 - margin.left - margin.right,
  height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .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 + ")");

// Initialise a X axis:
var x = d3.scaleLinear().range([0, width - 100]);
var xAxis = d3.axisBottom().scale(x);
svg.append("g")
  .attr("transform", "translate(0," + height + ")")
  .attr("class", "myXaxis")

// Initialize an Y axis
var y = d3.scaleLinear().range([height, 0]);
var yAxis = d3.axisLeft().scale(y);
svg.append("g")
  .attr("class", "myYaxis")

// create the Y axis
y.domain([0, d3.max(data3, function (d) { return d.ser2 })]);
svg.selectAll(".myYaxis")
  .transition()
  .duration(1000)
  .call(yAxis);

// Create a function that takes a dataset as input and update the plot:
function update(data) {

  // Create the X axis:
  x.domain([d3.min(data, function (d) { return d.ser1 }), d3.max(data, function (d) { return d.ser1 })]);
  svg.selectAll(".myXaxis").transition()
    .duration(1000)
    .call(xAxis);

  // Create a update selection: bind to the new data
  var u = svg.selectAll(".lineTest")
    .data([data], function (d) { return d.ser1 });

  // Update the line
  u
    .enter()
    .append("path")
    .attr("class", "lineTest")
    .merge(u)
    .transition()
    .duration(1000)
    .attr("d", d3.line()
      .x(function (d) { return x(d.ser1); })
      .y(function (d) { return y(d.ser2); }))
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-width", 2.5)

}

// At the beginning, I run the update function on the first dataset:
update(data3)
.mayabtn {
  margin-left: 30px;
}
<script src="https://d3js.org/d3.v5.js"></script>
<button onclick="update(getFilteredData(data3, 1))" class="mayabtn">1.0</button>
<button onclick="update(getFilteredData(data3, 2))">2.0</button>
<button onclick="update(getFilteredData(data3, 3))">3.0</button>
<button onclick="update(getFilteredData(data3, 4))">All</button>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>

回答1:


One possible way would be the following:

  • Don't change the datum object of the line, so you always plot the entire spectrum;
  • Change the x.domain(), however, and then re-draw the line;
  • Use a clipPath element to avoid the line from overflowing.

Because the line has already been drawn, it will just be moved left and right when the x.domain() changes. This gives the desired zoom effect.

function getFilteredData(data, group) {
  console.log(group)
  if (group === 4) {
    return data
  } else {
    return data.filter(function (point) { return point.group === parseInt(group); });
  }
}

var data3 = [
  { group: 1, ser1: 1, ser2: 3 },
  { group: 1, ser1: 2, ser2: 5 },
  { group: 1, ser1: 3, ser2: 9 },
  { group: 1, ser1: 4, ser2: 3 },
  { group: 1, ser1: 5, ser2: 5 },
  { group: 1, ser1: 6, ser2: 9 },
  { group: 2, ser1: 7, ser2: 10 },
  { group: 2, ser1: 8, ser2: 9 },
  { group: 2, ser1: 9, ser2: 10 },
  { group: 2, ser1: 10, ser2: 20 },
  { group: 2, ser1: 11, ser2: 10 },
  { group: 2, ser1: 12, ser2: 12 },
  { group: 3, ser1: 13, ser2: 20 },
  { group: 3, ser1: 14, ser2: 12 },
  { group: 3, ser1: 15, ser2: 4 },
  { group: 3, ser1: 16, ser2: 22 },
  { group: 3, ser1: 17, ser2: 2 },
  { group: 3, ser1: 18, ser2: 4 },
]

var studio = [
  { x: 0, y: 8 },
  { x: 18, y: 8 }
]

var line = d3.line()
  .x(function (d) { return x(d.ser1); })
  .y(function (d) { return y(d.ser2); })

// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 50 },
  width = 700 - margin.left - margin.right,
  height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .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 + ")");

svg.append("defs")
  .append("clipPath")
  .attr("id", "chart-path")
  .append("rect")
  .attr("width", width)
  .attr("height", height);

// Initialise a X axis:
var x = d3.scaleLinear().range([0, width]);
var xAxis = d3.axisBottom().scale(x);
svg.append("g")
  .attr("transform", "translate(0," + height + ")")
  .attr("class", "myXaxis")

// Initialize an Y axis
var y = d3.scaleLinear().range([height, 0]);
var yAxis = d3.axisLeft().scale(y);
svg.append("g")
  .attr("class", "myYaxis")

// create the Y axis
y.domain([0, d3.max(data3, function (d) { return d.ser2 })]);
svg.selectAll(".myYaxis")
  .transition()
  .duration(1000)
  .call(yAxis);

// Create a update selection: bind to the new data
var u = svg.selectAll(".lineTest")
  .data([data3])
  .enter()
  .append("path")
  .attr("class", "lineTest")
  .attr("fill", "none")
  .attr("stroke", "steelblue")
  .attr("stroke-width", 2.5)
  .attr("clip-path", "url(#chart-path)");

// Create a function that takes a dataset as input and update the plot:
function update(data) {

  // Create the X axis:
  x.domain(d3.extent(data, function (d) { return d.ser1 }));
  svg.selectAll(".myXaxis")
    .transition()
    .duration(1000)
    .call(xAxis);
  u.transition()
    .duration(1000)
    .attr("d", line);
}

// At the beginning, I run the update function on the first dataset:
update(data3)
.mayabtn {
  margin-left: 30px;
}
<script src="https://d3js.org/d3.v5.js"></script>
<button onclick="update(getFilteredData(data3, 1))" class="mayabtn">1.0</button>
<button onclick="update(getFilteredData(data3, 2))">2.0</button>
<button onclick="update(getFilteredData(data3, 3))">3.0</button>
<button onclick="update(getFilteredData(data3, 4))">All</button>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>


来源:https://stackoverflow.com/questions/64729567/d3-zoom-pan-to-specific-region-of-line-graph

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!