如何在dc.js的SeriesChart中添加水平基线?
dc.js SeriesChart 添加水平基线问题修复
问题背景
已通过代码实现dc.js的SeriesChart,图表运行正常。需在y轴0.5数值位置添加黑色水平基线,不想修改数据源。转换垂直基线方案后出现两个问题:
- 线条起始位置未对齐y轴
- 线条绘制在0.51而非0.50位置
基于相关方案封装了工具函数,可在pretransition事件中调用。
原代码示例
'use strict'; var sectorChart = new dc.SeriesChart('#test-chart'); d3.json('php/query.php?option=sectors').then(data => { const dateFormatSpecifier = '%Y-%m-%d'; const dateFormat = d3.timeFormat(dateFormatSpecifier); const dateFormatParser = d3.timeParse(dateFormatSpecifier); const numberFormat = d3.format('.2f'); var minDate = new Date(); var maxDate = new Date(); var minRatio = .5 var maxRatio = .5 data.forEach(d => { d.dd = dateFormatParser(d.date); d.ratio = +d.ratio; // 转换为数字 if (d.dd < minDate ) minDate = d.dd; if (d.dd > maxDate ) maxDate = d.dd; if (d.ratio < minRatio ) minRatio = d.ratio if (d.ratio > maxRatio ) maxRatio = d.ratio }); const ndx = crossfilter(data); const all = ndx.groupAll(); // 维度是行业和日期的数组,后续用日期作为x轴,行业作为标签 const dateDimension = ndx.dimension(d => [d.name, d.dd]); // 按平均值分组,作为绘图值 var avgGroup = dateDimension.group().reduceSum(d => d.average); sectorChart .width(990) .height(500) .chart(c => new dc.LineChart(c)) .x(d3.scaleTime().domain([minDate, maxDate])) .y(d3.scaleLinear().domain([minRatio, maxRatio])) .margins({top: 30, right: 10, bottom: 20, left: 40}) .brushOn(false) .yAxisLabel("ratio avg 10") .renderHorizontalGridLines(true) .dimension(dateDimension) .group(avgGroup) .seriesAccessor(d => d.key[0]) .keyAccessor(d => d.key[1]) .valueAccessor(d => +d.value) .legend(dc.legend().x(450).y(15).itemHeight(13).gap(5).horizontal(1).legendWidth(340).autoItemWidth(true)); dc.renderAll(); });
原工具函数
/** * 在dc.js图表中绘制固定的水平或垂直线 * 需要在"pretransition"事件中调用 * @example * .on('pretransition', function (chart) { * dcUtilFixedLine(chart, true, 0.5, [{attr: 'stroke', value: 'red'}] ) * } * @param (Chart} [chart] 事件触发时传入的图表实例 * @param (Boolean) [horizontal] true表示水平线,false表示垂直线 * @param (Object) [value] 线条所在位置的数值,垂直线对应维度值,水平线对应度量值 * @param (Array) [attributes] 包含attr/value键值对的对象数组,用于设置线条属性,默认黑色、1px宽度 */ function dcUtilFixedLine(chart, horizontal, value, attributes ) { if (horizontal) { var extra_data = [ {y: chart.y()(value) + chart.margins().top, x: chart.margins().left}, {y: chart.y()(value) + chart.margins().top, x: chart.margins().left + chart.effectiveWidth()} ] } else { var extra_data = [ {x: chart.x()(value) + chart.margins().left, y: chart.margins().top}, {x: chart.x()(value) + chart.margins().left, y: chart.margins().top + chart.effectiveHeight()} ] } var addedPath = 'extraLine' + Math.random().toString().substr(2, 8) var line = d3.line().x(d => d.x ).y(d => d.y ); var chartBody = chart.select('g'); var path = chartBody.selectAll('path.'+addedPath).data([extra_data]); path = path.enter() .append('path') .attr('class', addedPath) .attr('id', 'oeLine') .attr('stroke', 'black') .merge(path); attributes.forEach( attribute => path.attr(attribute.attr, attribute.value)) path.attr('d', line); }
问题修复及修正后的工具函数
问题原因分析
- 线条未对齐y轴:原代码错误地给坐标添加了margin偏移,而SeriesChart的绘图区域
<g.chart-body>已经包含了margin的位移,重复添加导致偏移。 - 线条位置偏移:使用
chart.y()获取的是顶层配置的比例尺,而非子图表实际使用的y轴比例尺,导致计算出的位置不准确。
修正后的工具函数
/** * 在dc.js图表中绘制固定的水平或垂直线 * 需要在"pretransition"事件中调用 * @example * .on('pretransition', function (chart) { * dcUtilFixedLine(chart, true, 0.5, [{attr: 'stroke', value: 'red'}]) * }) * @param {Chart} chart 事件触发时传入的图表实例 * @param {Boolean} horizontal true表示水平线,false表示垂直线 * @param {Object} value 线条所在位置的数值,垂直线对应维度值,水平线对应度量值 * @param {Array} attributes 可选,包含attr/value键值对的对象数组,用于设置线条属性,默认黑色、1px宽度 */ function dcUtilFixedLine(chart, horizontal, value, attributes) { // 获取实际使用的轴比例尺 var scale = horizontal ? chart.yAxis().scale() : chart.xAxis().scale(); var extra_data; if (horizontal) { var yPos = scale(value); // 绘图区域内坐标从0开始,无需添加margin偏移 extra_data = [ {y: yPos, x: 0}, {y: yPos, x: chart.effectiveWidth()} ]; } else { var xPos = scale(value); extra_data = [ {x: xPos, y: 0}, {x: xPos, y: chart.effectiveHeight()} ]; } var addedPath = 'extraLine' + Math.random().toString().substr(2, 8); var line = d3.line().x(d => d.x).y(d => d.y); // 选择实际绘图区域的g元素 var chartBody = chart.select('g.chart-body'); var path = chartBody.selectAll('path.' + addedPath).data([extra_data]); path = path.enter() .append('path') .attr('class', addedPath) .attr('id', 'oeLine') .attr('stroke', 'black') .attr('stroke-width', 1) .merge(path); // 处理自定义属性 if (attributes && Array.isArray(attributes)) { attributes.forEach(attribute => path.attr(attribute.attr, attribute.value)); } path.attr('d', line); }
使用方式
在SeriesChart的配置中添加pretransition事件监听:
sectorChart // ... 其他原有配置 .on('pretransition', function(chart) { // 绘制y=0.5的黑色水平基线 dcUtilFixedLine(chart, true, 0.5, [{attr: 'stroke', value: 'black'}, {attr: 'stroke-width', value: 1}]); });
内容的提问来源于stack exchange,提问作者Julen
相关产品推荐
相关产品推荐

