score:1

Accepted answer

--- diclaimer: much of the code below is pasted from chart.js with very little changes. chart.js code is under mit license, please refer to that license if you plan to use it ---

found how to do it, although it's not probably the best and/or most reusable way.

for the legend, legend itself is a plugin in chart.js source code. for that reason, i simply overwritten the plugin logic as such:

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

this code works just fine, if you need to use the old legend simply implement a logic to reuse the stashed functions.

tooltips are quite harder to handle though, since they are a core functionality with little to no exported api. but you could overwrite the prototype, reusing the code from chart.js:

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

Related Query

More Query from same tag