Chart.js标签颜色在工具提示/图例中的定位

Chart.js标签颜色在工具提示/图例中的定位,chart.js,ng2-charts,Chart.js,Ng2 Charts,设计师们决定制作一个非常标准的doghnut图表,并附带一些非标准的工具提示/图例。 中间的文本不是问题,使用chart.js上下文填充该文本。然而,当涉及到图例和工具提示时,事情会变得一团糟 对于工具提示,我尝试使用标题回调来放置粗体文本,使用标签回调可以创建文本,但这里的关键是标签颜色。事实上,它的形状是一个正方形,它在标题下,我没有发现任何配置将它放在一边和/或使它更大 至于图例,我发现的唯一配置是制作颜色“点样式”或将其放置在其他位置 有什么好办法可以达到预期的效果吗 另外,我实际上使

设计师们决定制作一个非常标准的doghnut图表,并附带一些非标准的工具提示/图例。

中间的文本不是问题,使用chart.js上下文填充该文本。然而,当涉及到图例和工具提示时,事情会变得一团糟

对于工具提示,我尝试使用标题回调来放置粗体文本,使用标签回调可以创建文本,但这里的关键是标签颜色。事实上,它的形状是一个正方形,它在标题下,我没有发现任何配置将它放在一边和/或使它更大

至于图例,我发现的唯一配置是制作颜色“点样式”或将其放置在其他位置

有什么好办法可以达到预期的效果吗

另外,我实际上使用的是ng2图表,我知道它有一些“猴子补丁”文件,可以做一些事情,但是如果不真正了解chart.js的内部结构,我就无法完全理解它的功能和/或如何在不更改依赖源代码的情况下编辑它。Chart.js代码在MIT许可下,如果您计划使用它,请参考该许可---

发现了如何做到这一点,尽管这可能不是最好和/或最可重用的方法

对于图例,图例本身是chart.js源代码中的一个插件。出于这个原因,我只是简单地覆盖了插件逻辑:

const defaults = Chart.defaults;
const Element = Chart.Element;
const helpers = Chart.helpers;
const layouts = Chart.layouts;

const columnLegendPlugin = {
id: 'column-legend',

beforeInit: function (chart) {
    this.stash_draw = chart.legend.draw;
    chart.legend.draw = function () {
        const me = chart.legend;
        const opts = me.options;
        const labelOpts = opts.labels;
        const globalDefaults = defaults.global;
        const defaultColor = globalDefaults.defaultColor;
        const lineDefault = globalDefaults.elements.line;
        const legendHeight = me.height;
        const columnHeights = me.columnHeights;
        const legendWidth = me.width;
        const lineWidths = me.lineWidths;

        if(opts.display) {
            const ctx = me.ctx;
            const fontColor = helpers.valueOrDefault(labelOpts.fontColor, globalDefaults.defaultFontColor);
            const labelFont = helpers.options._parseFont(labelOpts);
            const fontSize = labelFont.size;
            let cursor;

            // Canvas setup
            ctx.textAlign = 'left';
            ctx.textBaseline = 'middle';
            ctx.lineWidth = 0.5;
            ctx.strokeStyle = fontColor; // for strikethrough effect
            ctx.fillStyle = fontColor; // render in correct colour
            ctx.font = labelFont.string;

            const boxWidth = getBoxWidth(labelOpts, fontSize);
            const hitboxes = me.legendHitBoxes;

            // current position
            const drawLegendBox = function (x, y, legendItem) {
                if(isNaN(boxWidth) || boxWidth <= 0) {
                    return;
                }

                // Set the ctx for the box
                ctx.save();

                const lineWidth = helpers.valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
                ctx.fillStyle = helpers.valueOrDefault(legendItem.fillStyle, defaultColor);
                ctx.lineCap = helpers.valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
                ctx.lineDashOffset = helpers.valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
                ctx.lineJoin = helpers.valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
                ctx.lineWidth = lineWidth;
                ctx.strokeStyle = helpers.valueOrDefault(legendItem.strokeStyle, defaultColor);

                if(ctx.setLineDash) {
                    // IE 9 and 10 do not support line dash
                    ctx.setLineDash(helpers.valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
                }

                if(labelOpts && labelOpts.usePointStyle) {
                    // Recalculate x and y for drawPoint() because its expecting
                    // x and y to be center of figure (instead of top left)
                    const radius = boxWidth * Math.SQRT2 / 2;
                    const centerX = x + boxWidth / 2;
                    const centerY = y + fontSize / 2;

                    // Draw pointStyle as legend symbol
                    helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation);
                } else {
                    // Draw box as legend symbol
                    ctx.fillRect(x, y, boxWidth, Math.min(fontSize, labelOpts.boxHeight));
                    if(lineWidth !== 0) {
                        ctx.strokeRect(x, y, boxWidth, Math.min(fontSize, labelOpts.boxHeight));
                    }
                }

                ctx.restore();
            };
            const fillText = function (x, y, legendItem, textWidth) {
                const halfFontSize = fontSize / 2;
                const xLeft = /* boxWidth + halfFontSize + */ x;
                //const yMiddle = y + halfFontSize;

                const yMiddle = y + labelOpts.yShift;

                if(legendItem.text && legendItem.text.length > labelOpts.maxLabelLength) {
                    legendItem.text = (legendItem.text as string).slice(0, labelOpts.maxLabelLength) + '.';
                }

                ctx.fillText(legendItem.text, xLeft, yMiddle);

                if(legendItem.hidden) {
                    // Strikethrough the text if hidden
                    ctx.beginPath();
                    ctx.lineWidth = 2;
                    ctx.moveTo(xLeft, yMiddle);
                    ctx.lineTo(xLeft + textWidth, yMiddle);
                    ctx.stroke();
                }
            };

            const alignmentOffset = function (dimension, blockSize) {
                switch(opts.align) {
                    case 'start':
                        return labelOpts.padding;
                    case 'end':
                        return dimension - blockSize;
                    default: // center
                        return (dimension - blockSize + labelOpts.padding) / 2;
                }
            };

            // Horizontal
            const isHorizontal = me.isHorizontal();
            if(isHorizontal) {
                cursor = {
                    x: me.left + alignmentOffset(legendWidth, lineWidths[0]),
                    y: me.top + labelOpts.padding,
                    line: 0
                };
            } else {
                cursor = {
                    x: me.left + labelOpts.padding,
                    y: me.top + alignmentOffset(legendHeight, columnHeights[0]),
                    line: 0
                };
            }

            const itemHeight = fontSize + labelOpts.padding;
            helpers.each(me.legendItems, function (legendItem, i) {
                const textWidth = Math.min(ctx.measureText(legendItem.text).width, 100);
                const width = boxWidth + (fontSize / 2) + textWidth;
                let x = cursor.x;
                let y = cursor.y;

                // Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
                // instead of me.right and me.bottom because me.width and me.height
                // may have been changed since me.minSize was calculated
                if(isHorizontal) {
                    if(i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
                        y = cursor.y += itemHeight;
                        cursor.line++;
                        x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]);
                    }
                } else if(i > 0 && y + itemHeight > me.top + me.minSize.height) {
                    x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
                    cursor.line++;
                    y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]);
                }

                drawLegendBox(x, y, legendItem);

                hitboxes[i].left = x;
                hitboxes[i].top = y;
                hitboxes[i].height = labelOpts.yShift + labelOpts.boxHeight + labelOpts.fontSize;
                hitboxes[i].width = Math.max(Math.min(ctx.measureText(legendItem.text).width, 100), boxWidth);

                // Fill the actual label
                fillText(x, y, legendItem, textWidth);

                if(isHorizontal) {
                    cursor.x += width + labelOpts.padding;
                } else {
                    cursor.y += itemHeight;
                }
            });
        }
    };
  }
};
const defaults=Chart.defaults;
常量元素=图表元素;
const helpers=Chart.helpers;
const layouts=Chart.layouts;
const columnLegendPlugin={
id:'列图例',
beforeInit:函数(图表){
this.stash_draw=chart.legend.draw;
chart.legend.draw=函数(){
const me=chart.legend;
const opts=me.options;
const labelOpts=opts.labels;
const globalDefaults=defaults.global;
const defaultColor=globalDefaults.defaultColor;
const lineDefault=globalDefaults.elements.line;
常量legendHeight=me.height;
const columnHeights=me.columnHeights;
const legendWidth=me.width;
const lineWidths=me.lineWidths;
如果(选择显示){
常数ctx=me.ctx;
const fontColor=helpers.valueOrDefault(labelOpts.fontColor、globalDefaults.defaultFontColor);
const labelFont=helpers.options.\u parseFont(labelOpts);
const fontSize=labelFont.size;
让光标;
//画布设置
ctx.textAlign='左';
ctx.textb基线='中间';
ctx.lineWidth=0.5;
ctx.strokeStyle=fontColor;//用于删除线效果
ctx.fillStyle=fontColor;//以正确的颜色渲染
ctx.font=labelFont.string;
const-boxWidth=getBoxWidth(labelOpts,fontSize);
const hitboxs=me.legendhitbox;
//当前位置
常量drawLegendBox=函数(x,y,legendItem){
if(isNaN(箱宽)|箱宽标签长度最大值){
legendItem.text=(legendItem.text作为字符串).slice(0,labelOpts.MaxLabellLength)+';
}
ctx.fillText(legendItem.text、xLeft、yMiddle);
如果(legendItem.hidden){
//删除隐藏的文本
ctx.beginPath();
ctx.lineWidth=2;
ctx.移动到(xLeft,yMiddle);
ctx.lineTo(xLeft+文本宽度,yMiddle);
ctx.stroke();
}
};
const alignmentOffset=函数(维度、块大小){
开关(选择对齐){
案例“开始”:
返回labelOpts.padding;
案例“结束”:
返回维度-块大小;
默认值://center
返回(尺寸-块大小+标签填充)/2;
}
};
//水平的
const isHorizontal=me.isHorizontal();
if(水平){
光标={
x:me.left+alignmentOffset(legendWidth,线宽[0]),
y:me.top+labelOpts.padding,
行:0
};
}否则{
光标={
x:me.left+labelOpts.padding,
y:me.top+alignmentOffset(legendHeight,columnHeights[0]),
行:0
};
}
const itemHeight=fontSize+labelOpts.padding;
helpers.each(me.legendItems,function)(legendItem,i){
const textWidth=Math.min(ctx.measureText(legendItem.text).width,100);
const width=boxWidth+(fontSize/2)+textWidth;
设x=cursor.x;
设y=cursor.y;
//使用(me.left+me.minSize.width)和(me.top+me.minSize.height)
//而不是我。右和我。底因为我。宽和我。高
//自计算me.minSize后可能已更改
if(水平){
if(i>0&&x+width+labelOpts.padding>me.left+me.minSize.width){
y=光标。y+=项目高度;
cursor.line++;
x=cursor.x=me.left+alignmentOffset(legendWidth,线宽[cursor.line]);
}
}否则如果(i>0&&y+itemHeight>me.top+me.minSize.height){
x=cursor.x=x+me.columnWidths[cursor.line]+labelOpts.padding;
cursor.line++;
y=cursor.y=me.top+alignmentOffset(legendHeight,columnHeights[cursor.line]);
}
抽屉(x、y、legendItem);
const defaults = Chart.defaults;
const Element = Chart.Element;
const helpers = Chart.helpers;
const layouts = Chart.layouts;

function getAlignedX(vm, align) {
    return align === 'center'
    ? vm.x + vm.width / 2
    : align === 'right'
        ? vm.x + vm.width - vm.xPadding
        : vm.x + vm.xPadding;
}

export const niceTooltipPlugin = {
id: 'nice-tooltip-plugin',

beforeInit: function (chart) {
    Chart.Tooltip.prototype.draw = function () {
        const ctx = this._chart.ctx;
        const vm = this._view;

        if(vm.opacity === 0) {
            return;
        }

        const tooltipSize = {
            width: Math.max(vm.width, ctx.measureText(vm.body[0].lines[0].tooltipLabel).width + 50, ctx.measureText(vm.body[0].lines[0].tooltipData).width + 50),
            height: 1.5 * vm.height
        };
        const pt = {
            x: vm.x,
            y: vm.y
        };

        const opacity = vm.opacity;

        // Truthy/falsey value for empty tooltip
        const hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;

        if(this._options.enabled && hasTooltipContent) {
            ctx.save();
            ctx.globalAlpha = opacity;

            // Draw Background
            this.drawBackground(pt, vm, ctx, tooltipSize);

            // Draw Title, Body, and Footer
            pt.y += vm.yPadding;

            // Titles
            this.drawTitle(pt, vm, ctx);

            // Body
            this.drawBody(pt, vm, ctx);

            // Footer
            this.drawFooter(pt, vm, ctx);

            ctx.restore();
        }
    };

    Chart.Tooltip.prototype.drawBody = function (pt, vm, ctx) {
        const bodyFontSize = vm.bodyFontSize;
        const bodySpacing = vm.bodySpacing;
        const bodyAlign = vm._bodyAlign;
        const body = vm.body;
        const drawColorBoxes = vm.displayColors;
        const labelColors = vm.labelColors;
        let xLinePadding = 0;
        const colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
        let textColor;

        ctx.textAlign = bodyAlign;
        ctx.textBaseline = 'top';
        ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);

        pt.x = getAlignedX(vm, bodyAlign);

        // Before Body
        const fillLineOfText = function (line) {
            ctx.fillText(line, pt.x + xLinePadding, pt.y);
            pt.y += bodyFontSize + bodySpacing;
        };

        // Before body lines
        ctx.fillStyle = vm.bodyFontColor;
        helpers.each(vm.beforeBody, fillLineOfText);

        xLinePadding = drawColorBoxes && bodyAlign !== 'right'
            ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
            : 0;

        // Draw body lines now
        helpers.each(body, function (bodyItem, i) {
            textColor = vm.labelTextColors[i];
            ctx.fillStyle = textColor;
            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 = vm.legendColorBackground;
                    ctx.fillRect(colorX, pt.y, bodyFontSize, bodyFontSize);

                    // Border
                    ctx.lineWidth = 1;
                    ctx.strokeStyle = labelColors[i].borderColor;
                    ctx.strokeRect(colorX, pt.y, bodyFontSize, bodyFontSize);

                    // Inner square
                    ctx.fillStyle = labelColors[i].backgroundColor;
                    ctx.fillRect(colorX + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
                    ctx.fillStyle = textColor; */
                    ctx.fillStyle = labelColors[i].backgroundColor;
                    helpers.canvas.drawPoint(ctx, undefined, 5, pt.x, pt.y + 12, 360);
                    ctx.fillStyle = textColor;
                }

                ctx.font = helpers.fontString(bodyFontSize, "bold", vm._bodyFontFamily);
                fillLineOfText(line.tooltipLabel);
                ctx.font = helpers.fontString(bodyFontSize, "normal", vm._bodyFontFamily);
                ctx.fillStyle = "#b0b0b0";
                fillLineOfText(line.tooltipData);
                ctx.fillStyle = textColor;
            });

            helpers.each(bodyItem.after, fillLineOfText);
        });
    };
 }
};