ashiguruma / patternomaly

Easily generate patterns for use in data graphics
MIT License
524 stars 37 forks source link

Barchart: Error unable to parse color from object #10

Open toonevdb opened 7 years ago

toonevdb commented 7 years ago

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.

swierczek commented 7 years ago

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.

sanbiv commented 6 years ago

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
        }]
    }
})