DC-js 中的多系列条形图
Posted
技术标签:
【中文标题】DC-js 中的多系列条形图【英文标题】:Multi-series bar chart in DC-js 【发布时间】:2014-03-24 13:38:38 【问题描述】:我正在使用DC.js(D3 之上的 lib),并且有一个很好的单系列条形图示例:
var xf = crossfilter(data);
var dim = xf.dimension(function (d) return d["EmployeeName"]; );
var group = dim.group().reduceSum(function (d) return d["AverageSale"]; );
var chart = dc.barChart(elm);
chart.barPadding(0.1)
chart.outerPadding(0.05)
chart.brushOn(false)
chart.x(d3.scale.ordinal());
chart.xUnits(dc.units.ordinal);
chart.elasticY(true);
chart.dimension(dim).group(group);
chart.render();
但我想知道是否可以使用此库创建多维条形图。例如:按商店名称分组,然后按员工分组,然后 y 轴显示平均销售额(已由我的后端计算)。
数据如下:
[
"EmployeeName": "Heather",
"StoreName" : "Plaza",
"AverageSaleValue": 200
"EmployeeName": "Mellisa",
"StoreName" : "Plaza",
"AverageSaleValue": 240
,
"EmployeeName": "Sarah",
"StoreName" : "Oak Park",
"AverageSaleValue": 300
... ]
【问题讨论】:
【参考方案1】:在 dc.js 中立即想到的最接近您要求的内容是堆积条形图 (example)。但我认为您可能更喜欢grouped bar chart。我不确定 dc.js 当前是否支持这种图表类型。也许其他人知道。
【讨论】:
分组条形图是我一直在寻找的,但不敢相信 dc 没有这个! 可以通过d3的transform函数实现 @Goutam 请解释一下【参考方案2】:如果您要绘制静态组数,则可以使用复合图表实现所需的效果。
在下面的示例中,我对条形图之间的间隔进行了硬编码 - 我可以这样做,因为我知道要显示 12 个月。
var actuals = dc.barChart(compositeChart)
.gap(65)
.group(group)
.valueAccessor(function (d)
return d.value.Actual;
);
var budgets = dc.barChart(compositeChart)
.gap(65)
.group(group)
.valueAccessor(function (d)
return d.value.Budget;
);
我将这些条形图传递给复合图表的 compose 方法:
compositeChart
.width(1000)
.height(300)
.dimension(monthDimension)
.group(group)
.elasticY(true)
.x(d3.time.scale().domain(timeExtent))
.xUnits(d3.time.months)
.round(d3.time.month.round)
.renderHorizontalGridLines(true)
.compose([budgets, actuals])
.brushOn(true);
最后,我添加了一个渲染器,将其中一个图表向右移动几个像素:
compositeChart
.renderlet(function (chart)
chart.selectAll("g._1").attr("transform", "translate(" + 20 + ", 0)");
chart.selectAll("g._0").attr("transform", "translate(" + 1 + ", 0)");
);
我知道这不是最干净的方法,但它可以在紧要关头工作。
我希望这会有所帮助。
【讨论】:
谢谢,问题是我的图表是完全动态的......我确实有“组”的数量,所以也许我可以根据那个来计算宽度......嗯......【参考方案3】:我可以通过以下链接中的 renderlet 技术来做到这一点 renderlet function for coloring
我的代码如下
.renderlet(function (chart)
chart.selectAll('rect.bar').each(function(d)
d3.select(this).attr("fill",
(function(d)
var colorcode ="grey"
if(d.x[1] === "Zone1")
colorcode ="#ff7373";
else if(d.x[1] === "Zone2")
colorcode ="#b0e0e6";
else if(d.x[1] === "Zone3")
colorcode ="#c0c0c0";
else if(d.x[1] === "Zone4")
colorcode ="#003366";
else if(d.x[1] === "Zone5")
colorcode ="#ffa500";
else if(d.x[1] === "Zone6")
colorcode ="#468499";
else if(d.x[1] === "Zone7")
colorcode ="#660066";
return colorcode;
))
);
);
注意:我为系列使用了一个具有 2 个值的维度。
【讨论】:
【参考方案4】:我知道我迟到了,但这可能对其他人有所帮助。
要在 dc.js 中创建分组条形图而不覆盖原始 dc 代码,您可以利用“pretransition”事件并拆分条形以创建组。
I've created an example (jsfiddle)
奇迹发生在这里:
let scaleSubChartBarWidth = chart =>
let subs = chart.selectAll(".sub");
if (typeof barPadding === 'undefined') // first draw only
// to percentage
barPadding = BAR_PADDING / subs.size() * 100;
// each bar gets half the padding
barPadding = barPadding / 2;
let startAt, endAt,
subScale = d3.scale.linear().domain([0, subs.size()]).range([0, 100]);
subs.each(function (d, i)
startAt = subScale(i + 1) - subScale(1);
endAt = subScale(i + 1);
startAt += barPadding;
endAt -= barPadding;
// polygon(
// top-left-vertical top-left-horizontal,
// top-right-vertical top-right-horizontal,
// bottom-right-vertical bottom-right-horizontal,
// bottom-left-vertical bottom-left-horizontal,
// )
d3.select(this)
.selectAll('rect')
.attr("clip-path", `polygon($startAt% 0, $endAt% 0, $endAt% 100%, $startAt% 100%)`);
);
;
...
.on("pretransition", chart =>
scaleSubChartBarWidth(chart);
)
完整代码:
标记
<div id="chart-container"></div>
Js
//'use strict';
let compositeChart = dc.compositeChart("#chart-container");
const BAR_PADDING = .1; // percentage the padding will take from the bar
const RANGE_BAND_PADDING = .5; // padding between 'groups'
const OUTER_RANGE_BAND_PADDING = 0.5; // padding from each side of the chart
let sizing = chart =>
chart
.width(window.innerWidth)
.height(window.innerHeight)
.redraw();
;
let resizing = chart => window.onresize = () => sizing(chart);
let barPadding;
let scaleSubChartBarWidth = chart =>
let subs = chart.selectAll(".sub");
if (typeof barPadding === 'undefined') // first draw only
// to percentage
barPadding = BAR_PADDING / subs.size() * 100;
// each bar gets half the padding
barPadding = barPadding / 2;
let startAt, endAt,
subScale = d3.scale.linear().domain([0, subs.size()]).range([0, 100]);
subs.each(function (d, i)
startAt = subScale(i + 1) - subScale(1);
endAt = subScale(i + 1);
startAt += barPadding;
endAt -= barPadding;
// polygon(
// top-left-vertical top-left-horizontal,
// top-right-vertical top-right-horizontal,
// bottom-right-vertical bottom-right-horizontal,
// bottom-left-vertical bottom-left-horizontal,
// )
d3.select(this)
.selectAll('rect')
.attr("clip-path", `polygon($startAt% 0, $endAt% 0, $endAt% 100%, $startAt% 100%)`);
);
;
let data = [
key: "First",
value: [
key: 1, value: 0.18,
key: 2, value: 0.28,
key: 3, value: 0.68
]
,
key: "Second",
value: [
key: 1, value: 0.72,
key: 2, value: 0.32,
key: 3, value: 0.82
]
,
key: "Third",
value: [
key: 1, value: 0.3,
key: 2, value: 0.22,
key: 3, value: 0.7
]
,
key: "Fourth",
value: [
key: 1, value: 0.18,
key: 2, value: 0.58,
key: 3, value: 0.48
]
];
let ndx = crossfilter(data),
dimension = ndx.dimension(d => d.key),
group = all: () => data; // for simplicity sake (take a look at crossfilter group().reduce())
let barChart1 = dc.barChart(compositeChart)
.barPadding(0)
.valueAccessor(d => d.value[0].value)
.title(d => d.key + `[$d.value[0].key]: ` + d.value[0].value)
.colors(['#000']);
let barChart2 = dc.barChart(compositeChart)
.barPadding(0)
.valueAccessor(d => d.value[1].value)
.title(d => d.key + `[$d.value[1].key]: ` + d.value[1].value)
.colors(['#57B4F0']);
let barChart3 = dc.barChart(compositeChart)
.barPadding(0)
.valueAccessor(d => d.value[2].value)
.title(d => d.key + `[$d.value[2].key]: ` + d.value[2].value)
.colors(['#47a64a']);
compositeChart
.shareTitle(false)
.dimension(dimension)
.group(group)
._rangeBandPadding(RANGE_BAND_PADDING)
._outerRangeBandPadding(OUTER_RANGE_BAND_PADDING)
.x(d3.scale.ordinal())
.y(d3.scale.linear().domain([0, 1]))
.xUnits(dc.units.ordinal)
.compose([barChart1, barChart2, barChart3])
.on("pretransition", chart =>
scaleSubChartBarWidth(chart)
)
.on("filtered", (chart, filter) =>
console.log(chart, filter);
)
.on("preRedraw", chart =>
chart.rescale();
)
.on("preRender", chart =>
chart.rescale();
)
.render();
sizing(compositeChart);
resizing(compositeChart);
它并不完美,但它可以提供一个起点。
【讨论】:
如果我想创建一个类似于瀑布图的图表,在这种情况下将条形图移动到 y 轴上的特定位置。以上是关于DC-js 中的多系列条形图的主要内容,如果未能解决你的问题,请参考以下文章