Reputation: 179
Working on a custom chart that can determine the thickness of a bar in a bar or horizontal bar chart based on a value provided by the data, not a pre-set width as possible with barThickness or barPercentage / categoryPercentage.
Goal: I can create a horizontal bar chart with data that contains an x
value and a thickness
, where the thickness becomes the relative thickness of that bar in relation to the other bars.
For example, this code:
const data = {
labels: ['Northeast', 'Midwest', 'South', 'West'],
datasets: [
{
data: [
{ x: 10, thickness: 18 },
{ x: 20, thickness: 8 },
{ x: 13, thickness: 5 },
{ x: 4, thickness: 12 }
],
},
],
}
const context = document.querySelector('#myChart').getContext('2d')
const barChart = new Chart(context, {
type: 'horizontalBar',
data: data,
})
... would render a chart looking something like this:
... instead of this:
(didn't include irrelevant style options here).
Key features going for:
How can this be done?
I know I can hack the drawing of bar chart controllers to specify explicit widths to the rectangles drawn, but this fails to produce bars that are evenly spaced. Instead they have variable width within their fixed-width category, which is not the look I'm going for:
My hunch is that I need to extend a new axis but this seems complex.
Hoping to get any suggestions on best approaches! Thanks!
Similar to this unanswered question. Encountered these answers in my search which seemed promising but ultimately weren't the right answers:
Upvotes: 5
Views: 2621
Reputation: 179
There are three things that need to be done to support this feature:
#getPixelForValue()
method in the category scale so that the category widths reflect the variant thickness, instead of being uniformImplemented these as a custom chart in this package: https://github.com/swayable/chartjs-chart-superbar using Chart.js's new chart and new axes customization features.
Solved (1) by adding behavior like this to the category scale:
getBarThickness(thicknessPercentage) {
let thickness = thicknessPercentage * this._axisSize(),
spacing = this.options.spacing * 2
return thickness - spacing
}
_axisSize() {
return this.isHorizontal() ? this.width : this.height
}
(2) is a bit more complicated because the pixel for each value in the category scale needs to take into account all of the various thicknesses for each previous item in the dataset when calculating the distance from the edge (left
for horizontal, or top
for vertical). Otherwise the thicknesses of the categories in the axis will not match the thicknesses of the bars, and the category axis labels will not be centered on the bars.
Relevant file for (1) and (2): https://github.com/swayable/chartjs-chart-superbar/blob/master/src/scale.thickCategory.js
(3) is solved by changing the relevant dimension in the element's _view
and _model
properties using the Scale#getBarThickness()
method above.
Relevant file for (3): https://github.com/swayable/chartjs-chart-superbar/blob/master/src/mixin.barThickness.js
Figured most of this out by reading the source code for Chart.js, especially scale.category.js and core.datasetController.js. Also took some inspiration from the source code for Chart.smith.js.
Upvotes: 5