diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index f0955ce69..d5091e388 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -140,6 +140,8 @@ module.exports = { children: [ 'ellipse/basic', 'ellipse/rotation', + 'ellipse/label', + 'ellipse/image', ] }, { diff --git a/docs/guide/types/_commonInnerLabel.md b/docs/guide/types/_commonInnerLabel.md new file mode 100644 index 000000000..c1943ef16 --- /dev/null +++ b/docs/guide/types/_commonInnerLabel.md @@ -0,0 +1,35 @@ +## Label + +Namespace: `options.annotations[annotationID].label`, it defines options for the the label of annotation. + +All of these options can be [Scriptable](../options#scriptable-options) + +| Name | Type | Default | Notes +| ---- | ---- | :----: | ---- +| `color` | [`Color`](../options#color) | `'black'` | 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. +| `drawTime` | `string` | `options.drawTime` | See [drawTime](../options#draw-time). Defaults to the annotation draw time if unset +| `font` | [`Font`](../options#font) | `{ weight: 'bold' }` | Label font +| `height` | `number`\|`string` | `undefined` | Overrides the height of the image or canvas element. Could be set in pixel by a number, or in percentage of current height of image or canvas element by a string. If undefined, uses the height of the image or canvas element. It is used only when the content is an image or canvas element. +| `padding` | [`Padding`](../options#padding) | `6` | The padding to add around the text label. +| [`position`](#position) | `string`\|`{x: string, y: string}` | `'center'` | Anchor position of label in the annotation. +| `rotation` | `number` | `undefined` | Rotation of label, in degrees. If `undefined`, the annotation rotation is used. +| `textAlign` | `string` | `'start'` | Text alignment of label content when there's more than one line. Possible options are: `'left'`, `'start'`, `'center'`, `'end'`, `'right'`. +| `textStrokeColor` | [`Color`](../options#color) | `undefined` | The color of the stroke around the text. +| `textStrokeWidth` | `number` | `0` | Stroke width around the text. +| `width` | `number`\|`string` | `undefined` | Overrides the width of the image or canvas element. Could be set in pixel by a number, or in percentage of current width of image or canvas element by a string. If undefined, uses the width of the image or canvas element. It is used only when the content is an image or canvas element. +| `xAdjust` | `number` | `0` | Adjustment along x-axis (left-right) of label relative to computed position. Negative values move the label left, positive right. +| `yAdjust` | `number` | `0` | Adjustment along y-axis (top-bottom) of label relative to computed position. Negative values move the label up, positive down. +| `z` | `number` | `0` | It determines the drawing stack level of the label element, with same `drawTime`. + +### Position + +A position can be set in 2 different values types: + +1. `'start'`, `'center'`, `'end'` which are defining where the label will be located +2. a `string`, in percentage format `'number%'`, is representing the percentage on the size where the label will be located + +If this value is a string (possible options are `'start'`, `'center'`, `'end'` or a string in percentage format), it is applied to vertical and horizontal position in the annotation. + +If this value is an object, the `x` property defines the horizontal alignment in the annotation. Similarly, the `y` property defines the vertical alignment in the annotation. Possible options for both properties are `'start'`, `'center'`, `'end'`, a string in percentage format. Omitted property have value of the default, `'center'`. diff --git a/docs/guide/types/box.md b/docs/guide/types/box.md index 9c6a16408..51f534180 100644 --- a/docs/guide/types/box.md +++ b/docs/guide/types/box.md @@ -103,41 +103,7 @@ If one of the axes does not match an axis in the chart, the box will take the en 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. -## Label - -Namespace: `options.annotations[annotationID].label`, it defines options for the box annotation label. - -All of these options can be [Scriptable](../options#scriptable-options) - -| Name | Type | Default | Notes -| ---- | ---- | :----: | ---- -| `color` | [`Color`](../options#color) | `'black'` | 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. -| `drawTime` | `string` | `options.drawTime` | See [drawTime](../options#draw-time). Defaults to the box annotation draw time if unset -| `font` | [`Font`](../options#font) | `{ weight: 'bold' }` | Label font -| `height` | `number`\|`string` | `undefined` | Overrides the height of the image or canvas element. Could be set in pixel by a number, or in percentage of current height of image or canvas element by a string. If undefined, uses the height of the image or canvas element. It is used only when the content is an image or canvas element. -| `padding` | [`Padding`](../options#padding) | `6` | The padding to add around the text label. -| [`position`](#position) | `string`\|`{x: string, y: string}` | `'center'` | Anchor position of label in the box. -| `rotation` | `number` | `undefined` | Rotation of label, in degrees. If `undefined`, the box rotation is used. -| `textAlign` | `string` | `'start'` | Text alignment of label content when there's more than one line. Possible options are: `'left'`, `'start'`, `'center'`, `'end'`, `'right'`. -| `textStrokeColor` | [`Color`](../options#color) | `undefined` | The color of the stroke around the text. -| `textStrokeWidth` | `number` | `0` | Stroke width around the text. -| `width` | `number`\|`string` | `undefined` | Overrides the width of the image or canvas element. Could be set in pixel by a number, or in percentage of current width of image or canvas element by a string. If undefined, uses the width of the image or canvas element. It is used only when the content is an image or canvas element. -| `xAdjust` | `number` | `0` | Adjustment along x-axis (left-right) of label relative to computed position. Negative values move the label left, positive right. -| `yAdjust` | `number` | `0` | Adjustment along y-axis (top-bottom) of label relative to computed position. Negative values move the label up, positive down. -| `z` | `number` | `0` | It determines the drawing stack level of the label element, with same `drawTime`. - -### Position - -A position can be set in 2 different values types: - -1. `'start'`, `'center'`, `'end'` which are defining where the label will be located -2. a `string`, in percentage format `'number%'`, is representing the percentage on the size where the label will be located - -If this value is a string (possible options are `'start'`, `'center'`, `'end'` or a string in percentage format), it is applied to vertical and horizontal position in the box. - -If this value is an object, the `x` property defines the horizontal alignment in the box. Similarly, the `y` property defines the vertical alignment in the box. Possible options for both properties are `'start'`, `'center'`, `'end'`, a string in percentage format. Omitted property have value of the default, `'center'`. +!!!include(./guide/types/_commonInnerLabel.md)!!! ## Element diff --git a/docs/guide/types/ellipse.md b/docs/guide/types/ellipse.md index 4ead77c7c..88c4c50ac 100644 --- a/docs/guide/types/ellipse.md +++ b/docs/guide/types/ellipse.md @@ -55,6 +55,7 @@ The following options are available for ellipse annotations. | ---- | ---- | :----: | ---- | [`backgroundShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` | [`borderWidth`](#styling) | `number`| Yes | `1` +| [`label`](#label) | `object` | Yes | | [`rotation`](#general) | `number`| Yes | `0` !!!include(./guide/types/_commonOptions.md)!!! @@ -92,6 +93,8 @@ If one of the axes does not match an axis in the chart, the ellipse will take th | `shadowOffsetX` | The distance that shadow will be offset horizontally. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX). | `shadowOffsetY` | The distance that shadow will be offset vertically. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY). +!!!include(./guide/types/_commonInnerLabel.md)!!! + ## Element The following diagram is showing the element properties about a `'ellipse'` annotation: diff --git a/docs/samples/ellipse/image.md b/docs/samples/ellipse/image.md new file mode 100644 index 000000000..495e2fea2 --- /dev/null +++ b/docs/samples/ellipse/image.md @@ -0,0 +1,76 @@ +# Using images as labels + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 10; +const MAX = 100; + +const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX}; + +const data = { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + datasets: [{ + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation = { + type: 'ellipse', + backgroundColor: 'rgba(0, 0, 0, 0.2)', + borderWidth: 1, + borderColor: '#F27173', + yMin: 30, + yMax: 80, + xMax: 2, + xMin: 5, + label: { + display: true, + content: Utils.getImage(), + width: 150, + height: 150, + position: 'center' + } +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scales: { + y: { + beginAtZero: true + } + }, + plugins: { + annotation: { + annotations: { + annotation + } + } + } + } +}; +/* */ + +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(); + } + } +]; + +module.exports = { + actions: actions, + config: config +}; +``` \ No newline at end of file diff --git a/docs/samples/ellipse/label.md b/docs/samples/ellipse/label.md new file mode 100644 index 000000000..0bcedfe68 --- /dev/null +++ b/docs/samples/ellipse/label.md @@ -0,0 +1,72 @@ +# Labeling + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 10; +const MAX = 100; + +const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX}; + +const data = { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + datasets: [{ + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation = { + type: 'ellipse', + backgroundColor: 'rgba(208, 208, 208, 0.2)', + borderWidth: 0, + label: { + drawTime: 'afterDatasetsDraw', + display: true, + color: 'rgba(208, 208, 208, 0.6)', + content: 'whole year', + font: { + size: (ctx) => ctx.chart.chartArea.height / 4 + }, + position: { + x: 'center', + y: 'end' + } + } +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + plugins: { + annotation: { + annotations: { + annotation + } + } + } + } +}; +/* */ + +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(); + } + } +]; + +module.exports = { + actions: actions, + config: config, +}; +``` diff --git a/src/helpers/helpers.chart.js b/src/helpers/helpers.chart.js index f29086f08..87bc14ec0 100644 --- a/src/helpers/helpers.chart.js +++ b/src/helpers/helpers.chart.js @@ -1,5 +1,6 @@ -import {isFinite} from 'chart.js/helpers'; -import {isBoundToPoint} from './helpers.options'; +import {isFinite, toPadding} from 'chart.js/helpers'; +import {measureLabelSize} from './helpers.canvas'; +import {isBoundToPoint, getRelativePosition, toPosition} from './helpers.options'; /** * @typedef { import("chart.js").Chart } Chart @@ -144,6 +145,23 @@ export function resolvePointProperties(chart, options) { return getChartCircle(chart, options); } +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {AnnotationBoxModel} + */ +export function resolveBoxAndLabelProperties(chart, options) { + const properties = resolveBoxProperties(chart, options); + const {x, y} = properties; + properties.elements = [{ + type: 'label', + optionScope: 'label', + properties: resolveLabelElementProperties(chart, properties, options) + }]; + properties.initProperties = {x, y}; + return properties; +} + function getChartCircle(chart, options) { const point = getChartPoint(chart, options); const size = options.radius * 2; @@ -166,3 +184,54 @@ function getChartDimensionByScale(scale, options) { end: Math.max(result.start, result.end) }; } + +function calculateX({properties, options}, labelSize, position, padding) { + const {x: start, x2: end, width: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { + position: position.x, + padding: {start: padding.left, end: padding.right}, + adjust: options.label.xAdjust, + size: labelSize.width + }); +} + +function calculateY({properties, options}, labelSize, position, padding) { + const {y: start, y2: end, height: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { + position: position.y, + padding: {start: padding.top, end: padding.bottom}, + adjust: options.label.yAdjust, + size: labelSize.height + }); +} + +function calculatePosition(boxOpts, labelOpts) { + const {start, end, borderWidth} = boxOpts; + const {position, padding: {start: padStart, end: padEnd}, adjust} = labelOpts; + const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; + return start + borderWidth / 2 + adjust + getRelativePosition(availableSize, position); +} + +function resolveLabelElementProperties(chart, properties, options) { + const label = options.label; + label.backgroundColor = 'transparent'; + label.callout.display = false; + const position = toPosition(label.position); + const padding = toPadding(label.padding); + const labelSize = measureLabelSize(chart.ctx, label); + const x = calculateX({properties, options}, labelSize, position, padding); + const y = calculateY({properties, options}, labelSize, position, padding); + const width = labelSize.width + padding.width; + const height = labelSize.height + padding.height; + return { + x, + y, + x2: x + width, + y2: y + height, + width, + height, + centerX: x + width / 2, + centerY: y + height / 2, + rotation: label.rotation + }; +} diff --git a/src/types/box.js b/src/types/box.js index 20a398bb8..1c6e494ea 100644 --- a/src/types/box.js +++ b/src/types/box.js @@ -1,6 +1,6 @@ import {Element} from 'chart.js'; -import {toPadding, toRadians} from 'chart.js/helpers'; -import {drawBox, getRelativePosition, measureLabelSize, resolveBoxProperties, toPosition, inBoxRange, rotated, translate, getElementCenterPoint} from '../helpers'; +import {toRadians} from 'chart.js/helpers'; +import {drawBox, resolveBoxAndLabelProperties, inBoxRange, rotated, translate, getElementCenterPoint} from '../helpers'; export default class BoxAnnotation extends Element { @@ -25,15 +25,7 @@ export default class BoxAnnotation extends Element { } resolveElementProperties(chart, options) { - const properties = resolveBoxProperties(chart, options); - const {x, y} = properties; - properties.elements = [{ - type: 'label', - optionScope: 'label', - properties: resolveLabelElementProperties(chart, properties, options) - }]; - properties.initProperties = {x, y}; - return properties; + return resolveBoxAndLabelProperties(chart, options); } } @@ -102,54 +94,3 @@ BoxAnnotation.descriptors = { _fallback: true } }; - -function calculateX({properties, options}, labelSize, position, padding) { - const {x: start, x2: end, width: size} = properties; - return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { - position: position.x, - padding: {start: padding.left, end: padding.right}, - adjust: options.label.xAdjust, - size: labelSize.width - }); -} - -function calculateY({properties, options}, labelSize, position, padding) { - const {y: start, y2: end, height: size} = properties; - return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { - position: position.y, - padding: {start: padding.top, end: padding.bottom}, - adjust: options.label.yAdjust, - size: labelSize.height - }); -} - -function calculatePosition(boxOpts, labelOpts) { - const {start, end, borderWidth} = boxOpts; - const {position, padding: {start: padStart, end: padEnd}, adjust} = labelOpts; - const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; - return start + borderWidth / 2 + adjust + getRelativePosition(availableSize, position); -} - -function resolveLabelElementProperties(chart, properties, options) { - const label = options.label; - label.backgroundColor = 'transparent'; - label.callout.display = false; - const position = toPosition(label.position); - const padding = toPadding(label.padding); - const labelSize = measureLabelSize(chart.ctx, label); - const x = calculateX({properties, options}, labelSize, position, padding); - const y = calculateY({properties, options}, labelSize, position, padding); - const width = labelSize.width + padding.width; - const height = labelSize.height + padding.height; - return { - x, - y, - x2: x + width, - y2: y + height, - width, - height, - centerX: x + width / 2, - centerY: y + height / 2, - rotation: label.rotation - }; -} diff --git a/src/types/ellipse.js b/src/types/ellipse.js index e6a4783fb..d27ac1fa6 100644 --- a/src/types/ellipse.js +++ b/src/types/ellipse.js @@ -1,6 +1,7 @@ import {Element} from 'chart.js'; import {PI, toRadians} from 'chart.js/helpers'; -import {EPSILON, resolveBoxProperties, setBorderStyle, setShadowStyle, rotated, translate, getElementCenterPoint} from '../helpers'; +import {EPSILON, resolveBoxAndLabelProperties, setBorderStyle, setShadowStyle, rotated, translate, getElementCenterPoint} from '../helpers'; +import BoxAnnotation from './box'; export default class EllipseAnnotation extends Element { @@ -39,8 +40,12 @@ export default class EllipseAnnotation extends Element { ctx.restore(); } + get label() { + return this.elements && this.elements[0]; + } + resolveElementProperties(chart, options) { - return resolveBoxProperties(chart, options); + return resolveBoxAndLabelProperties(chart, options); } } @@ -55,6 +60,7 @@ EllipseAnnotation.defaults = { borderShadowColor: 'transparent', borderWidth: 1, display: true, + label: Object.assign({}, BoxAnnotation.defaults.label), rotation: 0, shadowBlur: 0, shadowOffsetX: 0, @@ -73,6 +79,12 @@ EllipseAnnotation.defaultRoutes = { backgroundColor: 'color' }; +EllipseAnnotation.descriptors = { + label: { + _fallback: true + } +}; + function pointInEllipse(p, ellipse, rotation, borderWidth) { const {width, height, centerX, centerY} = ellipse; const xRadius = width / 2; diff --git a/test/fixtures/ellipse/label-dynamic.js b/test/fixtures/ellipse/label-dynamic.js new file mode 100644 index 000000000..f685d806c --- /dev/null +++ b/test/fixtures/ellipse/label-dynamic.js @@ -0,0 +1,54 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 10 + }, + y: { + display: false, + min: 0, + max: 10 + } + }, + plugins: { + annotation: { + annotations: { + ellipse: { + type: 'ellipse', + xMin: 1, + xMax: 9, + yMin: 1, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + display: false, + content: 'This is dynamic!', + }, + enter({element}) { + element.label.options.display = true; + return true; + } + }, + } + } + } + } + }, + options: { + canvas: { + width: 256, + height: 256 + }, + spriteText: true, + async run(chart) { + const el = window.getAnnotationElements(chart)[0]; + await window.triggerMouseEvent(chart, 'mousemove', el.getCenterPoint()); + } + } +}; diff --git a/test/fixtures/ellipse/label-dynamic.png b/test/fixtures/ellipse/label-dynamic.png new file mode 100644 index 000000000..f37c30a45 Binary files /dev/null and b/test/fixtures/ellipse/label-dynamic.png differ diff --git a/test/fixtures/ellipse/label.js b/test/fixtures/ellipse/label.js new file mode 100644 index 000000000..32505a784 --- /dev/null +++ b/test/fixtures/ellipse/label.js @@ -0,0 +1,94 @@ +module.exports = { + tolerance: 0.0075, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + ellipse1: { + type: 'ellipse', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderDash: [6, 6], + borderWidth: 5, + label: { + display: true, + content: 'This is a label', + } + }, + ellipse2: { + type: 'ellipse', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'black', + borderWidth: 15, + label: { + display: true, + borderColor: 'green', + content: ['This label is very long', 'and is beyond the box of', 'annotation'], + position: 'start' + } + }, + ellipse3: { + type: 'ellipse', + xMin: -0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderDash: [6, 6], + borderWidth: 5, + label: { + display: true, + content: 'This is a label with different length', + position: 'end' + } + }, + ellipse4: { + type: 'ellipse', + xMin: 'June', + xMax: 'July', + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderDash: [6, 6], + borderWidth: 5, + label: { + display: true, + content: ['This is', 'a multiline', 'label'], + color: 'red', + position: { + x: 'center' + }, + textAlign: 'center' + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/label.png b/test/fixtures/ellipse/label.png new file mode 100644 index 000000000..7b11ea151 Binary files /dev/null and b/test/fixtures/ellipse/label.png differ diff --git a/test/fixtures/ellipse/labelCanvas.js b/test/fixtures/ellipse/labelCanvas.js new file mode 100644 index 000000000..d3a02c3fd --- /dev/null +++ b/test/fixtures/ellipse/labelCanvas.js @@ -0,0 +1,101 @@ +module.exports = { + tolerance: 0.0060, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + canvas1: { + type: 'ellipse', + xMin: -9, + xMax: -1, + yMin: 9, + yMax: 1, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + display: true, + position: 'start', + content: window.createCanvas, + width: '25%', + height: '25%' + } + }, + canvas2: { + type: 'ellipse', + xMin: 1, + xMax: 9, + yMin: 9, + yMax: 1, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + display: true, + position: 'end', + content: window.createCanvas, + width: '25%', + height: '25%' + } + }, + canvas3: { + type: 'ellipse', + xMin: -9, + xMax: -1, + yMin: -1, + yMax: -9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + display: true, + position: { + x: 'start', + y: 'center' + }, + content: window.createCanvas, + width: '25%', + height: '25%' + } + }, + canvas4: { + type: 'ellipse', + xMin: 1, + xMax: 9, + yMin: -1, + yMax: -9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + display: true, + position: { + x: 'center', + y: 'end' + }, + content: window.createCanvas, + width: '25%', + height: '25%' + } + } + } + } + } + } + } +}; diff --git a/test/fixtures/ellipse/labelCanvas.png b/test/fixtures/ellipse/labelCanvas.png new file mode 100644 index 000000000..c2688b4c6 Binary files /dev/null and b/test/fixtures/ellipse/labelCanvas.png differ diff --git a/test/fixtures/ellipse/labelDecoration.js b/test/fixtures/ellipse/labelDecoration.js new file mode 100644 index 000000000..19330834b --- /dev/null +++ b/test/fixtures/ellipse/labelDecoration.js @@ -0,0 +1,84 @@ +module.exports = { + tolerance: 0.0320, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 10 + }, + y: { + display: false, + min: 0, + max: 10 + } + }, + plugins: { + annotation: { + annotations: { + ellipse1: { + type: 'ellipse', + xMin: 0.5, + xMax: 9.5, + yMin: 6.5, + yMax: 9.5, + backgroundColor: '#f5f5f5', + borderColor: 'black', + borderWidth: 1, + label: { + display: true, + content: ['text stroke width 5', 'text stroke color red'], + textStrokeWidth: 5, + textStrokeColor: 'red', + font: { + size: 40 + } + } + }, + ellipse2: { + type: 'ellipse', + xMin: 0.5, + xMax: 9.5, + yMin: 4, + yMax: 6, + backgroundColor: '#f5f5f5', + borderColor: 'black', + borderWidth: 1, + label: { + display: true, + textStrokeWidth: 3, + color: 'white', + content: 'text stroke width 3', + font: { + size: 30 + } + } + }, + ellipse3: { + type: 'ellipse', + xMin: 0.5, + xMax: 9.5, + yMin: 0.5, + yMax: 3.5, + backgroundColor: '#f5f5f5', + borderColor: 'black', + borderWidth: 1, + label: { + display: true, + color: '#40E0D0', + content: ['text stroke width 10', 'text stroke color turq'], + textStrokeWidth: 10, + textStrokeColor: 'black', + font: { + size: 40 + } + } + } + } + } + } + } + } +}; diff --git a/test/fixtures/ellipse/labelDecoration.png b/test/fixtures/ellipse/labelDecoration.png new file mode 100644 index 000000000..e78c14c14 Binary files /dev/null and b/test/fixtures/ellipse/labelDecoration.png differ diff --git a/test/fixtures/ellipse/labelMultiline.js b/test/fixtures/ellipse/labelMultiline.js new file mode 100644 index 000000000..f9641fee3 --- /dev/null +++ b/test/fixtures/ellipse/labelMultiline.js @@ -0,0 +1,88 @@ +module.exports = { + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + ellipse1: { + type: 'ellipse', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + display: true, + content: ['This is a label', 'but this is multiline'], + } + }, + ellipse2: { + type: 'ellipse', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + display: true, + content: ['This is a label', 'but this is multiline'], + position: 'start' + } + }, + ellipse3: { + type: 'ellipse', + xMin: -0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + display: true, + content: ['This is a label', 'but this is multiline'], + position: 'end' + } + }, + ellipse4: { + type: 'ellipse', + xMin: 'June', + xMax: 'July', + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + display: true, + content: (ctx) => ['This is a label', 'type:' + ctx.type], + color: 'red', + position: { + x: 'start' + } + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/labelMultiline.png b/test/fixtures/ellipse/labelMultiline.png new file mode 100644 index 000000000..7e7a84508 Binary files /dev/null and b/test/fixtures/ellipse/labelMultiline.png differ diff --git a/test/fixtures/ellipse/labelPadding.js b/test/fixtures/ellipse/labelPadding.js new file mode 100644 index 000000000..1a5c9d110 --- /dev/null +++ b/test/fixtures/ellipse/labelPadding.js @@ -0,0 +1,96 @@ +module.exports = { + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + ellipse1: { + type: 'ellipse', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + display: true, + content: 'ellipse1: padding: 30', + position: 'start', + padding: 30 + } + }, + ellipse2: { + type: 'ellipse', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + display: true, + content: 'ellipse2: padding: {x: 20}', + position: 'start', + padding: {x: 20} + } + }, + ellipse3: { + type: 'ellipse', + xMin: -0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + display: true, + content: 'ellipse3: padding: {y: 20}', + position: 'end', + padding: {y: 20} + } + }, + ellipse4: { + type: 'ellipse', + xMin: 'June', + xMax: 'July', + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + display: true, + content: ['ellipse4:', 'padding', '{left: 20,', 'top: 20}'], + color: 'red', + position: { + x: 'start', + y: 'start' + }, + padding() { + return {left: 20, top: 20}; + } + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/labelPadding.png b/test/fixtures/ellipse/labelPadding.png new file mode 100644 index 000000000..c046ef72c Binary files /dev/null and b/test/fixtures/ellipse/labelPadding.png differ diff --git a/test/fixtures/ellipse/labelPosition.js b/test/fixtures/ellipse/labelPosition.js new file mode 100644 index 000000000..f3c12ef81 --- /dev/null +++ b/test/fixtures/ellipse/labelPosition.js @@ -0,0 +1,102 @@ +module.exports = { + tolerance: 0.0085, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + ellipse1: { + type: 'ellipse', + xMin: -9, + yMin: 9, + xMax: -1, + yMax: 1, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + display: true, + content: 'p: 0%,100%', + position: { + x: '0%', + y: '100%' + } + } + }, + ellipse2: { + type: 'ellipse', + xMin: 1, + yMin: 9, + xMax: 9, + yMax: 1, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + display: true, + content: 'p: 25%,75%', + position: { + x: '25%', + y: '75%' + } + } + }, + ellipse3: { + type: 'ellipse', + xMin: -9, + yMin: -1, + xMax: -1, + yMax: -9, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + display: true, + content: 'p: 50%,50%', + position: { + x: '50%', + y: '50%' + } + } + }, + ellipse4: { + type: 'ellipse', + xMin: 1, + yMin: -1, + xMax: 9, + yMax: -9, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + display: true, + content: 'p: 100%,0%', + position: { + x: '100%', + y: '0%' + } + } + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/labelPosition.png b/test/fixtures/ellipse/labelPosition.png new file mode 100644 index 000000000..333609fa9 Binary files /dev/null and b/test/fixtures/ellipse/labelPosition.png differ diff --git a/test/fixtures/ellipse/labelRotation.js b/test/fixtures/ellipse/labelRotation.js new file mode 100644 index 000000000..02750e1b6 --- /dev/null +++ b/test/fixtures/ellipse/labelRotation.js @@ -0,0 +1,141 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 10 + }, + y: { + display: false, + min: 0, + max: 10 + } + }, + plugins: { + annotation: { + annotations: { + ellipse1: { + type: 'ellipse', + xMin: 1, + xMax: 4, + yMin: 9, + yMax: 7, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + label: { + display: true, + rotation: 45, + content: 'rotation: 45', + } + }, + ellipse2: { + type: 'ellipse', + xMin: 4, + xMax: 7, + yMin: 9, + yMax: 7, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + label: { + display: true, + rotation: 90, + content: 'rotation: 90', + } + }, + ellipse3: { + type: 'ellipse', + xMin: 7, + xMax: 10, + yMin: 9, + yMax: 7, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + label: { + display: true, + rotation: 135, + content: 'rotation: 135', + } + }, + ellipse4: { + type: 'ellipse', + xMin: 1, + xMax: 4, + yMin: 6, + yMax: 4, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + label: { + display: true, + rotation: 180, + content: 'rotation: 180', + } + }, + ellipse5: { + type: 'ellipse', + xMin: 4, + xMax: 7, + yMin: 6, + yMax: 4, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + label: { + display: true, + rotation: 225, + content: 'rotation: 225', + } + }, + ellipse6: { + type: 'ellipse', + xMin: 7, + xMax: 10, + yMin: 6, + yMax: 4, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + label: { + display: true, + rotation: 270, + content: 'rotation: 270', + } + }, + ellipse7: { + type: 'ellipse', + xMin: 1, + xMax: 4, + yMin: 3, + yMax: 1, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + label: { + display: true, + rotation: 315, + content: 'rotation: 315', + } + }, + ellipse8: { + type: 'ellipse', + xMin: 4, + xMax: 7, + yMin: 3, + yMax: 1, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + rotation: 45, + label: { + display: true, + rotation: 0, + content: ['ellipse rot: 45', 'label rot: 0'] + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/labelRotation.png b/test/fixtures/ellipse/labelRotation.png new file mode 100644 index 000000000..0527afece Binary files /dev/null and b/test/fixtures/ellipse/labelRotation.png differ diff --git a/test/fixtures/ellipse/zIndex.js b/test/fixtures/ellipse/zIndex.js new file mode 100644 index 000000000..dd9180292 --- /dev/null +++ b/test/fixtures/ellipse/zIndex.js @@ -0,0 +1,120 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 10 + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + ellipse1: { + type: 'ellipse', + xMin: 3, + xMax: 7, + yMin: 1, + yMax: 4, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 1, + label: { + display: true, + content: 'ellipse1 z:+10/z: 0', + z: 0 + }, + z: 10 + }, + ellipse2: { + type: 'ellipse', + xMin: 1.5, + xMax: 5.5, + yMin: 15, + yMax: 23.5, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + label: { + display: true, + content: 'ellipse2 fallback/z: +100', + z: 100 + } + }, + ellipse3: { + type: 'ellipse', + xMin: 2.5, + xMax: 7, + yMin: 16, + yMax: 21, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + label: { + display: true, + content: 'ellipse3 fallback/z: -1', + z: -1 + } + }, + ellipse4: { + type: 'ellipse', + xMin: 3, + xMax: 7, + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + label: { + display: true, + content: 'ellipse4 fallback/fallback', + }, + }, + ellipse5: { + type: 'ellipse', + xMin: 1.5, + xMax: 5.5, + yMin: 10, + yMax: 14.5, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + label: { + display: true, + content: 'ellipse5 z: +100/z: 0', + position: 'start', + z: 0 + }, + z: 100 + }, + ellipse6: { + type: 'ellipse', + xMin: 2.5, + xMax: 4.5, + yMin: 11, + yMax: 13.5, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + label: { + display: true, + content: 'ellipse6 z: 0/fallback', + }, + z: 0 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/zIndex.png b/test/fixtures/ellipse/zIndex.png new file mode 100644 index 000000000..7303519dc Binary files /dev/null and b/test/fixtures/ellipse/zIndex.png differ diff --git a/types/options.d.ts b/types/options.d.ts index ed3671c4b..4b9e151aa 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -107,6 +107,7 @@ export interface BoxAnnotationOptions extends CoreAnnotationOptions { export interface EllipseAnnotationOptions extends CoreAnnotationOptions { backgroundColor?: Scriptable, + label?: BoxLabelOptions, rotation?: Scriptable }