如何在D3中将值传递给行生成函数

时间:2017-04-09 17:58:54

标签: d3.js

以下代码用于在D3中生成行:

    var lineFn = d3.line()
        .x((d) => this.base.xAxis.scale(d.x))
        .y((d) => this.base.yAxes[0].scale(d.y));

    // series is a collection of lines I want to plot
    series = [
         {
             data: [{x: 10, y: 20}, {x: 20, y: 30}],
             yAxis: 0,   // this indicates which y-axis to use
             color: red
         },
         ...
    ];

    _.forEach(series, (line) => {
        this.base.chart.append("path")
            .datum(line.data)
            .attr("class", "line")
            .attr("d", lineFn)
            .style("stroke", line.color)
    });

我的图表使用d3.axisLeft()d3.axisRight()的双y轴。

现在,我正在硬编码y-axis中使用lineFn的值。

.y((d) => this.base.yAxes[0].scale(d.y)); // 0-left axis, 1-right axis

我想要做的是在调用line函数时传递该值,例如:

.attr("d", lineFn(line.yAxis))

有没有办法实现这个目标?

感谢。

1 个答案:

答案 0 :(得分:1)

The easiest way to achieve what you want is simply creating two different line generators.

However, since you asked (not verbatim) "is it possible to define the scale dynamically when calling the line generator?", the answer is: yes, it is possible. Let's see how to do it.

In this example, I'm using an object to store the different scales:

var scales = {
    yScaleLeft: d3.scaleLinear()
        .domain([0, 100])
        .range([170, 30]),
    yScaleRight: d3.scaleLinear()
        .domain([0, 200])
        .range([170, 30])
};

And, in the dataset, defining which scale and color should be used for each line, just as you did:

var data = [{
    data: [{
        x: 1,
        y: 20
    }, {
        ...
    }, {
        x: 8,
        y: 50
    }],
    yAxis: "yScaleLeft",
    color: "red"
}, {
    data: [{
        x: 3,
        y: 120
    }, {
        ...
    }, {
        x: 9,
        y: 180
    }],
    yAxis: "yScaleRight",
    color: "blue"
}];

Then, when calling the line generator, we set a variable (in this case, thisScale) to specify the scale:

var thisScale;

paths.attr("stroke", d => d.color)
    .attr("d", d => {
        thisScale = scales[d.yAxis]
        return line(d.data);
    })
    .attr("fill", "none");

Here is the demo, the red line uses a scale going from 0 to 100, the blue line uses a scale going from 0 to 200:

var svg = d3.select("body").append("svg")
  .attr("width", 500)
  .attr("height", 200);
  
var thisScale;

var line = d3.line()
  .x(d => xScale(d.x))
  .y(d => thisScale(d.y))
  .curve(d3.curveMonotoneX);

var data = [{
  data: [{
    x: 1,
    y: 20
  }, {
    x: 2,
    y: 30
  }, {
    x: 3,
    y: 10
  }, {
    x: 4,
    y: 60
  }, {
    x: 5,
    y: 70
  }, {
    x: 6,
    y: 80
  }, {
    x: 7,
    y: 40
  }, {
    x: 8,
    y: 50
  }],
  yAxis: "yScaleLeft",
  color: "red"
}, {
  data: [{
    x: 3,
    y: 120
  }, {
    x: 4,
    y: 130
  }, {
    x: 5,
    y: 10
  }, {
    x: 6,
    y: 120
  }, {
    x: 7,
    y: 40
  }, {
    x: 8,
    y: 130
  }, {
    x: 9,
    y: 180
  }],
  yAxis: "yScaleRight",
  color: "blue"
}];

var scales = {
  yScaleLeft: d3.scaleLinear()
    .domain([0, 100])
    .range([170, 30]),
  yScaleRight: d3.scaleLinear()
    .domain([0, 200])
    .range([170, 30])
};

var xScale = d3.scalePoint()
  .domain(d3.range(11))
  .range([30, 470])

var paths = svg.selectAll("foo")
  .data(data)
  .enter()
  .append("path");

paths.attr("stroke", d => d.color)
  .attr("d", d => {
    thisScale = scales[d.yAxis]
    return line(d.data);
  })
  .attr("fill", "none");

var xAxis = d3.axisBottom(xScale);

var yAxisLeft = d3.axisLeft(scales.yScaleLeft);

var yAxisRight = d3.axisRight(scales.yScaleRight);

var gX = svg.append("g").attr("transform", "translate(0,170)").call(xAxis);

var gY = svg.append("g").attr("transform", "translate(30,0)").call(yAxisLeft);

var gY2 = svg.append("g").attr("transform", "translate(470,0)").call(yAxisRight);
<script src="https://d3js.org/d3.v4.min.js"></script>

And here the same solution, but using an array (instead of an object) to store the scales, as you asked in your question:

yAxis: 0//indicates the left axis
yAxis: 1//indicates the right axis

var svg = d3.select("body").append("svg")
  .attr("width", 500)
  .attr("height", 200);
  
var thisScale;

var line = d3.line()
  .x(d => xScale(d.x))
  .y(d => thisScale(d.y))
  .curve(d3.curveMonotoneX);

var data = [{
  data: [{
    x: 1,
    y: 20
  }, {
    x: 2,
    y: 30
  }, {
    x: 3,
    y: 10
  }, {
    x: 4,
    y: 60
  }, {
    x: 5,
    y: 70
  }, {
    x: 6,
    y: 80
  }, {
    x: 7,
    y: 40
  }, {
    x: 8,
    y: 50
  }],
  yAxis: 0,
  color: "red"
}, {
  data: [{
    x: 3,
    y: 120
  }, {
    x: 4,
    y: 130
  }, {
    x: 5,
    y: 10
  }, {
    x: 6,
    y: 120
  }, {
    x: 7,
    y: 40
  }, {
    x: 8,
    y: 130
  }, {
    x: 9,
    y: 180
  }],
  yAxis: 1,
  color: "blue"
}];

var scales = [d3.scaleLinear()
  .domain([0, 100])
  .range([170, 30]), d3.scaleLinear()
  .domain([0, 200])
  .range([170, 30])
];

var xScale = d3.scalePoint()
  .domain(d3.range(11))
  .range([30, 470])

var paths = svg.selectAll("foo")
  .data(data)
  .enter()
  .append("path");

paths.attr("stroke", d => d.color)
  .attr("d", d => {
    thisScale = scales[d.yAxis]
    return line(d.data);
  })
  .attr("fill", "none");

var xAxis = d3.axisBottom(xScale);

var yAxisLeft = d3.axisLeft(scales[0]);

var yAxisRight = d3.axisRight(scales[1]);

var gX = svg.append("g").attr("transform", "translate(0,170)").call(xAxis);

var gY = svg.append("g").attr("transform", "translate(30,0)").call(yAxisLeft);

var gY2 = svg.append("g").attr("transform", "translate(470,0)").call(yAxisRight);
<script src="https://d3js.org/d3.v4.min.js"></script>