d3实现时间轴面积图(V6.1.1-d3)

2020-09-21  本文已影响0人  蜀城走马

前言

相信很多人都看过最新版本的d3文档,里面有个示例叫focus+context,但是示例代码和常规的js代码有点不太一样,所以不能直接使用,不熟悉d3的人第一次使用很难尽快熟悉。我根据focus+context,实现了定制化的时间轴图表,做了部分扩展。我将在下面的内容中贴出示例代码,并进行一些关键点的解释。

一、源码

首先,贴出简单示例源码:

var data = data.map(item => {
            item.date = new Date(item.date).getTime();
            return item;
        })

        var margin = { top: 20, right: 40, bottom: 30, left: 40 };

        var height = 440;
        var width = 1000;

        var focusHeight = 50;


        var x = d3.scaleUtc()
            .domain(d3.extent(data, d => d.date))
            .range([margin.left, width - margin.right])

        var y = d3.scaleLinear()
            .domain([0, d3.max(data, d => d.value)])
            .range([height - margin.bottom, margin.top])


        var xAxis = (g, x, height) => g
            .attr("transform", `translate(0,${height - margin.bottom})`)
            .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0));

        var yAxis = (g, y) => g
            .attr("transform", `translate(${margin.left},0)`)
            .call(d3.axisLeft(y).tickSizeOuter(0));

        var area = (x, y) => d3.area()
            .defined(d => !isNaN(d.value))
            .x(d => x(d.date))
            .y0(y(0))
            .y1(d => y(d.value));

        function brushed({ selection }) {
            console.log(selection.map(x.invert, x).map(d3.utcDay.round))
        }

        const svg = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height + focusHeight);

        const brush = d3.brushX()
            .extent([[margin.left, 0.5], [width - margin.right, height - margin.bottom]])
            .on("brush", brushed);

        const context = svg.append("g")
            .attr("class", "context");

        const focus = svg.append("g")
            .attr("class", "focus")
            .attr("transform", "translate(0," + height + ")");

        const gPath = context.append("path")
            .datum(data)
            .attr("fill", "rgba(35,110,187,0.3)").attr("d", area(x, y.copy().range([height - margin.bottom, 4])));


        const gx = context.append("g")
            .call(xAxis, x, height);

        const gy = context.append("g")
            .call(yAxis, y);


        context.append("g")
            .attr("class", "context-brush")
            .call(brush);

        const focusBrush = d3.brushX()
            .extent([[margin.left, 0.5], [width - margin.right, focusHeight - margin.bottom + 0.5]])
            .on("brush", focusBrushed)
            .on("end", focusBrushended);

        const defaultSelection = [x(d3.utcMonth.offset(x.domain()[1], -1)), x.range()[1]];

        const gb = focus.append("g")
            .call(focusBrush)
            .call(focusBrush.move, defaultSelection);

        function focusBrushed({ selection }) {
            if (selection) {
                const [minX, maxX] = selection.map(x.invert, x).map(d3.utcDay.round);
                const maxY = d3.max(data, d => minX <= d.date && d.date <= maxX ? d.value : NaN);
                const [focusX, focusY] = [x.copy().domain([minX, maxX]), y.copy().domain([0, maxY])];
                gx.call(xAxis, focusX, height);
                gy.call(yAxis, focusY);
                gPath.attr("d", area(focusX, focusY));
            }
        }

        function focusBrushended({ selection }) {
            if (!selection) {
                gb.call(brush.move, defaultSelection);
            }
        }

示例数据:

data = [
{ "date": "2007-04-23T00:00:00.000Z", "value": 93.24 },
 { "date": "2007-04-24T00:00:00.000Z", "value": 95.35 },
 { "date": "2007-04-25T00:00:00.000Z", "value": 98.84 },
 { "date": "2007-04-26T00:00:00.000Z", "value": 99.92 },
 { "date": "2007-04-29T00:00:00.000Z", "value": 99.8 }, 
{ "date": "2007-05-01T00:00:00.000Z", "value": 99.47 },
 { "date": "2007-05-02T00:00:00.000Z", "value": 100.39 }, 
{ "date": "2007-05-03T00:00:00.000Z", "value": 100.4 },
 { "date": "2007-05-04T00:00:00.000Z", "value": 100.81 },
 { "date": "2007-05-07T00:00:00.000Z", "value": 103.92 }, 
{ "date": "2007-05-08T00:00:00.000Z", "value": 105.06 }, 
{ "date": "2007-05-09T00:00:00.000Z", "value": 106.88 }, 
{ "date": "2007-05-09T00:00:00.000Z", "value": 107.34 }, 
{ "date": "2007-05-10T00:00:00.000Z", "value": 108.74 }, 
{ "date": "2007-05-13T00:00:00.000Z", "value": 109.36 }, 
{ "date": "2007-05-14T00:00:00.000Z", "value": 107.52 }, 
{ "date": "2007-05-15T00:00:00.000Z", "value": 107.34 }, 
{ "date": "2007-05-16T00:00:00.000Z", "value": 109.44 }, 
{ "date": "2007-05-17T00:00:00.000Z", "value": 110.02 },
 { "date": "2007-05-20T00:00:00.000Z", "value": 111.98 },
 { "date": "2007-05-21T00:00:00.000Z", "value": 113.54 }, 
{ "date": "2007-05-22T00:00:00.000Z", "value": 112.89 }, 
{ "date": "2007-05-23T00:00:00.000Z", "value": 110.69 }, 
{ "date": "2007-05-24T00:00:00.000Z", "value": 113.62 }, 
{ "date": "2007-05-28T00:00:00.000Z", "value": 114.35 },
 { "date": "2007-05-29T00:00:00.000Z", "value": 118.77 }, 
{ "date": "2007-05-30T00:00:00.000Z", "value": 121.19 }, 
{ "date": "2007-06-01T00:00:00.000Z", "value": 118.4 }, 
{ "date": "2007-06-04T00:00:00.000Z", "value": 121.33 },
 { "date": "2007-06-05T00:00:00.000Z", "value": 122.67 }, 
{ "date": "2007-06-06T00:00:00.000Z", "value": 123.64 }, 
{ "date": "2007-06-07T00:00:00.000Z", "value": 124.07 },
 { "date": "2007-06-08T00:00:00.000Z", "value": 124.49 },
 { "date": "2007-06-10T00:00:00.000Z", "value": 120.19 },
 { "date": "2007-06-11T00:00:00.000Z", "value": 120.38 }, 
{ "date": "2007-06-12T00:00:00.000Z", "value": 117.5 }, 
{ "date": "2007-06-13T00:00:00.000Z", "value": 118.75 }, 
{ "date": "2007-06-14T00:00:00.000Z", "value": 120.5 },
 { "date": "2007-06-17T00:00:00.000Z", "value": 125.09 },
 { "date": "2007-06-18T00:00:00.000Z", "value": 123.66 },
 { "date": "2007-06-19T00:00:00.000Z", "value": 121.55 },
 { "date": "2007-06-20T00:00:00.000Z", "value": 123.9 }, 
{ "date": "2007-06-21T00:00:00.000Z", "value": 123 },
 { "date": "2007-06-24T00:00:00.000Z", "value": 122.34 },
 { "date": "2007-06-25T00:00:00.000Z", "value": 119.65 }, 
{ "date": "2007-06-26T00:00:00.000Z", "value": 121.89 }, 
{ "date": "2007-06-27T00:00:00.000Z", "value": 120.56 },
 { "date": "2007-06-28T00:00:00.000Z", "value": 122.04 },
 { "date": "2007-07-02T00:00:00.000Z", "value": 121.26 },
 { "date": "2007-07-03T00:00:00.000Z", "value": 127.17 }, 
{ "date": "2007-07-05T00:00:00.000Z", "value": 132.75 },
 { "date": "2007-07-06T00:00:00.000Z", "value": 132.3 }, 
{ "date": "2007-07-09T00:00:00.000Z", "value": 130.33 }, 
{ "date": "2007-07-09T00:00:00.000Z", "value": 132.35 },
 { "date": "2007-07-10T00:00:00.000Z", "value": 132.39 },
 { "date": "2007-07-11T00:00:00.000Z", "value": 134.07 },
 { "date": "2007-07-12T00:00:00.000Z", "value": 137.73 },
 { "date": "2007-07-15T00:00:00.000Z", "value": 138.1 },
 { "date": "2007-07-16T00:00:00.000Z", "value": 138.91 },
 { "date": "2007-07-17T00:00:00.000Z", "value": 138.12 },
 { "date": "2007-07-18T00:00:00.000Z", "value": 140 },
 { "date": "2007-07-19T00:00:00.000Z", "value": 143.75 },
 { "date": "2007-07-22T00:00:00.000Z", "value": 143.7 },
 { "date": "2007-07-23T00:00:00.000Z", "value": 134.89 },
 { "date": "2007-07-24T00:00:00.000Z", "value": 137.26 },
 { "date": "2007-07-25T00:00:00.000Z", "value": 146 }, 
{ "date": "2007-07-26T00:00:00.000Z", "value": 143.85 }, 
{ "date": "2007-07-29T00:00:00.000Z", "value": 141.43 }, 
{ "date": "2007-07-30T00:00:00.000Z", "value": 131.76 }
];

备注:
1、首先,模拟数据是时间字符串,但是d3处理的时间格式是时间戳格式,所以在使用自己的真实数据时,如果是时间字符串,记得自己格式化处理成时间戳格式。
2、代码中的变量context,是上放包含面积图和x轴、y轴、内容笔刷的svg组合,focus是下方包含滚动笔刷的内容svg组合。模拟出类似滚动条查看时间轴的效果。
3、面积图部分也可以用笔刷进行时间框选,然后在实际使用中可能结合时间框选有一些其他相关的业务。在brushed回调函数中,我打印了转换后的,框选时间的范围,可以结合业务使用。

二、效果图如下:

image.png

三、扩展和注意事项

const brush = d3.brushX()
            .extent([[margin.left, 0.5], [width - margin.right, height - margin.bottom]])
            .on("brush", brushed)
            .on('end', function(e){
                // 加个判断,判定是鼠标拖拽事件的结束进行回调处理。
                // 否则如果是调用brush.move方法,会一直触发该回调函数。可以想象类似,x偏移1px,就会触发一次回调
                if(e.sourceEvent){
                    // do something...
                }
            });

踩坑场景(我描述的定制化图表,会在后面文章中进行案例和代码分享):
我碰到该问题,是由于在一个可视化界面,我有竖向排列的多个同x轴不同y轴数据的面积图,需要在其中一个面积图框选时间范围结束后,其他几个面积图同时框选相同的时间范围框展示效果。所以,我是在每个图的笔刷的end事件监听中,进行move调用达到业务需求,不加e.sourceEvent的判断,就回陷入回调地狱、内存溢出。

brush.call(brush.move, null);
上一篇下一篇

猜你喜欢

热点阅读