【问题标题】:How to zoom in chart correctly with multiple y-axis如何使用多个 y 轴正确放大图表
【发布时间】:2019-10-10 10:30:38
【问题描述】:

我已经为我的图表实现了缩放功能,可以通过拖动并释放该区域来放大图表的某个区域。我的小提琴可以访问here

蓝线的缩放功能正常工作,因为蓝线和左轴都在更新。但是,当红线放大时,右轴没有被更新。我已经为右轴硬编码了一个从 0 到 200 的域,所以每当我放大域时,域都会从 0 变为 200,而不是正确的放大域。右侧轴的域代码应该是什么,以便在缩放期间更新?非常感谢任何帮助!

    var data = [ {x: 0, y: 0, y1: 0}, {x: 1, y: 30, y1: 100}, {x: 2, y: 40, y1: 200},
                {x: 3, y: 60, y1: 300}, {x: 4, y: 70, y1: 400}, {x: 5, y: 90, y1: 500} ];

    const margin = {
      left: 20,
      right: 20,
      top: 20,
      bottom: 80
    };

    const svg = d3.select('svg');
    svg.selectAll("*").remove();

    const width = 200 - margin.left - margin.right;
    const height = 200 - margin.top - margin.bottom;

    const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);

    var x = d3.scaleLinear()
            .domain([0, d3.max(data, function(d){ return d.x; })])
            .range([0,width])
            .nice();

    var y = d3.scaleLinear()
            .domain([0, d3.max(data, function(d){ return d.y; })])
            .range([0,height])
            .nice();

    var y1 = d3.scaleLinear()
            .domain([0, d3.max(data, function(d) { return d.y1; })])
            .range([0, height])
            .nice();

    const xAxis = d3.axisTop()
                .scale(x)
                .ticks(5)
                .tickPadding(3)
                .tickSize(-height)

    const yAxis = d3.axisLeft()
                .scale(y)
                .ticks(5)
                .tickPadding(3)
                .tickSize(-width);

    const yAxis1 = d3.axisRight()
                .scale(y1)

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(20,20)")
      .call(xAxis);

    svg.append("g")
      .attr("class", "y axis")
      .attr("transform", "translate(20,20)")
      .call(yAxis);

    svg.append("g")
      .attr("class", "y1 axis")
      .attr("transform", "translate(185,20)")
      .call(yAxis1);


    var lineFunction = d3.line()
    .x(function(d) {return x(d.x); })
    .y(function(d) {return y(d.y); })
    .curve(d3.curveLinear);

    var lineFunctionOne = d3.line()
    .x(function(d) {return x(d.x); })
    .y(function(d) {return y1(d.y1); })
    .curve(d3.curveLinear);

    //defining and plotting the lines
    var path = g.append("path")
              .attr("class", "path1")
              .attr("id", "blueLine")
              .attr("d", lineFunction(data))
              .attr("stroke", "blue")
              .attr("stroke-width", 2)
              .attr("fill", "none")
              .attr("clip-path", "url(#clip)");

    var path1 = g.append("path")
              .attr("class", "path2")
              .attr("id", "redLine")
              .attr("d", lineFunctionOne(data))
              .attr("stroke", "red")
              .attr("stroke-width", 2)
              .attr("fill", "none")
              .attr("clip-path", "url(#clip)");

    //************* Zoom ***************
    //add brushing
    var brush = d3.brush().extent([[0, 0], [width, height]]).on("end", brushended),
        idleTimeout,
        idleDelay = 350;
      g.append("g")
      .attr("class", "brush")
      .call(brush);

    // Add a clipPath: everything out of this area won't be drawn when chart is zoomed in
    var 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);

    function brushended() {

      var s = d3.event.selection;
      //If no selection, re-initialize chart on double click. Otherwise, update x-axis and y-axis domain
      if (!s) {
        // This allows to wait a little bit
        if (!idleTimeout) return idleTimeout = setTimeout(idled, 350);
        x.domain(d3.extent(data, function (d) { return d.x; })).nice();
        y.domain(d3.extent(data, function (d) { return d.y; })).nice();
        y1.domain(d3.extent(data, function (d) { return d.y1; })).nice();
      } else {
        x.domain([s[0][0], s[1][0]].map(x.invert, x));
        y.domain([s[0][1], s[1][1]].map(y.invert, y));
        y1.domain([0, 200]); //hardcoded domain
        //This removes the grey brush area as soon as the selection has been done
        g.select(".brush").call(brush.move, null)
      }
      zoom();
    }

    function idled() {
      idleTimeout = null;
    }

    function zoom() {
      var t = svg.transition().duration(750);
      svg.select(".x.axis").transition(t).call(xAxis);
      svg.select(".y.axis").transition(t).call(yAxis);
      svg.select(".y1.axis").transition(t).call(yAxis1);
      svg.select(".path1").transition(t).attr("d", lineFunction(data));
      svg.select(".path2").transition(t).attr("d", lineFunctionOne(data));
    }

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    我对你的问题感到困惑......你需要做的只是上面一行所做的相同的事情:

    y1.domain([s[0][1], s[1][1]].map(y1.invert, y));
    

    顺便说一句,map 中的thisArg 不需要,可以只是:

    y1.domain([s[0][1], s[1][1]].map(y1.invert));
    

    这是更新后的代码:

    var data = [{
        x: 0,
        y: 0,
        y1: 0
      }, {
        x: 1,
        y: 30,
        y1: 100
      }, {
        x: 2,
        y: 40,
        y1: 200
      },
      {
        x: 3,
        y: 60,
        y1: 300
      }, {
        x: 4,
        y: 70,
        y1: 400
      }, {
        x: 5,
        y: 90,
        y1: 500
      }
    ];
    
    const margin = {
      left: 20,
      right: 20,
      top: 20,
      bottom: 80
    };
    
    const svg = d3.select('svg');
    svg.selectAll("*").remove();
    
    const width = 200 - margin.left - margin.right;
    const height = 200 - margin.top - margin.bottom;
    
    const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
    
    var x = d3.scaleLinear()
      .domain([0, d3.max(data, function(d) {
        return d.x;
      })])
      .range([0, width])
      .nice();
    
    var y = d3.scaleLinear()
      .domain([0, d3.max(data, function(d) {
        return d.y;
      })])
      .range([0, height])
      .nice();
    
    var y1 = d3.scaleLinear()
      .domain([0, d3.max(data, function(d) {
        return d.y1;
      })])
      .range([0, height])
      .nice();
    
    const xAxis = d3.axisTop()
      .scale(x)
      .ticks(5)
      .tickPadding(3)
      .tickSize(-height)
    
    const yAxis = d3.axisLeft()
      .scale(y)
      .ticks(5)
      .tickPadding(3)
      .tickSize(-width);
    
    const yAxis1 = d3.axisRight()
      .scale(y1)
    
    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(20,20)")
      .call(xAxis);
    
    svg.append("g")
      .attr("class", "y axis")
      .attr("transform", "translate(20,20)")
      .call(yAxis);
    
    svg.append("g")
      .attr("class", "y1 axis")
      .attr("transform", "translate(185,20)")
      .call(yAxis1);
    
    
    var lineFunction = d3.line()
      .x(function(d) {
        return x(d.x);
      })
      .y(function(d) {
        return y(d.y);
      })
      .curve(d3.curveLinear);
    
    var lineFunctionOne = d3.line()
      .x(function(d) {
        return x(d.x);
      })
      .y(function(d) {
        return y1(d.y1);
      })
      .curve(d3.curveLinear);
    
    //defining the lines
    var path = g.append("path")
      .attr("class", "path1")
      .attr("id", "blueLine")
      .attr("d", lineFunction(data))
      .attr("stroke", "blue")
      .attr("stroke-width", 2)
      .attr("fill", "none")
      .attr("clip-path", "url(#clip)");
    
    var path1 = g.append("path")
      .attr("class", "path2")
      .attr("id", "redLine")
      .attr("d", lineFunctionOne(data))
      .attr("stroke", "red")
      .attr("stroke-width", 2)
      .attr("fill", "none")
      .attr("clip-path", "url(#clip)");
    
    //************* Zoom ***************
    //add brushing
    var brush = d3.brush().extent([
        [0, 0],
        [width, height]
      ]).on("end", brushended),
      idleTimeout,
      idleDelay = 350;
    g.append("g")
      .attr("class", "brush")
      .call(brush);
    
    // Add a clipPath: everything out of this area won't be drawn when chart is zoomed in
    var 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);
    
    function brushended() {
    
      var s = d3.event.selection;
      //If no selection, re-initialize chart on double click. Otherwise, update x-axis and y-axis domain
      if (!s) {
        // This allows to wait a little bit
        if (!idleTimeout) return idleTimeout = setTimeout(idled, 350);
        x.domain(d3.extent(data, function(d) {
          return d.x;
        })).nice();
        y.domain(d3.extent(data, function(d) {
          return d.y;
        })).nice();
        y1.domain(d3.extent(data, function(d) {
          return d.y1;
        })).nice();
      } else {
        x.domain([s[0][0], s[1][0]].map(x.invert, x));
        y.domain([s[0][1], s[1][1]].map(y.invert, y));
        y1.domain([s[0][1], s[1][1]].map(y1.invert, y)); //hardcoded domain
        //This removes the grey brush area as soon as the selection has been done
        g.select(".brush").call(brush.move, null)
      }
      zoom();
    }
    
    function idled() {
      idleTimeout = null;
    }
    
    function zoom() {
      var t = svg.transition().duration(750);
      svg.select(".x.axis").transition(t).call(xAxis);
      svg.select(".y.axis").transition(t).call(yAxis);
      svg.select(".y1.axis").transition(t).call(yAxis1);
      svg.select(".path1").transition(t).attr("d", lineFunction(data));
      svg.select(".path2").transition(t).attr("d", lineFunctionOne(data));
    }
    .xy_chart {
      position: relative;
      left: 50px
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
    <svg class="xy_chart"></svg>

    【讨论】:

    • 啊,我实际上尝试过,但在 map 中将 thisArg 保留为 y1,好像我误解了 .map()。感谢您的帮助!
    猜你喜欢
    • 2018-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多