美文网首页
d3实现时间轴面积图(V6.1.1-d3)

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

作者: 蜀城走马 | 来源:发表于2020-09-21 20:33 被阅读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

    三、扩展和注意事项

    • 1、brush的使用,除了有brush(笔刷正在拖拽滑动的事件)的监听之外,还有个end的监听事件,该监听事件常规来说是由鼠标拖拽结束后触发。但是,某些时候我们有特殊需求,需要默认移动笔刷框选区域,调用brush.move方法,移动笔刷位置。这个时候就得进行代码防错,否则就会导致栈溢出,浏览器崩溃。如下代码所示:
    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的判断,就回陷入回调地狱、内存溢出。

    • 2、brush的框选区域清除
      brush的现成api中,没有提供可以清除框选区域的方法,但是调用move方法可以达到效果。调用笔刷的move方法,传入参数为null,框选区域就会被清空,如下代码所示:
    brush.call(brush.move, null);
    

    相关文章

      网友评论

          本文标题:d3实现时间轴面积图(V6.1.1-d3)

          本文链接:https://www.haomeiwen.com/subject/wtdbyktx.html