前言
相信很多人都看过最新版本的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);
网友评论