D3.js放大面积图,y域的奇怪行为取负值

D3.js Zoom on area graph, weird behaivor if y domain takes negative value

我一直在尝试做一个带缩放的面积图,除非我给 Y 域一个负值,否则效果很好。然后如果我不尝试沿 y 轴缩放,它看起来很棒

我已经尝试使用 y 的最小值作为 y0 并且修复了渲染(它看起来很糟糕并且它渲染 X 没有值)。

// set the dimensions and margins of the graph
let margin = {
    top: 0,
    right: 0,
    bottom: 30,
    left: 30
  },
  width = 440 - margin.left - margin.right,
  height = 240 - margin.top - margin.bottom;

// append the svg object to the body of the page
let 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 + ")");

//Read the data
d3.csv("https://raw.githubusercontent.com/dimitriiBirsan/RandomNumbersYear1970-2031/main/testing%20negative%20numbers.csv",

  // When reading the csv, I must format variables:
  function(d) {
    return {
      date: d3.timeParse("%m/%d/%Y")(d.date),
      value: d.value
    }
  },

  // Now I can use this dataset:
  function(data) {
    // Add X axis --> it is a date format
    let x = d3.scaleTime()
      .domain(d3.extent(data, function(d) {
        return +d.date;
      }))
      .range([0, width]);
    let xAxis = svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x).ticks(4));

    // Add Y axis
    let y = d3.scaleLinear()
      .domain(d3.extent(data, function(d) {
        return +d.value;
      }))
      .range([height, 0]);
    let yAxis = svg.append("g")
      .call(d3.axisLeft(y));

    function make_x_gridlines(x) {
      return d3.axisBottom(x)
    }

    function make_y_gridlines(y) {
      return d3.axisLeft(y)
    }

    // Add a clipPath : everything out of this area won't be drawn
    let clip = svg.append("defs").append("svg:clipPath")
      .attr("id", "clip")
      .append("svg:rect")
      .attr("width", width)
      .attr("height", height)
      .attr("x", 0)
      .attr("y", 0);

    // Add the area
    let line = svg.append('g')
      .attr("clip-path", "url(#clip)")
    line.append("path")
      .datum(data)
      .attr("class", "polyArea")
      .attr("fill", "blue")
      .attr("opacity", 0.7)
      .attr("stroke", "none")
      .attr("stroke-width", 1.5)
      .attr("d", d3.area()
        .x(function(d) {
          return x(d.date)
        })
        .y0(y(0))
        .y1(function(d) {
          return y(d.value)
        })
        .curve(d3.curveStepAfter)
        .defined((d, i) => (i != null))
      );

    let zoom = d3.zoom()
      .scaleExtent([1, 50]) // This control how much you can unzoom (x0.5) and zoom (x20)
      .translateExtent([
        [0, 0],
        [width, height]
      ])
      .extent([
        [0, 0],
        [width, height]
      ])
      .on("zoom", updateChart);

    // This adds an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
    svg.append("rect")
      .attr("width", width)
      .attr("height", height)
      .style("fill", "none")
      .style("pointer-events", "all")
      .attr('transform', 'translate(' + margin.top + ')')
      .call(zoom);

    function updateChart() {
      // recover the new scale
      let newX = d3.event.transform.rescaleX(x);
      let newY = d3.event.transform.rescaleY(y);

      // update axes with these new boundaries
      xAxis.call(d3.axisBottom(newX).ticks(5))
      yAxis.call(d3.axisLeft(newY))
      // update location
      svg
        .select('.polyArea')
        .attr("d", d3.area()
          .x(function(d) {
            return newX(d.date)
          })
          .y0(y(0))
          .y1(function(d) {
            return newY(d.value)
          })
          .curve(d3.curveStepAfter)
          .defined((d, i) => (i != null)))
    }
  })
.grid line {
  stroke: grey;
  stroke-opacity: 0.7;
  shape-rendering: crispEdges;
}

.grid path {
  stroke-width 0;
}
<main>
  <div id="my_dataviz">
  </div>
</main>
<script src="https://d3js.org/d3.v4.js"></script>

您忘记使用 newY(0) 而不是 y(0) 所以现在当您缩放时,零线在它应该在的位置,而不是您开始时的位置

// set the dimensions and margins of the graph
let margin = {
    top: 0,
    right: 0,
    bottom: 30,
    left: 30
  },
  width = 440 - margin.left - margin.right,
  height = 240 - margin.top - margin.bottom;

// append the svg object to the body of the page
let 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 + ")");

//Read the data
d3.csv("https://raw.githubusercontent.com/dimitriiBirsan/RandomNumbersYear1970-2031/main/testing%20negative%20numbers.csv",

  // When reading the csv, I must format variables:
  function(d) {
    return {
      date: d3.timeParse("%m/%d/%Y")(d.date),
      value: d.value
    }
  },

  // Now I can use this dataset:
  function(data) {
    // Add X axis --> it is a date format
    let x = d3.scaleTime()
      .domain(d3.extent(data, function(d) {
        return +d.date;
      }))
      .range([0, width]);
    let xAxis = svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x).ticks(4));

    // Add Y axis
    let y = d3.scaleLinear()
      .domain(d3.extent(data, function(d) {
        return +d.value;
      }))
      .range([height, 0]);
    let yAxis = svg.append("g")
      .call(d3.axisLeft(y));

    function make_x_gridlines(x) {
      return d3.axisBottom(x)
    }

    function make_y_gridlines(y) {
      return d3.axisLeft(y)
    }

    // Add a clipPath : everything out of this area won't be drawn
    let clip = svg.append("defs").append("svg:clipPath")
      .attr("id", "clip")
      .append("svg:rect")
      .attr("width", width)
      .attr("height", height)
      .attr("x", 0)
      .attr("y", 0);

    // Add the area
    let line = svg.append('g')
      .attr("clip-path", "url(#clip)")
    line.append("path")
      .datum(data)
      .attr("class", "polyArea")
      .attr("fill", "blue")
      .attr("opacity", 0.7)
      .attr("stroke", "none")
      .attr("stroke-width", 1.5)
      .attr("d", d3.area()
        .x(function(d) {
          return x(d.date)
        })
        .y0(y(0))
        .y1(function(d) {
          return y(d.value)
        })
        .curve(d3.curveStepAfter)
        .defined((d, i) => (i != null))
      );

    let zoom = d3.zoom()
      .scaleExtent([1, 50]) // This control how much you can unzoom (x0.5) and zoom (x20)
      .translateExtent([
        [0, 0],
        [width, height]
      ])
      .extent([
        [0, 0],
        [width, height]
      ])
      .on("zoom", updateChart);

    // This adds an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
    svg.append("rect")
      .attr("width", width)
      .attr("height", height)
      .style("fill", "none")
      .style("pointer-events", "all")
      .attr('transform', 'translate(' + margin.top + ')')
      .call(zoom);

    function updateChart() {
      // recover the new scale
      let newX = d3.event.transform.rescaleX(x);
      let newY = d3.event.transform.rescaleY(y);

      // update axes with these new boundaries
      xAxis.call(d3.axisBottom(newX).ticks(5))
      yAxis.call(d3.axisLeft(newY))
      // update location
      svg
        .select('.polyArea')
        .attr("d", d3.area()
          .x(function(d) {
            return newX(d.date)
          })
          .y0(newY(0))
          .y1(function(d) {
            return newY(d.value)
          })
          .curve(d3.curveStepAfter)
          .defined((d, i) => (i != null)))
    }
  })
.grid line {
  stroke: grey;
  stroke-opacity: 0.7;
  shape-rendering: crispEdges;
}

.grid path {
  stroke-width 0;
}
<main>
  <div id="my_dataviz">
  </div>
</main>
<script src="https://d3js.org/d3.v4.js"></script>