I\'m using DC.js ( lib on top of D3 ) and have a great example of a single series bar chart:
I was able to do this with a twist of the renderlet technique in the following link renderlet function for coloring
My code goes as follows
.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;
}))
});
});
Note : I was using a dimension with 2 values for the series .
If you have a static number of groups to graph, you can achieve the desired effect with a composite chart.
In the example below, I hard coded the gap between the bar charts - I can do this because I know there are 12 months being displayed.
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;
});
I pass these bar charts to the compose method of a composite chart:
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);
Finally, I add a renderlet to move one of the charts to the right a few pixels:
compositeChart
.renderlet(function (chart) {
chart.selectAll("g._1").attr("transform", "translate(" + 20 + ", 0)");
chart.selectAll("g._0").attr("transform", "translate(" + 1 + ", 0)");
});
I know this isn't the cleanest approach but it can work in a pinch.
I hope this helps.
The closest thing to what you're asking for that comes to mind immediately in dc.js would be a stacked bar chart (example). But I think what you might prefer is a grouped bar chart. I'm not sure that this chart type is currently supported by dc.js. Maybe someone else knows.
I know I’m late for this but it might help someone else.
To create grouped-bar-chart in dc.js without overwrite the original dc code you can take advantage of ‘pretransition’ event and split the bars to create a group.
I've created an example (jsfiddle)
The magic happens here:
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);
})
Complete code:
markup
<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);
It's not perfect but it could give a starting point.