首页 > 解决方案 > 如何使用多个 y 轴正确放大图表

问题描述

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

由于蓝线和左轴都在更新,因此蓝线的缩放功能正常工作。但是,当红线放大时,右轴没有更新。我已经为右轴硬编码了一个从 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));
    }

标签: javascriptd3.js

解决方案


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

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

顺便说一句,你不需要thisArgin map,它可以只是:

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>


推荐阅读