带有虚线段的D3多线图

时间:2014-11-18 17:19:51

标签: d3.js charts linechart

现实生活场景:我需要在两线图上绘制收入/支出,我需要将上个月的线点缀,以表明它尚未确定,并且可以改变。下面的数据和代码基本上遵循mbostock的多线图示例,有两条路径。

现在,问题是使最后一段加点。我读过关于stroke-dasharrays的内容,并认为这是一个明智的想法,但我不知道如何获得路径中的最后一行“,我发现的每个教程都建议使用行代替,但我不能为我的生活重构我的代码使用我的数据集行。

任何帮助,或重构代码的指示,将不胜感激!谢谢,伙计们。

数据如下所示:

data =
{
    {
        'type': 'Expense',
        'data': [{ 'date': '201401', 'value': 456 }, {}, ...]
    },
    {
        'type': 'Income',
        'data': [{ 'date': '201401', 'value': 456 }, {}, ...]
    }
}

我使用了mbostock的教程,我的代码如下:

function(data)
{
    var self = this;
    this.graphWidth = this.container.width() - 200 - this.margin.left - this.margin.right;
    this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;

    this.svg = d3.select('#graphsContainer svg')
            .attr('width', this.graphWidth + this.margin.left + this.margin.right)
            .attr('height', this.graphHeight + this.margin.top + this.margin.bottom)
            .append('g')
            .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');

    this.x = d3.time.scale().range([0, this.graphWidth]);
    this.y = d3.scale.linear().range([this.graphHeight, 0]);

    this.xAxis = d3.svg.axis().scale(this.x).orient('bottom');
    this.yAxis = d3.svg.axis().scale(this.y).orient('left');

    var parseDate = d3.time.format('%Y%m').parse;

    var line = d3.svg.line()
            .interpolate('basis')
            .x(function(d) { return self.x(d.date); })
            .y(function(d) { return self.y(d.value); });

    data.forEach(function(kv)
    {
        kv.data.forEach(function(d) { d.date = parseDate(d.date); });
    });

    var minX = d3.min(data, function (kv) { return d3.min(kv.data, function (d) { return d.date; }) });
    var maxX = d3.max(data, function (kv) { return d3.max(kv.data, function (d) { return d.date; }) });
    var minY = d3.min(data, function (kv) { return d3.min(kv.data, function (d) { return d.value; }) });
    var maxY = d3.max(data, function (kv) { return d3.max(kv.data, function (d) { return d.value; }) });

    this.x.domain([minX, maxX]);
    this.y.domain([minY, maxY]);

    // x axis
    this.svg.append('g')
            .attr('class', 'x axis')
            .attr('transform', 'translate(0,' + this.graphHeight + ')')
            .call(this.xAxis);

    // y axis
    this.svg.append('g')
            .attr('class', 'y axis')
            .call(this.yAxis);

    var value = this.svg.selectAll('.value')
            .data(data)
            .enter().append('g')
                .attr('class', 'value');

    var path = value.append('path')
            .attr('class', 'line')
            .attr('d', function(d) { return line(d.data); })
            .style('stroke', function(d) { return d.type == 'Income' ? '#006600' : '#CC0000'; });

    value.append('text')
            .datum(function(d)
            {
                return {
                    name: d.type,
                    date: d.data[d.data.length - 1].date,
                    value: d.data[d.data.length - 1].value
                };
            })
            .attr('transform', function(d) { return 'translate(' + self.x(d.date) + ',' + self.y(d.value) + ')'; })
            .attr('x', 3)
            .attr('dy', '.35em')
            .text(function(d) { return d.name; });
    },

1 个答案:

答案 0 :(得分:0)

从理论上讲,只需设置正确的笔划字符串数组字符串即可解决此问题。像dash-array: 200 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4;这样的东西。这些显然只是样本值,但想法是有一个非常长的短划线(在这个例子中为200),从时间开始到第二个月到最后一个月结束,然后有一系列破折号( 4 4 4 ...)在路径的其余部分。但是,确定长划线结束的位置可能会变得棘手。您可以使用SVG路径方法getTotalLength()计算整个路径的长度,然后以某种方式确定沿此长度转换为破折号的位置(即" 200"实际上应该等于)。请记住,这是沿着行的长度,而不是沿着x轴,所以确定找到该点需要考虑到这一点,并且实际上只有在虚线的上个月段是直线时才有效。你可以尝试一下。

或者,d3方式可能是分割路径,而不是2,你有4条路径 - 其中2条路径只是上个月的数据:

    expenses = {
            'type': 'Expense',
            'period': 'past',
            'data': [{ 'date': '201401', 'value': 456 }, {}, ...]
    }

    income = {
            'type': 'Expense',
            'period': 'past',
            'data': [{ 'date': '201401', 'value': 456 }, {}, ...]
    }

    expensesLast = {
            'type': 'Expense',
            'period': 'current',
            'data': [
              expenses.data[expenses.data.length - 2],
              expenses.data.pop()// this ALSO removes the last entry from the array above
            ]
    }

    incomeLast = {
            'type': 'Income'
            'period': 'current',
            'data': [
              income.data[income.data.length - 2],
              income.data.pop()// this ALSO removes the last entry from the array above
            ]
    }

    data = [ expenses, income, expensesLast, incomeLast]

使用新的data,您可以像现在一样绘制线条,然后根据它的周期分配stroke-dasharray

    var path = value.append('path')
            .attr('class', 'line')
            .attr('d', function(d) { return line(d.data); })
            .style('stroke', function(d) {return d.type == 'Income' ? '#006600' : '#CC0000'; });
            .style('stroke-dasharray', function(d) {
              return d.period == 'current' ? '4 4' : ''; // <--- That!
            });