Skip to content

Commit

Permalink
Enable callout in the label of line annotation (chartjs#740)
Browse files Browse the repository at this point in the history
* Enable callout in the label of line annotation

* adds test cases

* adds test case with oblique labels and auto rotation

* solves CC issue of duplicate code

* Add element diagrams to the annotation types guide

* revert wrong merging
  • Loading branch information
stockiNail authored Sep 28, 2022
1 parent 236111e commit 1cfbfc6
Show file tree
Hide file tree
Showing 12 changed files with 560 additions and 23 deletions.
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ module.exports = {
'line/limited',
'line/average',
'line/standardDeviation',
'line/callout',
'line/visibility',
'line/labelVisibility',
'line/canvas',
Expand Down
23 changes: 23 additions & 0 deletions docs/guide/types/line.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ All of these options can be [Scriptable](../options#scriptable-options)
| [`borderRadius`](#borderradius) | `number` \| `object` | `6` | Radius of label box corners in pixels.
| `borderShadowColor` | [`Color`](../options#color) | `'transparent'` | The color of border shadow of the box where the label is located. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor).
| `borderWidth` | `number` | `0` | The border line width (in pixels).
| [`callout`](#callout) | `object` | | Can connect the label to the line. See [callout](#callout).
| `color` | [`Color`](../options#color) | `'#fff'` | Text color.
| `content` | `string`\|`string[]`\|[`Image`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image)\|[`HTMLCanvasElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) | `null` | The content to show in the label.
| `display` | `boolean` | `false` | Whether or not the label is shown.
Expand All @@ -151,6 +152,28 @@ All of these options can be [Scriptable](../options#scriptable-options)

If this value is a number, it is applied to all corners of the rectangle (topLeft, topRight, bottomLeft, bottomRight). If this value is an object, the `topLeft` property defines the top-left corners border radius. Similarly, the `topRight`, `bottomLeft`, and `bottomRight` properties can also be specified. Omitted corners have radius of 0.

### Callout

A callout can connect the label to the line when the label is arbitrarily (by `xAdjust` and `yAdjust` options) moved from its original position.

Namespace: `options.annotations[annotationID].label.callout`, it defines options for the callout on the label of the line annotation.

All of these options can be [Scriptable](../options#scriptable-options).

| Name | Type | Default | Notes
| ---- | ---- | :----: | ----
| `borderCapStyle` | `string` | `'butt'` | Cap style of the border line of callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap).
| `borderColor` | [`Color`](../options#color) | `undefined` | Stroke color of the pointer of the callout.
| `borderDash` | `number[]` | `[]` | Length and spacing of dashes of callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).
| `borderDashOffset` | `number` | `0` | Offset for line dashes of callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).
| `borderJoinStyle` | `string` | `'miter'` | Border line join style of the callout. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).
| `borderWidth` | `number` | `1` | Stroke width of the pointer of the callout.
| `display` | `boolean` | `false` | If true, the callout is drawn.
| `margin` | `number` | `5` | Amount of pixels between the label and the callout separator.
| `position` | `string` | `'auto'` | The position of callout, with respect to the label. Could be `left`, `top`, `right`, `bottom` or `auto`.
| `side` | `number` | `5` | Width of the starter line of callout pointer.
| `start` | `number`\|`string` | `'50%'` | The percentage of the separator dimension to use as starting point for callout pointer. Could be set in pixel by a number, or in percentage of the separator dimension by a string.

## Arrow heads

Namespace: `options.annotations[annotationID].arrowHeads`, it defines options for the line annotation arrow heads.
Expand Down
179 changes: 179 additions & 0 deletions docs/samples/line/callout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Callout

```js chart-editor
// <block:setup:5>
const DATA_COUNT = 16;
const MIN = 20;
const MAX = 100;

Utils.srand(8);

const labels = [];
for (let i = 0; i < DATA_COUNT; ++i) {
labels.push('' + i);
}

const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX};

const data = {
labels: labels,
datasets: [{
data: Utils.numbers(numberCfg)
}]
};
// </block:setup>

// <block:annotation1:1>
const annotation1 = {
type: 'line',
borderColor: 'rgb(100, 149, 237)',
borderDash: [6, 6],
borderDashOffset: 0,
borderWidth: 3,
label: {
display: true,
backgroundColor: 'rgb(100, 149, 237)',
content: (ctx) => 'Average: ' + average(ctx).toFixed(2)
},
scaleID: 'y',
value: (ctx) => average(ctx)
};
// </block:annotation1>

// <block:annotation2:2>
const annotation2 = {
type: 'line',
borderColor: 'rgba(102, 102, 102, 0.5)',
borderDash: [6, 6],
borderDashOffset: 0,
borderWidth: 3,
label: {
display: true,
backgroundColor: 'rgba(102, 102, 102, 0.5)',
borderWidth: 1,
borderColor: 'rgba(102, 102, 102, 0.5)',
callout: {
display: true,
borderColor: 'rgba(102, 102, 102, 0.5)',
borderDash: [6, 6],
borderWidth: 2,
margin: 0
},
color: 'black',
content: (ctx) => (average(ctx) + standardDeviation(ctx)).toFixed(2),
position: 'start',
xAdjust: 100,
yAdjust: -50
},
scaleID: 'y',
value: (ctx) => average(ctx) + standardDeviation(ctx)
};
// </block:annotation2>

// <block:annotation3:3>
const annotation3 = {
type: 'line',
borderColor: 'rgba(102, 102, 102, 0.5)',
borderDash: [6, 6],
borderDashOffset: 0,
borderWidth: 3,
label: {
display: true,
backgroundColor: 'rgba(102, 102, 102, 0.5)',
borderWidth: 1,
borderColor: 'rgba(102, 102, 102, 0.5)',
callout: {
display: true,
borderColor: 'rgba(102, 102, 102, 0.5)',
borderDash: [6, 6],
borderWidth: 2,
margin: 0
},
color: 'black',
content: (ctx) => (average(ctx) - standardDeviation(ctx)).toFixed(2),
position: 'end',
xAdjust: -100,
yAdjust: 50
},
scaleID: 'y',
value: (ctx) => average(ctx) - standardDeviation(ctx)
};
// </block:annotation3>

/* <block:config:0> */
const config = {
type: 'line',
data,
options: {
scale: {
y: {
beginAtZero: true,
max: 120,
min: 0
}
},
plugins: {
annotation: {
annotations: {
annotation1,
annotation2,
annotation3
}
}
}
}
};
/* </block:config> */

// <block:utils:4>
function average(ctx) {
const values = ctx.chart.data.datasets[0].data;
return values.reduce((a, b) => a + b, 0) / values.length;
}

function standardDeviation(ctx) {
const values = ctx.chart.data.datasets[0].data;
const n = values.length;
const mean = average(ctx);
return Math.sqrt(values.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
}

// </block:utils>

const actions = [
{
name: 'Randomize',
handler: function(chart) {
chart.data.datasets.forEach(function(dataset, i) {
dataset.data = dataset.data.map(() => Utils.rand(MIN, MAX));
});
chart.update();
}
},
{
name: 'Add data',
handler: function(chart) {
chart.data.labels.push(chart.data.labels.length);
chart.data.datasets.forEach(function(dataset, i) {
dataset.data.push(Utils.rand(MIN, MAX));
});
chart.update();
}
},
{
name: 'Remove data',
handler: function(chart) {
chart.data.labels.shift();
chart.data.datasets.forEach(function(dataset, i) {
dataset.data.shift();
});
chart.update();
}
}
];

module.exports = {
actions: actions,
config: config,
};
```
9 changes: 4 additions & 5 deletions src/types/line.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Element} from 'chart.js';
import {PI, toRadians, toDegrees, toPadding} from 'chart.js/helpers';
import {EPSILON, clamp, scaleValue, measureLabelSize, getRelativePosition, setBorderStyle, setShadowStyle, getElementCenterPoint, retrieveScaleID, getDimensionByScale} from '../helpers';
import LabelAnnotation from './label';

const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
const interpolateX = (y, p1, p2) => pointInLine(p1, p2, Math.abs((y - p1.y) / (p2.y - p1.y))).x;
Expand Down Expand Up @@ -148,9 +149,7 @@ LineAnnotation.defaults = {
borderRadius: 6,
borderShadowColor: 'transparent',
borderWidth: 0,
callout: {
display: false
},
callout: Object.assign({}, LabelAnnotation.defaults.callout),
color: '#fff',
content: null,
display: false,
Expand Down Expand Up @@ -275,8 +274,6 @@ function applyScaleValueToDimension(area, scale, options) {
}

function resolveLabelElementProperties(chart, properties, options) {
// TODO to remove by another PR to enable callout for line label
options.callout.display = false;
const borderWidth = options.borderWidth;
const padding = toPadding(options.padding);
const textSize = measureLabelSize(chart.ctx, options);
Expand Down Expand Up @@ -312,6 +309,8 @@ function calculateLabelPosition(properties, label, sizes, chartArea) {
y2: centerY + (height / 2),
centerX,
centerY,
pointX: pt.x,
pointY: pt.y,
width,
height,
rotation: toDegrees(rotation)
Expand Down
106 changes: 106 additions & 0 deletions test/fixtures/line/labelCallout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
module.exports = {
config: {
type: 'scatter',
options: {
scales: {
x: {
display: false,
min: 0,
max: 10
},
y: {
display: false,
min: 0,
max: 10
}
},
plugins: {
annotation: {
annotations: {
line1: {
type: 'line',
scaleID: 'y',
value: 8,
borderColor: 'black',
borderWidth: 5,
label: {
display: true,
backgroundColor: '#f5f5f5',
borderColor: 'black',
borderRadius: 0,
borderWidth: 1,
content: 'yAdjust: -40, callout position: auto',
yAdjust: -40,
callout: {
display: true
}
}
},
line2: {
type: 'line',
scaleID: 'y',
value: 6,
borderColor: 'black',
borderWidth: 5,
label: {
display: true,
backgroundColor: '#f5f5f5',
borderColor: 'black',
borderRadius: 0,
borderWidth: 1,
content: ['yAdjust: -40, xAdjust: -150', 'callout position: auto'],
yAdjust: -40,
xAdjust: -150,
callout: {
display: true
}
}
},
line3: {
type: 'line',
scaleID: 'y',
value: 4,
borderColor: 'black',
borderWidth: 5,
label: {
display: true,
backgroundColor: '#f5f5f5',
borderColor: 'black',
borderRadius: 0,
borderWidth: 1,
content: ['yAdjust: 40, xAdjust: 150', 'callout position: auto'],
yAdjust: 40,
xAdjust: 150,
callout: {
display: true
}
}
},
line4: {
type: 'line',
scaleID: 'y',
value: 2,
borderColor: 'black',
borderWidth: 5,
label: {
display: true,
backgroundColor: '#f5f5f5',
borderColor: 'black',
borderRadius: 0,
borderWidth: 1,
content: 'yAdjust: 40, callout position: auto',
yAdjust: 40,
callout: {
display: true
}
}
}
}
}
}
}
},
options: {
spriteText: true
}
};
Binary file added test/fixtures/line/labelCallout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1cfbfc6

Please sign in to comment.