I am using Highcharts to create a waffle chart. Here's a good example:
https://datavizproject.com/data-type/percentage-grid/
Highcharts calls them "item charts", and they work pretty well.
However, I can't figure out how to control the spacing/padding between the items.
I've tried using the itemPadding
property, but that only seems to set the padding between the rows, but not the columns.
Here's a working fiddle:
https://jsfiddle.net/stuehler/frt80u2p/4/
As you can see in this screenshot below, the row padding is 1px (itemPadding: 1
- that's what I want), but the column padding is much larger:
Here is the code I'm using:
Highcharts.chart('container', {
chart: {
type: 'item'
},
series: [{
marker: {
symbol: 'square'
},
itemPadding: 1,
rows: 10,
data: [
{
name: 'Male',
y: 43
},
{
name: 'Female',
y: 57
}
]
}]
});
Thanks in advance for your help!
If you are willing to extend/overwrite, here is some food for thought on how to achieve the spacing. Take the examples below as they are. The code is not production ready in terms of maturity or testing.
(function (H) {
const ItemSeries = H.Series.types.item;
H.wrap(ItemSeries.prototype, 'drawPoints', function (proceed) {
const originalPlotWidth = this.chart.plotWidth;
this.chart.plotWidth = this.chart.plotHeight;
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
this.chart.plotWidth = originalPlotWidth;
});
}(Highcharts));
It just sets the plotWidth
such that it is equal to plotHeight
during the call to drawPoints
, giving each cell the same width and height. It ends up showing correctly for some simple demo scenarios, but the chart is shown aligned to the left.
See this JSFiddle demonstration. It should perhaps be using Math.min
for width and height.
(function (H) {
console.log(H);
H.Series.types.item.prototype.drawPoints = function() {
const series = this, options = this.options, renderer = series.chart.renderer, seriesMarkerOptions = options.marker, borderWidth = this.borderWidth, crisp = borderWidth % 2 ? 0.5 : 1, rows = this.getRows(), cols = Math.ceil(this.total / rows);
const cellWidth = Math.min(this.chart.plotWidth / cols, this.chart.plotHeight / rows), cellHeight = Math.min(this.chart.plotWidth / cols, this.chart.plotHeight / rows), itemSize = this.itemSize || Math.min(cellWidth, cellHeight);
const leftPadding = Math.max(0, (this.chart.plotWidth - (cellWidth * cols)) / 2);
let i = 0;
/* @todo: remove if not needed
this.slots.forEach(slot => {
this.chart.renderer.circle(slot.x, slot.y, 6)
.attr({
fill: 'silver'
})
.add(this.group);
});
//*/
for (const point of series.points) {
const pointMarkerOptions = point.marker || {}, symbol = (pointMarkerOptions.symbol ||
seriesMarkerOptions.symbol), r = H.pick(pointMarkerOptions.radius, seriesMarkerOptions.radius), size = H.defined(r) ? 2 * r : itemSize, padding = size * options.itemPadding;
let attr, graphics, pointAttr, x, y, width, height;
point.graphics = graphics = point.graphics || [];
if (!series.chart.styledMode) {
pointAttr = series.pointAttribs(point, point.selected && 'select');
}
if (!point.isNull && point.visible) {
if (!point.graphic) {
point.graphic = renderer.g('point')
.add(series.group);
}
for (let val = 0; val < (point.y || 0); ++val) {
// Semi-circle
if (series.center && series.slots) {
// Fill up the slots from left to right
const slot = series.slots.shift();
x = slot.x - itemSize / 2;
y = slot.y - itemSize / 2;
}
else if (options.layout === 'horizontal') {
x = cellWidth * (i % cols);
y = cellHeight * Math.floor(i / cols);
}
else {
x = leftPadding + (cellWidth * Math.floor(i / rows));
y = cellHeight * (i % rows);
}
x += padding;
y += padding;
width = Math.round(size - 2 * padding);
height = width;
if (series.options.crisp) {
x = Math.round(x) - crisp;
y = Math.round(y) + crisp;
}
attr = {
x: x,
y: y,
width: width,
height: height
};
if (typeof r !== 'undefined') {
attr.r = r;
}
// Circles attributes update (#17257)
if (pointAttr) {
H.extend(attr, pointAttr);
}
let graphic = graphics[val];
if (graphic) {
graphic.animate(attr);
}
else {
graphic = renderer
.symbol(symbol, void 0, void 0, void 0, void 0, {
backgroundSize: 'within'
})
.attr(attr)
.add(point.graphic);
}
graphic.isActive = true;
graphics[val] = graphic;
++i;
}
}
for (let j = 0; j < graphics.length; j++) {
const graphic = graphics[j];
if (!graphic) {
return;
}
if (!graphic.isActive) {
graphic.destroy();
graphics.splice(j, 1);
j--; // Need to substract 1 after splice, #19053
}
else {
graphic.isActive = false;
}
}
}
};
}(Highcharts));
This is essentially a full overwrite of the drawPoints
code, using the original as a source. It defines the cellWidth
and cellHeight
variables differently from the original, making the cells square. It also defines a leftPadding
variable to be able to center the chart in the plot.