Skip to content

Commit

Permalink
Add overflow option to labels configuration (#106)
Browse files Browse the repository at this point in the history
* Add overflow option to labels configuration

* adds test cases

* adds types definition

* adds docs and samples

* improves check if overflow is hidden

* apply review
  • Loading branch information
stockiNail authored Sep 26, 2022
1 parent 2213041 commit 6246d19
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 14 deletions.
16 changes: 16 additions & 0 deletions docs/samples/captions.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,30 @@ const config = {
weight: 'bold'
},
padding: 5
},
labels: {
display: false,
overflow: 'hidden'
}
}]
},
options: options
};
// </block:config>

const actions = [
{
name: 'Toggle labels',
handler(chart) {
const labels = chart.data.datasets[0].labels;
labels.display = !labels.display;
chart.update();
}
}
];

module.exports = {
config,
actions
};
```
12 changes: 10 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,10 @@ The labels options can control if and how a label, to represent the data, can be
| `color` | [`Color`](https://www.chartjs.org/docs/latest/general/colors.html) | Yes | `undefined`
| `display` | `boolean` | - | `false`
| [`formatter`](#formatter) | `function` | Yes |
| [`font`](https://www.chartjs.org/docs/latest/general/fonts.html) | `Font` | Yes | `{}`
| [`font`](https://www.chartjs.org/docs/latest/general/fonts.html) | `Font` | Yes | `{}`
| `hoverColor` | [`Color`](https://www.chartjs.org/docs/latest/general/colors.html) | Yes | `undefined`
| [`hoverFont`](https://www.chartjs.org/docs/latest/general/fonts.html) | `Font` | Yes | `{}`
| [`hoverFont`](https://www.chartjs.org/docs/latest/general/fonts.html) | `Font` | Yes | `{}`
| [`overflow`](#overflow) | `string` | Yes | `cut`
| `padding` | `number` | - | `3`
| [`position`](#position) | `string` | Yes | `middle`

Expand All @@ -198,6 +199,13 @@ The align property specifies the text horizontal alignment used when drawing the
* `left`: the text is left-aligned.
* `right`: the text is right-aligned.

### Overflow

The overflow property controls what happens to a label that is too big to fit into a rectangle. The possible values are:

* `cut`: if the label is too big, it will be cut to stay inside the rectangle. It is the default.
* `hidden`: the label is removed altogether if the rectangle is too small for it.

### Position

The position property specifies the text vertical alignment used when drawing the label. The possible values are:
Expand Down
59 changes: 47 additions & 12 deletions src/element.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {Element} from 'chart.js';
import {toFont, isArray, isObject} from 'chart.js/helpers';

const widthCache = new Map();

/**
* Helper function to get the bounds of the rect
* @param {TreemapElement} rect the rect
Expand Down Expand Up @@ -114,24 +116,56 @@ function drawCaption(ctx, rect, item) {
ctx.fillText(captionsOpts.formatter || item.g, x, rect.y + padding + spacing + (font.lineHeight / 2));
}

function measureLabelSize(ctx, lines, font) {
const mapKey = lines.join() + font.string + (ctx._measureText ? '-spriting' : '');
if (!widthCache.has(mapKey)) {
ctx.save();
ctx.font = font.string;
const count = lines.length;
let width = 0;
for (let i = 0; i < count; i++) {
const text = lines[i];
width = Math.max(width, ctx.measureText(text).width);
}
ctx.restore();
const height = count * font.lineHeight;
widthCache.set(mapKey, {width, height});
}
return widthCache.get(mapKey);
}

function labelToDraw(ctx, rect, options, {labels, font}) {
const overflow = options.overflow;
if (overflow === 'hidden') {
const padding = options.padding;
const labelSize = measureLabelSize(ctx, labels, font);
return !((labelSize.width + padding * 2) > rect.width || (labelSize.height + padding * 2) > rect.height);
}
return true;
}

function drawLabel(ctx, rect) {
const opts = rect.options;
const labelsOpts = opts.labels;
const optColor = (rect.active ? labelsOpts.hoverColor : labelsOpts.color) || labelsOpts.color;
const label = labelsOpts.formatter;
if (!label) {
return;
}
const labels = isArray(label) ? label : [label];
const optFont = (rect.active ? labelsOpts.hoverFont : labelsOpts.font) || labelsOpts.font;
const font = toFont(optFont);
const lh = font.lineHeight;
const borderWidth = parseBorderWidth(opts.borderWidth, rect.width / 2, rect.height / 2);
const label = labelsOpts.formatter;
if (label) {
const labels = isArray(label) ? label : [label];
const xyPoint = calculateXYLabel(opts, rect, labels, lh, borderWidth);
ctx.font = font.string;
ctx.textAlign = labelsOpts.align;
ctx.textBaseline = labelsOpts.position;
ctx.fillStyle = optColor;
labels.forEach((l, i) => ctx.fillText(l, xyPoint.x, xyPoint.y + i * lh));
if (!labelToDraw(ctx, rect, labelsOpts, {labels, font})) {
return;
}
const borderWidth = parseBorderWidth(opts.borderWidth, rect.width / 2, rect.height / 2);
const optColor = (rect.active ? labelsOpts.hoverColor : labelsOpts.color) || labelsOpts.color;
const lh = font.lineHeight;
const xyPoint = calculateXYLabel(opts, rect, labels, lh, borderWidth);
ctx.font = font.string;
ctx.textAlign = labelsOpts.align;
ctx.textBaseline = labelsOpts.position;
ctx.fillStyle = optColor;
labels.forEach((l, i) => ctx.fillText(l, xyPoint.x, xyPoint.y + i * lh));
}

function drawDivider(ctx, rect, item) {
Expand Down Expand Up @@ -286,6 +320,7 @@ TreemapElement.defaults = {
display: false,
formatter: (ctx) => ctx.raw.g ? [ctx.raw.g, ctx.raw.v] : (ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v] : ctx.raw.v),
font: {},
overflow: 'clip',
position: 'middle',
padding: 3
}
Expand Down
32 changes: 32 additions & 0 deletions test/fixtures/basic/labelsMultilineOverflowCut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export default {
tolerance: 0.0120,
config: {
type: 'treemap',
data: {
datasets: [{
label: 'Simple treemap',
data: [6, 6, 4, 3, 2, 2, 1],
backgroundColor: 'red',
labels: {
display: true,
overflow: 'cut',
formatter: (ctx) => ('value is ' + ctx.raw.v + ',').repeat(8).split(',')
}
}]
},
options: {
layout: {
padding: {
bottom: 10
}
}
}
},
options: {
spriteText: true,
canvas: {
height: 256,
width: 512
}
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions test/fixtures/basic/labelsMultilineOverflowHidden.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export default {
tolerance: 0.0025,
config: {
type: 'treemap',
data: {
datasets: [{
label: 'Simple treemap',
data: [6, 6, 4, 3, 2, 2, 1],
backgroundColor: 'red',
labels: {
display: true,
overflow: 'hidden',
formatter: (ctx) => ('value is ' + ctx.raw.v + ',').repeat(6).split(',')
}
}]
},
options: {
layout: {
padding: {
bottom: 10
}
}
}
},
options: {
spriteText: true,
canvas: {
height: 256,
width: 512
}
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions test/fixtures/basic/labelsOverflowCut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export default {
tolerance: 0.0060,
config: {
type: 'treemap',
data: {
datasets: [{
label: 'Simple treemap',
data: [6, 6, 4, 3, 2, 2, 1],
backgroundColor: 'red',
labels: {
display: true,
overflow: 'cut',
formatter: (ctx) => ('value is ' + ctx.raw.v).repeat(5)
}
}]
},
options: {
layout: {
padding: {
bottom: 10
}
}
}
},
options: {
spriteText: true,
canvas: {
height: 256,
width: 512
}
}
};
Binary file added test/fixtures/basic/labelsOverflowCut.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions test/fixtures/basic/labelsOverflowHidden.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export default {
config: {
type: 'treemap',
data: {
datasets: [{
label: 'Simple treemap',
data: [6, 6, 4, 3, 2, 2, 1],
backgroundColor: 'red',
labels: {
display: true,
overflow: 'hidden',
formatter: (ctx) => ('value is ' + ctx.raw.v).repeat(5)
}
}]
},
options: {
layout: {
padding: {
bottom: 10
}
}
}
},
options: {
spriteText: true,
canvas: {
height: 256,
width: 512
}
}
};
Binary file added test/fixtures/basic/labelsOverflowHidden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions types/index.esm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type TreemapControllerDatasetLabelsOptions = {
font?: FontSpec,
hoverColor?: Scriptable<Color, TreemapScriptableContext>,
hoverFont?: FontSpec,
overflow?: Scriptable<LabelOverflow, TreemapScriptableContext>
padding?: number,
position?: Scriptable<LabelPosition, TreemapScriptableContext>
}
Expand All @@ -38,6 +39,8 @@ export type LabelPosition = 'top' | 'middle' | 'bottom';

export type LabelAlign = 'left' | 'center' | 'right';

export type LabelOverflow = 'cut' | 'hidden';

type TreemapControllerDatasetDividersOptions = {
display?: boolean,
lineCapStyle?: string,
Expand Down

0 comments on commit 6246d19

Please sign in to comment.