Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/plugins/plugin.legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ const getBoxSize = (labelOpts, fontSize) => {

const itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;

function getAlignedHitBox(x, width, boxWidth, fontSize, textAlign, right) {
const textWidth = width - boxWidth - fontSize / 2;
const textX = _textX(textAlign, x + boxWidth + fontSize / 2, right, false);
const textLeft = textX - (textAlign === 'center' ? textWidth / 2 : textAlign === 'right' ? textWidth : 0);
const left = Math.min(x, textLeft);
// Cover both the symbol and the aligned text in LTR coordinates.
return {left, width: Math.max(x + boxWidth, textLeft + textWidth) - left};
}

export class Legend extends Element {

/**
Expand Down Expand Up @@ -232,7 +241,8 @@ export class Legend extends Element {
return;
}
const titleHeight = this._computeTitleHeight();
const {legendHitBoxes: hitboxes, options: {align, labels: {padding}, rtl}} = this;
const {legendHitBoxes: hitboxes, options: {align, labels, rtl}} = this;
const {padding} = labels;
const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
if (this.isHorizontal()) {
let row = 0;
Expand All @@ -247,16 +257,22 @@ export class Legend extends Element {
left += hitbox.width + padding;
}
} else {
const fontSize = toFont(labels.font).size;
const {boxWidth} = getBoxSize(labels, fontSize);
let col = 0;
let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
for (const hitbox of hitboxes) {
for (let i = 0; i < hitboxes.length; i++) {
const hitbox = hitboxes[i];
if (hitbox.col !== col) {
col = hitbox.col;
top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
}
hitbox.top = top;
hitbox.left += this.left + padding;
hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
const x = hitbox.left + this.left + padding;
const textAlign = this.legendItems[i].textAlign || labels.textAlign;
const aligned = getAlignedHitBox(x, hitbox.width, boxWidth, fontSize, textAlign, this.right);
hitbox.width = aligned.width;
hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(aligned.left), hitbox.width);
top += hitbox.height + padding;
}
}
Expand Down
70 changes: 70 additions & 0 deletions test/specs/plugin.legend.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,76 @@ describe('Legend block tests', function() {
expect(hitBox.height).toBe(40);
});

for (const position of ['left', 'right', 'top', 'bottom']) {
for (const textAlign of ['left', 'center', 'right']) {
for (const rtl of [false, true]) {
it(`should hit test the full ${position} legend label with ${textAlign} alignment and rtl=${rtl}`, function() {
const chart = window.acquireChart({
type: 'line',
data: {
datasets: ['Short', 'A much longer dataset label'].map(label => ({label, data: []}))
},
options: {
plugins: {legend: {position, rtl, labels: {textAlign}}}
}
}, {canvas: {width: 600, height: 300}});
const {ctx, legend} = chart;
const labels = [];
const symbols = [];
const rect = ctx.rect.bind(ctx);
spyOn(ctx, 'rect').and.callFake(function(x, y, width, height) {
if (width === legend.options.labels.boxWidth) {
symbols.push({x: x + width / 2, y: y + height / 2});
}
rect(x, y, width, height);
});
const fillText = ctx.fillText.bind(ctx);
spyOn(ctx, 'fillText').and.callFake(function(text, x, y, maxWidth) {
if (text === 'Short') {
const width = ctx.measureText(text).width;
const left = x - (ctx.textAlign === 'center' ? width / 2 : ctx.textAlign === 'right' ? width : 0);
labels.push({left, right: left + width, y});
}
fillText(text, x, y, maxWidth);
});

chart.draw();

expect(labels.length).toBe(1);
const {left, right, y} = labels[0];
expect(symbols.length).toBe(2);
expect(legend._getLegendItemAt(symbols[0].x, symbols[0].y)).toBe(legend.legendItems[0]);
expect(legend._getLegendItemAt(left + 1, y)).toBe(legend.legendItems[0]);
expect(legend._getLegendItemAt(right - 1, y)).toBe(legend.legendItems[0]);
});
}
}
}

it('should use a legend item text alignment before the global label alignment for hit testing', function() {
const chart = window.acquireChart({
type: 'line',
data: {datasets: []},
options: {
plugins: {
legend: {
position: 'right',
labels: {
textAlign: 'left',
generateLabels: () => [
{text: 'Short', textAlign: 'right'},
{text: 'A much longer dataset label'}
]
}
}
}
}
});
const {legend} = chart;
const hitbox = legend.legendHitBoxes[0];
expect(legend._getLegendItemAt(legend.right - 1, hitbox.top + hitbox.height / 2)).toBe(legend.legendItems[0]);
});

it('should pick up the first item when the property is an array', function() {
var chart = window.acquireChart({
type: 'bar',
Expand Down