patternomaly
patternomaly copied to clipboard
Barchart: Error unable to parse color from object
Hello,
I'm getting the following error when using patternomaly as the backgroundColor when using a barchart:
Uncaught Error: Unable to parse color from object {"shapeType":"dot"}
at Color (vendor-bundle.js:52739)
at Color (vendor-bundle.js:52699)
at Object.helpers.color (vendor-bundle.js:58367)
at mergeOpacity (vendor-bundle.js:61267)
at vendor-bundle.js:61935
at Object.helpers.each (vendor-bundle.js:57465)
at vendor-bundle.js:61923
at Object.helpers.each (vendor-bundle.js:57465)
at ChartElement.drawBody (vendor-bundle.js:61920)
at ChartElement.draw (vendor-bundle.js:62012)
at Chart.Controller.draw (vendor-bundle.js:56740)
at ChartElement.animation.render (vendor-bundle.js:56699)
at Object.startDigest (vendor-bundle.js:56121)
at vendor-bundle.js:56094
The error comes from the following code in the Chart.js library in /dist/Chart.js around line 280:
else if (typeof obj === 'object') {
vals = obj;
if (vals.r !== undefined || vals.red !== undefined) {
this.setValues('rgb', vals);
} else if (vals.l !== undefined || vals.lightness !== undefined) {
this.setValues('hsl', vals);
} else if (vals.v !== undefined || vals.value !== undefined) {
this.setValues('hsv', vals);
} else if (vals.w !== undefined || vals.whiteness !== undefined) {
this.setValues('hwb', vals);
} else if (vals.c !== undefined || vals.cyan !== undefined) {
this.setValues('cmyk', vals);
} else {
throw new Error('Unable to parse color from object ' + JSON.stringify(obj));
}
}
obj only has the shapeType property. Is this fixable? The error occurs when hovering over the chart.
Here is a workaround I got working by adjusting Chart.js (v2.4.0). These errors get thrown because the backgroundColor is not a standard color, but instead a CanvasPattern. I'm not sure how this would get fixed in this library instead of Chart.js, but regardless here's what I got working for me.
The fix here is to detect when it's trying to use the CanvasPattern incorrectly and adjust it to use either the CanvasPattern that already exists or a solid color if that's what it's looking for.
If you define hoverBackgroundColor
in all of your patterned datasets you can skip this first adjustment. If you don't define it for all of your patterns, then you'll need this first part to set the hovers style to at least a solid color instead of the pattern. Adjust setHoverStyle on line ~6397 to:
setHoverStyle: function(rectangle) {
var dataset = this.chart.data.datasets[rectangle._datasetIndex];
var index = rectangle._index;
var custom = rectangle.custom || {};
var model = rectangle._model;
var bgColor = model.backgroundColor;
if (bgColor instanceof CanvasPattern) {
bgColor = model.borderColor; //use the border color instead of the background color. Again, not necessary if you set the 'hoverBackgroundColor' value, but this is a safe backup
}
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(bgColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
},
Then to adjust the tooltip, set drawBody on line ~13564 to the following (only ~5 lines have been edited and commented below which part is important):
drawBody: function(pt, vm, ctx, opacity) {
var bodyFontSize = vm.bodyFontSize;
var bodySpacing = vm.bodySpacing;
var body = vm.body;
ctx.textAlign = vm._bodyAlign;
ctx.textBaseline = 'top';
var textColor = mergeOpacity(vm.bodyFontColor, opacity);
ctx.fillStyle = textColor;
ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
// Before Body
var xLinePadding = 0;
var fillLineOfText = function(line) {
ctx.fillText(line, pt.x + xLinePadding, pt.y);
pt.y += bodyFontSize + bodySpacing;
};
// Before body lines
helpers.each(vm.beforeBody, fillLineOfText);
var drawColorBoxes = vm.displayColors;
xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
// Draw body lines now
helpers.each(body, function(bodyItem, i) {
helpers.each(bodyItem.before, fillLineOfText);
helpers.each(bodyItem.lines, function(line) {
// Draw Legend-like boxes if needed
if (drawColorBoxes) {
// Fill a white rect so that colours merge nicely if the opacity is < 1
ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
// Border
ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
//this is the important part!!
var bgColor = vm.labelColors[i].backgroundColor;
if (bgColor instanceof CanvasPattern) {
//set the fillStyle to the pattern
ctx.fillStyle = vm.labelColors[i].backgroundColor;
} else {
// Inner square
ctx.fillStyle = mergeOpacity(bgColor, opacity);
}
ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
ctx.fillStyle = textColor;
}
fillLineOfText(line);
});
helpers.each(bodyItem.after, fillLineOfText);
});
// Reset back to 0 for after body
xLinePadding = 0;
// After body lines
helpers.each(vm.afterBody, fillLineOfText);
pt.y -= bodySpacing; // Remove last body spacing
},
This will use the existing CanvasPattern in the tooltip when backgroundColor is actually a pattern, and voila no more errors.
I use this simple workaround (with pattornamaly & chart.js 2.5)
const getPattern = (shape, color)=> {
let rgb = Chart.helpers.colors(color)
let bgPattern = pattern.draw(shape, color)
return Chart.helpers.extend(bgPattern, {r: rgb.red(), g: rgb.green(), b: rgb.blue(), alpha: rgb.alpha()})
}
// in my chart options
// ...code omitted.....
const backgroundColor = getPattern("dot", "red");
let chart = new Chart(ctx, {
data: {
labels: ['Item 1', 'Item 2', 'Item 3'],
datasets: [{
data: [10, 20, 30],
backgroundColor
}]
}
})