diff --git a/.eslintrc.yml b/.eslintrc.yml index 389c35be5..875ccdcb0 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -31,3 +31,4 @@ rules: es/no-regexp-named-capture-groups: "error" es/no-regexp-s-flag: "error" es/no-regexp-unicode-property-escapes: "error" + linebreak-style: ["error", "unix"] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..7535f72b9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# We'll let Git's auto-detection algorithm infer if a file is text. If it is, +# enforce LF line endings regardless of OS or git configurations. +* text=auto eol=lf + +# Isolate binary files in case the auto-detection algorithm fails and +# marks them as text files (which could brick them). +*.{png,jpg,jpeg,gif,webp,woff,woff2} binary diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 81176ec1d..92ce08363 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -19,6 +19,7 @@ categories: - title: 'Development' labels: - 'chore' + - 'performance' exclude-labels: - 'infrastructure' change-template: '- #$NUMBER $TITLE' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e4079928..56e287000 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,10 @@ jobs: steps: - uses: actions/checkout@v2 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 + with: + cache: npm + node-version: 16 - run: | npm ci npm run build diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 686e80b9e..a699bc0e2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,31 +8,36 @@ on: jobs: checks: - if: github.event_name != 'push' + if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 with: - node-version: '14.x' + cache: npm + node-version: 16 - name: Test Build run: | npm ci + ./scripts/docs-config.sh master npm run docs gh-release: - if: github.event_name != 'pull_request' + if: github.event_name == 'push' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 with: - node-version: '14.x' + cache: npm + node-version: 16 - name: Build run: | npm ci + ./scripts/docs-config.sh master npm run docs - name: Release to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GH_AUTH_TOKEN }} publish_dir: dist/docs + destination_dir: master diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 54dad571b..9a62ff970 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -20,7 +20,10 @@ jobs: steps: - uses: actions/checkout@v2 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 + with: + cache: npm + node-version: 16 - name: Test run: | npm ci @@ -31,10 +34,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: - node-version: 12 + cache: npm + node-version: 16 registry-url: https://registry.npmjs.org/ + - name: Setup and build run: | npm ci @@ -45,13 +50,64 @@ jobs: npm pack env: VERSION: ${{ needs.setup.outputs.version }} + - name: Publish @next run: npm publish --tag next if: "github.event.release.prerelease" env: NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} + - name: Publish @latest run: npm publish --tag latest if: "!github.event.release.prerelease" env: NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} + + - name: Build docs + run: | + ./scripts/docs-config.sh "$VERSION" release + npm run docs + env: + VERSION: ${{ needs.setup.outputs.version }} + + - name: Release to GitHub Pages (version) + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GH_AUTH_TOKEN }} + publish_dir: dist/docs + destination_dir: ${{ needs.setup.outputs.version }} + + publish-docs: + needs: [test, setup] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 16 + registry-url: https://registry.npmjs.org/ + + - name: Build docs (latest/next) + run: | + npm ci + npm run build + ./scripts/docs-config.sh "$VERSION" + npm run docs + env: + VERSION: ${{ needs.setup.outputs.version }} + + - name: Release to GitHub Pages (latest) + if: "!github.event.release.prerelease" + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GH_AUTH_TOKEN }} + publish_dir: dist/docs + destination_dir: latest + + - name: Release to GitHub Pages (next) + if: "github.event.release.prerelease" + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GH_AUTH_TOKEN }} + publish_dir: dist/docs + destination_dir: next diff --git a/README.md b/README.md index af25005e6..64b7a460a 100644 --- a/README.md +++ b/README.md @@ -15,20 +15,13 @@ An annotation plugin for Chart.js >= 3.0.0 For Chart.js 2.4.0 to 2.9.x support, use [version 0.5.7 of this plugin](https://github.com/chartjs/chartjs-plugin-annotation/releases/tag/v0.5.7) -This plugin draws lines, boxes, points and ellipses on the chart area. +This plugin draws lines, boxes, points, labels, polygons and ellipses on the chart area. Annotations work with line, bar, scatter and bubble charts that use linear, logarithmic, time, or category scales. Annotations will not work on any chart that does not have exactly two axes, including pie, radar, and polar area charts. ![Example Screenshot](docs/guide/banner.png) -[View this example](https://chartjs.org/chartjs-plugin-annotation/samples/intro.html) - -## To-do Items - -The following features still need to be done: - -* Box annotation labels -* Text annotations +[View this example](https://www.chartjs.org/chartjs-plugin-annotation/latest/samples/intro.html) ## Documentation @@ -40,4 +33,4 @@ Before submitting an issue or a pull request to the project, please take a momen ## License -Chart.Annotation.js is available under the [MIT license](http://opensource.org/licenses/MIT). +Chart.Annotation.js is available under the [MIT license](LICENSE.md). diff --git a/dist/chartjs-plugin-annotation.esm.js b/dist/chartjs-plugin-annotation.esm.js index 606d6fb7a..6a540f449 100644 --- a/dist/chartjs-plugin-annotation.esm.js +++ b/dist/chartjs-plugin-annotation.esm.js @@ -1,21 +1,19 @@ /*! -* chartjs-plugin-annotation v1.0.2 +* chartjs-plugin-annotation v1.2.2 * https://www.chartjs.org/chartjs-plugin-annotation/index - * (c) 2021 chartjs-plugin-annotation Contributors + * (c) 2022 chartjs-plugin-annotation Contributors * Released under the MIT License */ import ChartJsV3, { Element, defaults, Chart, Animations } from 'chart.js-v3'; -import { Image } from 'canvas'; +import { Image as Image$1 } from 'canvas'; -const {distanceBetweenPoints} = ChartJsV3.helpers; -const callHandler = ChartJsV3.helpers.callback; +const {distanceBetweenPoints, defined: defined$2, callback} = ChartJsV3.helpers; const clickHooks = ['click', 'dblclick']; const moveHooks = ['enter', 'leave']; const hooks = clickHooks.concat(moveHooks); function updateListeners(chart, state, options) { - const annotations = state.annotations || []; state.listened = false; state.moveListened = false; @@ -23,6 +21,8 @@ function updateListeners(chart, state, options) { if (typeof options[hook] === 'function') { state.listened = true; state.listeners[hook] = options[hook]; + } else if (defined$2(state.listeners[hook])) { + delete state.listeners[hook]; } }); moveHooks.forEach(hook => { @@ -32,7 +32,7 @@ function updateListeners(chart, state, options) { }); if (!state.listened || !state.moveListened) { - annotations.forEach(scope => { + state.annotations.forEach(scope => { if (!state.listened) { clickHooks.forEach(hook => { if (typeof scope[hook] === 'function') { @@ -52,21 +52,21 @@ function updateListeners(chart, state, options) { } } -function handleEvent(chart, state, event, options) { +function handleEvent(state, event, options) { if (state.listened) { switch (event.type) { case 'mousemove': case 'mouseout': - handleMoveEvents(chart, state, event); + handleMoveEvents(state, event); break; case 'click': - handleClickEvents(chart, state, event, options); + handleClickEvents(state, event, options); break; } } } -function handleMoveEvents(chart, state, event) { +function handleMoveEvents(state, event) { if (!state.moveListened) { return; } @@ -80,20 +80,20 @@ function handleMoveEvents(chart, state, event) { const previous = state.hovered; state.hovered = element; - dispatchMoveEvents(chart, state, {previous, element}, event); + dispatchMoveEvents(state, {previous, element}, event); } -function dispatchMoveEvents(chart, state, elements, event) { +function dispatchMoveEvents(state, elements, event) { const {previous, element} = elements; if (previous && previous !== element) { - dispatchEvent(chart, previous.options.leave || state.listeners.leave, previous, event); + dispatchEvent(previous.options.leave || state.listeners.leave, previous, event); } if (element && element !== previous) { - dispatchEvent(chart, element.options.enter || state.listeners.enter, element, event); + dispatchEvent(element.options.enter || state.listeners.enter, element, event); } } -function handleClickEvents(chart, state, event, options) { +function handleClickEvents(state, event, options) { const listeners = state.listeners; const element = getNearestItem(state.elements, event); if (element) { @@ -104,22 +104,22 @@ function handleClickEvents(chart, state, event, options) { // 2nd click before timeout, so its a double click clearTimeout(element.clickTimeout); delete element.clickTimeout; - dispatchEvent(chart, dblclick, element, event); + dispatchEvent(dblclick, element, event); } else if (dblclick) { // if there is a dblclick handler, wait for dblClickSpeed ms before deciding its a click element.clickTimeout = setTimeout(() => { delete element.clickTimeout; - dispatchEvent(chart, click, element, event); + dispatchEvent(click, element, event); }, options.dblClickSpeed); } else { // no double click handler, just call the click handler directly - dispatchEvent(chart, click, element, event); + dispatchEvent(click, element, event); } } } -function dispatchEvent(chart, handler, element, event) { - callHandler(handler, [{chart, element}, event]); +function dispatchEvent(handler, element, event) { + callback(handler, [element.$context, event]); } function getNearestItem(elements, position) { @@ -145,7 +145,71 @@ function getNearestItem(elements, position) { .slice(0, 1)[0]; // return only the top item } -const {isFinite: isFinite$1} = ChartJsV3.helpers; +const {isFinite: isFinite$1, valueOrDefault: valueOrDefault$2, defined: defined$1} = ChartJsV3.helpers; + +function adjustScaleRange(chart, scale, annotations) { + const range = getScaleLimits(scale, annotations); + let changed = changeScaleLimit(scale, range, 'min', 'suggestedMin'); + changed = changeScaleLimit(scale, range, 'max', 'suggestedMax') || changed; + if (changed && typeof scale.handleTickRangeOptions === 'function') { + scale.handleTickRangeOptions(); + } +} + +function verifyScaleOptions(annotations, scales) { + for (const annotation of annotations) { + verifyScaleIDs(annotation, scales); + } +} + +function changeScaleLimit(scale, range, limit, suggestedLimit) { + if (isFinite$1(range[limit]) && !scaleLimitDefined(scale.options, limit, suggestedLimit)) { + const changed = scale[limit] !== range[limit]; + scale[limit] = range[limit]; + return changed; + } +} + +function scaleLimitDefined(scaleOptions, limit, suggestedLimit) { + return defined$1(scaleOptions[limit]) || defined$1(scaleOptions[suggestedLimit]); +} + +function verifyScaleIDs(annotation, scales) { + for (const key of ['scaleID', 'xScaleID', 'yScaleID']) { + if (annotation[key] && !scales[annotation[key]]) { + console.warn(`No scale found with id '${annotation[key]}' for annotation '${annotation.id}'`); + } + } +} + +function getScaleLimits(scale, annotations) { + const axis = scale.axis; + const scaleID = scale.id; + const scaleIDOption = axis + 'ScaleID'; + const limits = { + min: valueOrDefault$2(scale.min, Number.NEGATIVE_INFINITY), + max: valueOrDefault$2(scale.max, Number.POSITIVE_INFINITY) + }; + for (const annotation of annotations) { + if (annotation.scaleID === scaleID) { + updateLimits(annotation, scale, ['value', 'endValue'], limits); + } else if (annotation[scaleIDOption] === scaleID) { + updateLimits(annotation, scale, [axis + 'Min', axis + 'Max', axis + 'Value'], limits); + } + } + return limits; +} + +function updateLimits(annotation, scale, props, limits) { + for (const prop of props) { + const raw = annotation[prop]; + if (defined$1(raw)) { + const value = scale.parse(raw); + limits.min = Math.min(limits.min, value); + limits.max = Math.max(limits.max, value); + } + } +} const clamp = (x, from, to) => Math.min(to, Math.max(from, x)); @@ -156,23 +220,249 @@ function clampAll(obj, from, to) { return obj; } -function scaleValue(scale, value, fallback) { - value = typeof value === 'number' ? value : scale.parse(value); - return isFinite$1(value) ? scale.getPixelForValue(value) : fallback; +function inPointRange(point, center, radius, borderWidth) { + if (!point || !center || radius <= 0) { + return false; + } + const hBorderWidth = borderWidth / 2 || 0; + return (Math.pow(point.x - center.x, 2) + Math.pow(point.y - center.y, 2)) <= Math.pow(radius + hBorderWidth, 2); +} + +function inBoxRange(mouseX, mouseY, {x, y, width, height}, borderWidth) { + const hBorderWidth = borderWidth / 2 || 0; + return mouseX >= x - hBorderWidth && + mouseX <= x + width + hBorderWidth && + mouseY >= y - hBorderWidth && + mouseY <= y + height + hBorderWidth; +} + +function getElementCenterPoint(element, useFinalPosition) { + const {x, y} = element.getProps(['x', 'y'], useFinalPosition); + return {x, y}; +} + +const isOlderPart = (act, req) => req > act || (act.length > req.length && act.substr(0, req.length) === req); + +function requireVersion(pkg, min, ver, strict = true) { + const parts = ver.split('.'); + let i = 0; + for (const req of min.split('.')) { + const act = parts[i++]; + if (parseInt(req, 10) < parseInt(act, 10)) { + break; + } + if (isOlderPart(act, req)) { + if (strict) { + throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`); + } else { + return false; + } + } + } + return true; +} + +const {isObject: isObject$1, valueOrDefault: valueOrDefault$1, defined} = ChartJsV3.helpers; + +const isPercentString = (s) => typeof s === 'string' && s.endsWith('%'); +const toPercent = (s) => clamp(parseFloat(s) / 100, 0, 1); + +function getRelativePosition(size, positionOption) { + if (positionOption === 'start') { + return 0; + } + if (positionOption === 'end') { + return size; + } + if (isPercentString(positionOption)) { + return toPercent(positionOption) * size; + } + return size / 2; +} + +function getSize(size, value) { + if (typeof value === 'number') { + return value; + } else if (isPercentString(value)) { + return toPercent(value) * size; + } + return size; +} + +function calculateTextAlignment(size, options) { + const {x, width} = size; + const textAlign = options.textAlign; + if (textAlign === 'center') { + return x + width / 2; + } else if (textAlign === 'end' || textAlign === 'right') { + return x + width; + } + return x; +} + +function toPosition(value) { + if (isObject$1(value)) { + return { + x: valueOrDefault$1(value.x, 'center'), + y: valueOrDefault$1(value.y, 'center'), + }; + } + value = valueOrDefault$1(value, 'center'); + return { + x: value, + y: value + }; +} + +function isBoundToPoint(options) { + return options && (defined(options.xValue) || defined(options.yValue)); +} + +const {addRoundedRectPath, isArray: isArray$1, toFont, toTRBLCorners, valueOrDefault} = ChartJsV3.helpers; + +const widthCache = new Map(); + +function isImageOrCanvas(content) { + return content instanceof Image$1 || content instanceof Image || content instanceof HTMLCanvasElement; +} + +/** + * Apply border options to the canvas context before drawing a shape + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options with border configuration + * @returns {boolean} true is the border options have been applied + */ +function setBorderStyle(ctx, options) { + if (options && options.borderWidth) { + ctx.lineCap = options.borderCapStyle; + ctx.setLineDash(options.borderDash); + ctx.lineDashOffset = options.borderDashOffset; + ctx.lineJoin = options.borderJoinStyle; + ctx.lineWidth = options.borderWidth; + ctx.strokeStyle = options.borderColor; + return true; + } +} + +/** + * Apply shadow options to the canvas context before drawing a shape + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options with shadow configuration + */ +function setShadowStyle(ctx, options) { + ctx.shadowColor = options.backgroundShadowColor; + ctx.shadowBlur = options.shadowBlur; + ctx.shadowOffsetX = options.shadowOffsetX; + ctx.shadowOffsetY = options.shadowOffsetY; +} + +/** + * Measure the label size using the label options. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options to configure the label + * @returns {{width: number, height: number}} the measured size of the label + */ +function measureLabelSize(ctx, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + return { + width: getSize(content.width, options.width), + height: getSize(content.height, options.height) + }; + } + const font = toFont(options.font); + const lines = isArray$1(content) ? content : [content]; + 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); +} + +/** + * Draw a box with the size and the styling options. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {{x: number, y: number, width: number, height: number}} rect - rect to draw + * @param {Object} options - options to style the box + * @returns {undefined} + */ +function drawBox(ctx, rect, options) { + const {x, y, width, height} = rect; + ctx.save(); + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + ctx.fillStyle = options.backgroundColor; + ctx.beginPath(); + addRoundedRectPath(ctx, { + x, y, w: width, h: height, + // TODO: v2 remove support for cornerRadius + radius: clampAll(toTRBLCorners(valueOrDefault(options.cornerRadius, options.borderRadius)), 0, Math.min(width, height) / 2) + }); + ctx.closePath(); + ctx.fill(); + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); +} + +function drawLabel(ctx, rect, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + ctx.drawImage(content, rect.x, rect.y, rect.width, rect.height); + return; + } + const labels = isArray$1(content) ? content : [content]; + const font = toFont(options.font); + const lh = font.lineHeight; + const x = calculateTextAlignment(rect, options); + const y = rect.y + (lh / 2); + ctx.font = font.string; + ctx.textBaseline = 'middle'; + ctx.textAlign = options.textAlign; + ctx.fillStyle = options.color; + labels.forEach((l, i) => ctx.fillText(l, x, y + (i * lh))); +} + +/** + * @typedef {import('chart.js').Point} Point + */ + +/** + * @param {{x: number, y: number, width: number, height: number}} rect + * @returns {Point} + */ +function getRectCenterPoint(rect) { + const {x, y, width, height} = rect; + return { + x: x + width / 2, + y: y + height / 2 + }; } /** * Rotate a `point` relative to `center` point by `angle` - * @param {{x: number, y: number}} point - the point to rotate - * @param {{x: number, y: number}} center - center point for rotation + * @param {Point} point - the point to rotate + * @param {Point} center - center point for rotation * @param {number} angle - angle for rotation, in radians - * @returns {{x: number, y: number}} rotated point + * @returns {Point} rotated point */ function rotated(point, center, angle) { - var cos = Math.cos(angle); - var sin = Math.sin(angle); - var cx = center.x; - var cy = center.y; + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const cx = center.x; + const cy = center.y; return { x: cx + cos * (point.x - cx) - sin * (point.y - cy), @@ -180,105 +470,227 @@ function rotated(point, center, angle) { }; } -const {addRoundedRectPath: addRoundedRectPath$1, toTRBLCorners: toTRBLCorners$1, valueOrDefault: valueOrDefault$2} = ChartJsV3.helpers; +const {isFinite} = ChartJsV3.helpers; -class BoxAnnotation extends Element { - inRange(mouseX, mouseY, useFinalPosition) { - const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition); +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").Scale } Scale + * @typedef { import("chart.js").Point } Point + * @typedef { import('../../types/options').CoreAnnotationOptions } CoreAnnotationOptions + * @typedef { import('../../types/options').PointAnnotationOptions } PointAnnotationOptions + */ - return mouseX >= x && - mouseX <= x + width && - mouseY >= y && - mouseY <= y + height; - } +/** + * @param {Scale} scale + * @param {number|string} value + * @param {number} fallback + * @returns {number} + */ +function scaleValue(scale, value, fallback) { + value = typeof value === 'number' ? value : scale.parse(value); + return isFinite(value) ? scale.getPixelForValue(value) : fallback; +} - getCenterPoint(useFinalPosition) { - const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition); +/** + * @param {Scale} scale + * @param {{start: number, end: number}} options + * @returns {{start: number, end: number}} + */ +function getChartDimensionByScale(scale, options) { + if (scale) { + const min = scaleValue(scale, options.min, options.start); + const max = scaleValue(scale, options.max, options.end); return { - x: x + width / 2, - y: y + height / 2 + start: Math.min(min, max), + end: Math.max(min, max) }; } + return { + start: options.start, + end: options.end + }; +} - draw(ctx) { - const {x, y, width, height, options} = this; +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {Point} + */ +function getChartPoint(chart, options) { + const {chartArea, scales} = chart; + const xScale = scales[options.xScaleID]; + const yScale = scales[options.yScaleID]; + let x = chartArea.width / 2; + let y = chartArea.height / 2; + + if (xScale) { + x = scaleValue(xScale, options.xValue, x); + } - ctx.save(); + if (yScale) { + y = scaleValue(yScale, options.yValue, y); + } + return {x, y}; +} - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.fillStyle = options.backgroundColor; +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {{x?:number, y?: number, x2?: number, y2?: number, width?: number, height?: number}} + */ +function getChartRect(chart, options) { + const xScale = chart.scales[options.xScaleID]; + const yScale = chart.scales[options.yScaleID]; + let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; + if (!xScale && !yScale) { + return {}; + } - ctx.beginPath(); - addRoundedRectPath$1(ctx, { - x, y, w: width, h: height, - // TODO: v2 remove support for cornerRadius - radius: clampAll(toTRBLCorners$1(valueOrDefault$2(options.cornerRadius, options.borderRadius)), 0, Math.min(width, height) / 2) - }); - ctx.closePath(); - ctx.fill(); + const xDim = getChartDimensionByScale(xScale, {min: options.xMin, max: options.xMax, start: x, end: x2}); + x = xDim.start; + x2 = xDim.end; + const yDim = getChartDimensionByScale(yScale, {min: options.yMin, max: options.yMax, start: y, end: y2}); + y = yDim.start; + y2 = yDim.end; - // If no border, don't draw it - if (options.borderWidth) { - ctx.stroke(); - } + return { + x, + y, + x2, + y2, + width: x2 - x, + height: y2 - y + }; +} - ctx.restore(); +/** + * @param {Chart} chart + * @param {PointAnnotationOptions} options + */ +function getChartCircle(chart, options) { + const point = getChartPoint(chart, options); + return { + x: point.x + options.xAdjust, + y: point.y + options.yAdjust, + width: options.radius * 2, + height: options.radius * 2 + }; +} + +/** + * @param {Chart} chart + * @param {PointAnnotationOptions} options + * @returns + */ +function resolvePointPosition(chart, options) { + if (!isBoundToPoint(options)) { + const box = getChartRect(chart, options); + const point = getRectCenterPoint(box); + let radius = options.radius; + if (!radius || isNaN(radius)) { + radius = Math.min(box.width, box.height) / 2; + options.radius = radius; + } + return { + x: point.x + options.xAdjust, + y: point.y + options.yAdjust, + width: radius * 2, + height: radius * 2 + }; } + return getChartCircle(chart, options); +} - resolveElementProperties(chart, options) { - const xScale = chart.scales[options.xScaleID]; - const yScale = chart.scales[options.yScaleID]; - let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea; - let min, max; +const {toPadding: toPadding$2} = ChartJsV3.helpers; - if (!xScale && !yScale) { - return {options: {}}; - } +class BoxAnnotation extends Element { + inRange(mouseX, mouseY, useFinalPosition) { + return inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition), this.options.borderWidth); + } - if (xScale) { - min = scaleValue(xScale, options.xMin, x); - max = scaleValue(xScale, options.xMax, x2); - x = Math.min(min, max); - x2 = Math.max(min, max); - } + getCenterPoint(useFinalPosition) { + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); + } - if (yScale) { - min = scaleValue(yScale, options.yMin, y2); - max = scaleValue(yScale, options.yMax, y); - y = Math.min(min, max); - y2 = Math.max(min, max); - } + draw(ctx) { + ctx.save(); + drawBox(ctx, this, this.options); + ctx.restore(); + } - return { - x, - y, - x2, - y2, - width: x2 - x, - height: y2 - y + drawLabel(ctx) { + const {x, y, width, height, options} = this; + const {label, borderWidth} = options; + const halfBorder = borderWidth / 2; + const position = toPosition(label.position); + const padding = toPadding$2(label.padding); + const labelSize = measureLabelSize(ctx, label); + const labelRect = { + x: calculateX(this, labelSize, position, padding), + y: calculateY(this, labelSize, position, padding), + width: labelSize.width, + height: labelSize.height }; + + ctx.save(); + ctx.beginPath(); + ctx.rect(x + halfBorder + padding.left, y + halfBorder + padding.top, + width - borderWidth - padding.width, height - borderWidth - padding.height); + ctx.clip(); + drawLabel(ctx, labelRect, label); + ctx.restore(); + } + + resolveElementProperties(chart, options) { + return getChartRect(chart, options); } } BoxAnnotation.id = 'boxAnnotation'; BoxAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0, - borderWidth: 1, + borderJoinStyle: 'miter', borderRadius: 0, - xScaleID: 'x', - xMin: undefined, + borderShadowColor: 'transparent', + borderWidth: 1, + cornerRadius: undefined, // TODO: v2 remove support for cornerRadius + display: true, + label: { + borderWidth: undefined, + color: 'black', + content: null, + drawTime: undefined, + enabled: false, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: 'bold' + }, + height: undefined, + padding: 6, + position: 'center', + textAlign: 'start', + xAdjust: 0, + yAdjust: 0, + width: undefined + }, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' }; BoxAnnotation.defaultRoutes = { @@ -286,13 +698,47 @@ BoxAnnotation.defaultRoutes = { backgroundColor: 'color' }; -const {addRoundedRectPath, isArray: isArray$1, toFontString, toRadians: toRadians$1, toTRBLCorners, valueOrDefault: valueOrDefault$1} = ChartJsV3.helpers; +BoxAnnotation.descriptors = { + label: { + _fallback: true + } +}; + +function calculateX(box, labelSize, position, padding) { + const {x: start, x2: end, width: size, options} = box; + const {xAdjust: adjust, borderWidth} = options.label; + return calculatePosition$1({start, end, size}, { + position: position.x, + padding: {start: padding.left, end: padding.right}, + adjust, borderWidth, + size: labelSize.width + }); +} + +function calculateY(box, labelSize, position, padding) { + const {y: start, y2: end, height: size, options} = box; + const {yAdjust: adjust, borderWidth} = options.label; + return calculatePosition$1({start, end, size}, { + position: position.y, + padding: {start: padding.top, end: padding.bottom}, + adjust, borderWidth, + size: labelSize.height + }); +} + +function calculatePosition$1(boxOpts, labelOpts) { + const {start, end} = boxOpts; + const {position, padding: {start: padStart, end: padEnd}, adjust, borderWidth} = labelOpts; + const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; + return start + borderWidth / 2 + adjust + padStart + getRelativePosition(availableSize, position); +} + +const {PI: PI$2, toRadians: toRadians$1, toPadding: toPadding$1} = ChartJsV3.helpers; -const PI = Math.PI; 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; const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p2.x - p1.x))).y; -const toPercent = (s) => typeof s === 'string' && s.endsWith('%') && parseFloat(s) / 100; +const sqr = v => v * v; function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) { return !( @@ -330,10 +776,11 @@ function limitLineToArea(p1, p2, area) { } class LineAnnotation extends Element { - intersects(x, y, epsilon = 0.001) { + + // TODO: make private in v2 + intersects(x, y, epsilon = 0.001, useFinalPosition) { // Adapted from https://stackoverflow.com/a/6853926/25507 - const sqr = v => v * v; - const {x: x1, y: y1, x2, y2} = this; + const {x: x1, y: y1, x2, y2} = this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); const dx = x2 - x1; const dy = y2 - y1; const lenSq = sqr(dx) + sqr(dy); @@ -352,29 +799,37 @@ class LineAnnotation extends Element { return (sqr(x - xx) + sqr(y - yy)) < epsilon; } - labelIsVisible(chartArea) { - const label = this.options.label; - - const inside = !chartArea || isLineInArea(this, chartArea); - return inside && label && label.enabled && label.content; + /** + * @todo make private in v2 + * @param {boolean} useFinalPosition - use the element's animation target instead of current position + * @param {top, right, bottom, left} [chartArea] - optional, area of the chart + * @returns {boolean} true if the label is visible + */ + labelIsVisible(useFinalPosition, chartArea) { + const labelOpts = this.options.label; + if (!labelOpts || !labelOpts.enabled) { + return false; + } + return !chartArea || isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea); } - isOnLabel(mouseX, mouseY) { - const {labelRect} = this; - if (!labelRect || !this.labelIsVisible()) { + // TODO: make private in v2 + isOnLabel(mouseX, mouseY, useFinalPosition) { + if (!this.labelIsVisible(useFinalPosition)) { return false; } - - const {x, y} = rotated({x: mouseX, y: mouseY}, labelRect, -labelRect.rotation); - const w2 = labelRect.width / 2; - const h2 = labelRect.height / 2; - return x >= labelRect.x - w2 && x <= labelRect.x + w2 && - y >= labelRect.y - h2 && y <= labelRect.y + h2; + const {labelX, labelY, labelWidth, labelHeight, labelRotation} = this.getProps(['labelX', 'labelY', 'labelWidth', 'labelHeight', 'labelRotation'], useFinalPosition); + const {x, y} = rotated({x: mouseX, y: mouseY}, {x: labelX, y: labelY}, -labelRotation); + const hBorderWidth = this.options.label.borderWidth / 2 || 0; + const w2 = labelWidth / 2 + hBorderWidth; + const h2 = labelHeight / 2 + hBorderWidth; + return x >= labelX - w2 && x <= labelX + w2 && + y >= labelY - h2 && y <= labelY + h2; } - inRange(x, y) { - const epsilon = this.options.borderWidth || 1; - return this.intersects(x, y, epsilon) || this.isOnLabel(x, y); + inRange(mouseX, mouseY, useFinalPosition) { + const epsilon = sqr(this.options.borderWidth / 2); + return this.intersects(mouseX, mouseY, epsilon, useFinalPosition) || this.isOnLabel(mouseX, mouseY, useFinalPosition); } getCenterPoint() { @@ -386,28 +841,55 @@ class LineAnnotation extends Element { draw(ctx) { const {x, y, x2, y2, options} = this; - ctx.save(); - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; + ctx.save(); + if (!setBorderStyle(ctx, options)) { + // no border width, then line is not drawn + return ctx.restore(); + } + setShadowStyle(ctx, options); + const angle = Math.atan2(y2 - y, x2 - x); + const length = Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)); + const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(this); - // Draw + ctx.translate(x, y); + ctx.rotate(angle); ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x2, y2); + ctx.moveTo(0 + startAdjust, 0); + ctx.lineTo(length - endAdjust, 0); + ctx.shadowColor = options.borderShadowColor; ctx.stroke(); - + drawArrowHead(ctx, 0, startAdjust, startOpts); + drawArrowHead(ctx, length, -endAdjust, endOpts); ctx.restore(); } drawLabel(ctx, chartArea) { - if (this.labelIsVisible(chartArea)) { - ctx.save(); - drawLabel(ctx, this, chartArea); - ctx.restore(); + if (!this.labelIsVisible(false, chartArea)) { + return; } + const {labelX, labelY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options: {label}} = this; + + ctx.save(); + ctx.translate(labelX, labelY); + ctx.rotate(labelRotation); + + const boxRect = { + x: -(labelWidth / 2), + y: -(labelHeight / 2), + width: labelWidth, + height: labelHeight + }; + drawBox(ctx, boxRect, label); + + const labelTextRect = { + x: -(labelWidth / 2) + labelPadding.left + label.borderWidth / 2, + y: -(labelHeight / 2) + labelPadding.top + label.borderWidth / 2, + width: labelTextSize.width, + height: labelTextSize.height + }; + drawLabel(ctx, labelTextRect, label); + ctx.restore(); } resolveElementProperties(chart, options) { @@ -440,189 +922,164 @@ class LineAnnotation extends Element { } } const inside = isLineInArea({x, y, x2, y2}, chart.chartArea); - return inside + const properties = inside ? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea) : {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; + + const label = options.label; + if (label && label.content) { + return loadLabelRect(properties, chart, label); + } + return properties; } } LineAnnotation.id = 'lineAnnotation'; + +const arrowHeadsDefaults = { + backgroundColor: undefined, + backgroundShadowColor: undefined, + borderColor: undefined, + borderDash: undefined, + borderDashOffset: undefined, + borderShadowColor: undefined, + borderWidth: undefined, + enabled: undefined, + fill: undefined, + length: undefined, + shadowBlur: undefined, + shadowOffsetX: undefined, + shadowOffsetY: undefined, + width: undefined +}; + LineAnnotation.defaults = { - display: true, adjustScaleRange: true, - borderWidth: 2, + arrowHeads: { + enabled: false, + end: Object.assign({}, arrowHeadsDefaults), + fill: false, + length: 12, + start: Object.assign({}, arrowHeadsDefaults), + width: 6 + }, borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 2, + display: true, + endValue: undefined, label: { backgroundColor: 'rgba(0,0,0,0.8)', + backgroundShadowColor: 'transparent', borderCapStyle: 'butt', borderColor: 'black', borderDash: [], borderDashOffset: 0, borderJoinStyle: 'miter', borderRadius: 6, + borderShadowColor: 'transparent', borderWidth: 0, + color: '#fff', + content: null, + cornerRadius: undefined, // TODO: v2 remove support for cornerRadius drawTime: undefined, + enabled: false, font: { family: undefined, lineHeight: undefined, size: undefined, - style: 'bold', - weight: undefined + style: undefined, + weight: 'bold' }, - color: '#fff', - xPadding: 6, - yPadding: 6, - rotation: 0, + height: undefined, + padding: 6, position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + width: undefined, xAdjust: 0, + xPadding: undefined, // TODO: v2 remove support for xPadding yAdjust: 0, - textAlign: 'center', - enabled: false, - content: null + yPadding: undefined, // TODO: v2 remove support for yPadding }, - value: undefined, - endValue: undefined, scaleID: undefined, - xScaleID: 'x', - xMin: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + value: undefined, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' +}; + +LineAnnotation.descriptors = { + arrowHeads: { + start: { + _fallback: true + }, + end: { + _fallback: true + }, + _fallback: true + } }; LineAnnotation.defaultRoutes = { borderColor: 'color' }; +function loadLabelRect(line, chart, options) { + // TODO: v2 remove support for xPadding and yPadding + const {padding: lblPadding, xPadding, yPadding, borderWidth} = options; + const padding = getPadding(lblPadding, xPadding, yPadding); + const textSize = measureLabelSize(chart.ctx, options); + const width = textSize.width + padding.width + borderWidth; + const height = textSize.height + padding.height + borderWidth; + const labelRect = calculateLabelPosition(line, options, {width, height, padding}, chart.chartArea); + line.labelX = labelRect.x; + line.labelY = labelRect.y; + line.labelWidth = labelRect.width; + line.labelHeight = labelRect.height; + line.labelRotation = labelRect.rotation; + line.labelPadding = padding; + line.labelTextSize = textSize; + return line; +} + function calculateAutoRotation(line) { const {x, y, x2, y2} = line; const rotation = Math.atan2(y2 - y, x2 - x); // Flip the rotation if it goes > PI/2 or < -PI/2, so label stays upright - return rotation > PI / 2 ? rotation - PI : rotation < PI / -2 ? rotation + PI : rotation; + return rotation > PI$2 / 2 ? rotation - PI$2 : rotation < PI$2 / -2 ? rotation + PI$2 : rotation; } -function drawLabel(ctx, line, chartArea) { - const label = line.options.label; - - ctx.font = toFontString(label.font); - - const {width, height} = measureLabel(ctx, label); - const rect = line.labelRect = calculateLabelPosition(line, width, height, chartArea); - - ctx.translate(rect.x, rect.y); - ctx.rotate(rect.rotation); - - ctx.fillStyle = label.backgroundColor; - const stroke = setBorderStyle(ctx, label); - - ctx.beginPath(); - addRoundedRectPath(ctx, { - x: -(width / 2), y: -(height / 2), w: width, h: height, - // TODO: v2 remove support for cornerRadius - radius: clampAll(toTRBLCorners(valueOrDefault$1(label.cornerRadius, label.borderRadius)), 0, Math.min(width, height) / 2) - }); - ctx.closePath(); - ctx.fill(); - if (stroke) { - ctx.stroke(); - } - - ctx.fillStyle = label.color; - if (isArray$1(label.content)) { - ctx.textAlign = label.textAlign; - const x = calculateLabelXAlignment(label, width); - let textYPosition = -(height / 2) + label.yPadding; - for (let i = 0; i < label.content.length; i++) { - ctx.textBaseline = 'top'; - ctx.fillText( - label.content[i], - x, - textYPosition - ); - textYPosition += label.font.size + label.yPadding; - } - } else if (label.content instanceof Image) { - const x = -(width / 2) + label.xPadding; - const y = -(height / 2) + label.yPadding; - ctx.drawImage(label.content, x, y, width - (2 * label.xPadding), height - (2 * label.yPadding)); - } else { - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(label.content, 0, 0); +// TODO: v2 remove support for xPadding and yPadding +function getPadding(padding, xPadding, yPadding) { + let tempPadding = padding; + if (xPadding || yPadding) { + tempPadding = {x: xPadding || 6, y: yPadding || 6}; } + return toPadding$1(tempPadding); } -function setBorderStyle(ctx, options) { - if (options.borderWidth) { - ctx.lineCap = options.borderCapStyle; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; - ctx.lineJoin = options.borderJoinStyle; - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - return true; - } -} - -function calculateLabelXAlignment(label, width) { - const {textAlign, xPadding} = label; - if (textAlign === 'start') { - return -(width / 2) + xPadding; - } else if (textAlign === 'end') { - return +(width / 2) - xPadding; - } - return 0; -} - -function getImageSize(size, value) { - if (typeof value === 'number') { - return value; - } else if (typeof value === 'string') { - return toPercent(value) * size; - } - return size; -} - -const widthCache = new Map(); -function measureLabel(ctx, label) { - const content = label.content; - if (content instanceof Image) { - return { - width: getImageSize(content.width, label.width) + 2 * label.xPadding, - height: getImageSize(content.height, label.height) + 2 * label.yPadding - }; - } - const lines = isArray$1(content) ? content : [content]; - const count = lines.length; - let width = 0; - for (let i = 0; i < count; i++) { - const text = lines[i]; - if (!widthCache.has(text)) { - widthCache.set(text, ctx.measureText(text).width); - } - width = Math.max(width, widthCache.get(text)); - } - width += 2 * label.xPadding; - - return { - width, - height: count * label.font.size + ((count + 1) * label.yPadding) - }; -} - -function calculateLabelPosition(line, width, height, chartArea) { - const label = line.options.label; - const {xAdjust, yAdjust, xPadding, yPadding, position} = label; +function calculateLabelPosition(line, label, sizes, chartArea) { + const {width, height, padding} = sizes; + const {xAdjust, yAdjust} = label; const p1 = {x: line.x, y: line.y}; const p2 = {x: line.x2, y: line.y2}; const rotation = label.rotation === 'auto' ? calculateAutoRotation(line) : toRadians$1(label.rotation); const size = rotatedSize(width, height, rotation); - const t = calculateT(line, position, size, chartArea); + const t = calculateT(line, label, {labelSize: size, padding}, chartArea); const pt = pointInLine(p1, p2, t); - const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: xPadding}; - const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: yPadding}; + const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: padding.left}; + const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: padding.top}; return { x: adjustLabelCoordinate(pt.x, xCoordinateSizes) + xAdjust, @@ -642,24 +1099,25 @@ function rotatedSize(width, height, rotation) { }; } -function calculateT(line, position, rotSize, chartArea) { - let t = 0.5; +function calculateT(line, label, sizes, chartArea) { + let t; const space = spaceAround(line, chartArea); - const label = line.options.label; - if (position === 'start') { - t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, rotSize, label, space); - } else if (position === 'end') { - t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, rotSize, label, space); + if (label.position === 'start') { + t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, sizes, label, space); + } else if (label.position === 'end') { + t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, sizes, label, space); + } else { + t = getRelativePosition(1, label.position); } return t; } -function calculateTAdjust(lineSize, labelSize, label, space) { - const {xPadding, yPadding} = label; +function calculateTAdjust(lineSize, sizes, label, space) { + const {labelSize, padding} = sizes; const lineW = lineSize.w * space.dx; const lineH = lineSize.h * space.dy; - const x = (lineW > 0) && ((labelSize.w / 2 + xPadding - space.x) / lineW); - const y = (lineH > 0) && ((labelSize.h / 2 + yPadding - space.y) / lineH); + const x = (lineW > 0) && ((labelSize.w / 2 + padding.left - space.x) / lineW); + const y = (lineH > 0) && ((labelSize.h / 2 + padding.top - space.y) / lineH); return clamp(Math.max(x, y), 0, 0.25); } @@ -672,37 +1130,83 @@ function spaceAround(line, chartArea) { return { x: Math.min(l, r), y: Math.min(t, b), - dx: l < r ? 1 : -1, - dy: t < b ? 1 : -1 + dx: l <= r ? 1 : -1, + dy: t <= b ? 1 : -1 }; } function adjustLabelCoordinate(coordinate, labelSizes) { const {size, min, max, padding} = labelSizes; const halfSize = size / 2; - if (size > max - min) { // if it does not fit, display as much as possible return (max + min) / 2; } - if (min >= (coordinate - padding - halfSize)) { coordinate = min + padding + halfSize; } - if (max <= (coordinate + padding + halfSize)) { coordinate = max - padding - halfSize; } + return coordinate; +} + +function getArrowHeads(line) { + const options = line.options; + const arrowStartOpts = options.arrowHeads && options.arrowHeads.start; + const arrowEndOpts = options.arrowHeads && options.arrowHeads.end; + return { + startOpts: arrowStartOpts, + endOpts: arrowEndOpts, + startAdjust: getLineAdjust(line, arrowStartOpts), + endAdjust: getLineAdjust(line, arrowEndOpts) + }; +} + +function getLineAdjust(line, arrowOpts) { + if (!arrowOpts || !arrowOpts.enabled) { + return 0; + } + const {length, width} = arrowOpts; + const adjust = line.options.borderWidth / 2; + const p1 = {x: length, y: width + adjust}; + const p2 = {x: 0, y: adjust}; + return Math.abs(interpolateX(0, p1, p2)); +} - return coordinate; +function drawArrowHead(ctx, offset, adjust, arrowOpts) { + if (!arrowOpts || !arrowOpts.enabled) { + return; + } + const {length, width, fill, backgroundColor, borderColor} = arrowOpts; + const arrowOffsetX = Math.abs(offset - length) + adjust; + ctx.beginPath(); + setShadowStyle(ctx, arrowOpts); + setBorderStyle(ctx, arrowOpts); + ctx.moveTo(arrowOffsetX, -width); + ctx.lineTo(offset + adjust, 0); + ctx.lineTo(arrowOffsetX, width); + if (fill === true) { + ctx.fillStyle = backgroundColor || borderColor; + ctx.closePath(); + ctx.fill(); + ctx.shadowColor = 'transparent'; + } else { + ctx.shadowColor = arrowOpts.borderShadowColor; + } + ctx.stroke(); } -const {toRadians} = ChartJsV3.helpers; +const {PI: PI$1, toRadians} = ChartJsV3.helpers; -class EllipseAnnotation extends BoxAnnotation { +class EllipseAnnotation extends Element { - inRange(x, y) { - return pointInEllipse({x, y}, this); + inRange(mouseX, mouseY, useFinalPosition) { + return pointInEllipse({x: mouseX, y: mouseY}, this.getProps(['width', 'height'], useFinalPosition), this.options.rotation, this.options.borderWidth); + } + + getCenterPoint(useFinalPosition) { + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); } draw(ctx) { @@ -715,40 +1219,45 @@ class EllipseAnnotation extends BoxAnnotation { if (options.rotation) { ctx.rotate(toRadians(options.rotation)); } - + setShadowStyle(ctx, this.options); ctx.beginPath(); - - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; ctx.fillStyle = options.backgroundColor; - - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; - - ctx.ellipse(0, 0, height / 2, width / 2, Math.PI / 2, 0, 2 * Math.PI); - + const stroke = setBorderStyle(ctx, options); + ctx.ellipse(0, 0, height / 2, width / 2, PI$1 / 2, 0, 2 * PI$1); ctx.fill(); - ctx.stroke(); - + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } ctx.restore(); } + + resolveElementProperties(chart, options) { + return getChartRect(chart, options); + } + } EllipseAnnotation.id = 'ellipseAnnotation'; EllipseAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', borderWidth: 1, + display: true, rotation: 0, - xScaleID: 'x', - xMin: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' }; EllipseAnnotation.defaultRoutes = { @@ -756,7 +1265,7 @@ EllipseAnnotation.defaultRoutes = { backgroundColor: 'color' }; -function pointInEllipse(p, ellipse) { +function pointInEllipse(p, ellipse, rotation, borderWidth) { const {width, height} = ellipse; const center = ellipse.getCenterPoint(true); const xRadius = width / 2; @@ -765,84 +1274,302 @@ function pointInEllipse(p, ellipse) { if (xRadius <= 0 || yRadius <= 0) { return false; } - - return (Math.pow(p.x - center.x, 2) / Math.pow(xRadius, 2)) + (Math.pow(p.y - center.y, 2) / Math.pow(yRadius, 2)) <= 1.0; + // https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm + const angle = toRadians(rotation || 0); + const hBorderWidth = borderWidth / 2 || 0; + const cosAngle = Math.cos(angle); + const sinAngle = Math.sin(angle); + const a = Math.pow(cosAngle * (p.x - center.x) + sinAngle * (p.y - center.y), 2); + const b = Math.pow(sinAngle * (p.x - center.x) - cosAngle * (p.y - center.y), 2); + return (a / Math.pow(xRadius + hBorderWidth, 2)) + (b / Math.pow(yRadius + hBorderWidth, 2)) <= 1.0001; } -class PointAnnotation extends Element { - - inRange(x, y) { - const {width, options} = this; - const center = this.getCenterPoint(true); - const radius = width / 2 + options.borderWidth; +const {color, toPadding} = ChartJsV3.helpers; - if (radius <= 0) { - return false; - } +class LabelAnnotation extends Element { - return (Math.pow(x - center.x, 2) + Math.pow(y - center.y, 2)) <= Math.pow(radius, 2); + inRange(mouseX, mouseY, useFinalPosition) { + return inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition), this.options.borderWidth); } getCenterPoint(useFinalPosition) { - const {x, y} = this.getProps(['x', 'y'], useFinalPosition); - return {x, y}; + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); } draw(ctx) { - const {x, y, width, options} = this; + if (!this.options.content) { + return; + } + const {labelX, labelY, labelWidth, labelHeight, options} = this; + drawCallout(ctx, this); + if (this.boxVisible) { + drawBox(ctx, this, options); + } + drawLabel(ctx, {x: labelX, y: labelY, width: labelWidth, height: labelHeight}, options); + } - ctx.save(); + // TODO: make private in v2 + resolveElementProperties(chart, options) { + const point = !isBoundToPoint(options) ? getRectCenterPoint(getChartRect(chart, options)) : getChartPoint(chart, options); + const padding = toPadding(options.padding); + const labelSize = measureLabelSize(chart.ctx, options); + const boxSize = measureRect(point, labelSize, options, padding); + const bgColor = color(options.backgroundColor); + const boxVisible = options.borderWidth > 0 || (bgColor && bgColor.valid && bgColor.rgb.a > 0); + + const properties = { + boxVisible, + pointX: point.x, + pointY: point.y, + ...boxSize, + labelX: boxSize.x + padding.left + (options.borderWidth / 2), + labelY: boxSize.y + padding.top + (options.borderWidth / 2), + labelWidth: labelSize.width, + labelHeight: labelSize.height + }; + properties.calloutPosition = options.callout.enabled && resolveCalloutPosition(properties, options.callout); + return properties; + } +} - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.fillStyle = options.backgroundColor; +LabelAnnotation.id = 'labelAnnotation'; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; +LabelAnnotation.defaults = { + adjustScaleRange: true, + backgroundColor: 'transparent', + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 0, + borderShadowColor: 'transparent', + borderWidth: 0, + callout: { + borderCapStyle: 'butt', + borderColor: undefined, + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 1, + enabled: false, + margin: 5, + position: 'auto', + side: 5, + start: '50%', + }, + color: 'black', + content: null, + display: true, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: undefined + }, + height: undefined, + padding: 6, + position: 'center', + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + width: undefined, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: 'x', + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: 'y', + yValue: undefined +}; - ctx.beginPath(); - ctx.arc(x, y, width / 2, 0, Math.PI * 2); - ctx.fill(); - ctx.stroke(); +LabelAnnotation.defaultRoutes = { + borderColor: 'color' +}; - ctx.restore(); +function measureRect(point, size, options, padding) { + const width = size.width + padding.width + options.borderWidth; + const height = size.height + padding.height + options.borderWidth; + const position = toPosition(options.position); + + return { + x: calculatePosition(point.x, width, options.xAdjust, position.x), + y: calculatePosition(point.y, height, options.yAdjust, position.y), + width, + height + }; +} + +function calculatePosition(start, size, adjust = 0, position) { + return start - getRelativePosition(size, position) + adjust; +} + +function drawCallout(ctx, element) { + const {pointX, pointY, calloutPosition, options} = element; + if (!calloutPosition) { + return; } + const callout = options.callout; - resolveElementProperties(chart, options) { - const {chartArea, scales} = chart; - const xScale = scales[options.xScaleID]; - const yScale = scales[options.yScaleID]; - let x = chartArea.width / 2; - let y = chartArea.height / 2; - - if (xScale) { - x = scaleValue(xScale, options.xValue, x); - } + ctx.save(); + ctx.beginPath(); + const stroke = setBorderStyle(ctx, callout); + if (!stroke) { + return ctx.restore(); + } + const {separatorStart, separatorEnd} = getCalloutSeparatorCoord(element, calloutPosition); + const {sideStart, sideEnd} = getCalloutSideCoord(element, calloutPosition, separatorStart); + if (callout.margin > 0 || options.borderWidth === 0) { + ctx.moveTo(separatorStart.x, separatorStart.y); + ctx.lineTo(separatorEnd.x, separatorEnd.y); + } + ctx.moveTo(sideStart.x, sideStart.y); + ctx.lineTo(sideEnd.x, sideEnd.y); + ctx.lineTo(pointX, pointY); + ctx.stroke(); + ctx.restore(); +} - if (yScale) { - y = scaleValue(yScale, options.yValue, y); +function getCalloutSeparatorCoord(element, position) { + const {x, y, width, height} = element; + const adjust = getCalloutSeparatorAdjust(element, position); + let separatorStart, separatorEnd; + if (position === 'left' || position === 'right') { + separatorStart = {x: x + adjust, y}; + separatorEnd = {x: separatorStart.x, y: separatorStart.y + height}; + } else { + // position 'top' or 'bottom' + separatorStart = {x, y: y + adjust}; + separatorEnd = {x: separatorStart.x + width, y: separatorStart.y}; + } + return {separatorStart, separatorEnd}; +} + +function getCalloutSeparatorAdjust(element, position) { + const {width, height, options} = element; + const adjust = options.callout.margin + options.borderWidth / 2; + if (position === 'right') { + return width + adjust; + } else if (position === 'bottom') { + return height + adjust; + } + return -adjust; +} + +function getCalloutSideCoord(element, position, separatorStart) { + const {y, width, height, options} = element; + const start = options.callout.start; + const side = getCalloutSideAdjust(position, options.callout); + let sideStart, sideEnd; + if (position === 'left' || position === 'right') { + sideStart = {x: separatorStart.x, y: y + getSize(height, start)}; + sideEnd = {x: sideStart.x + side, y: sideStart.y}; + } else { + // position 'top' or 'bottom' + sideStart = {x: separatorStart.x + getSize(width, start), y: separatorStart.y}; + sideEnd = {x: sideStart.x, y: sideStart.y + side}; + } + return {sideStart, sideEnd}; +} + +function getCalloutSideAdjust(position, options) { + const side = options.side; + if (position === 'left' || position === 'top') { + return -side; + } + return side; +} + +function resolveCalloutPosition(element, options) { + const position = options.position; + if (position === 'left' || position === 'right' || position === 'top' || position === 'bottom') { + return position; + } + return resolveCalloutAutoPosition(element, options); +} + +function resolveCalloutAutoPosition(element, options) { + const {x, y, width, height, pointX, pointY} = element; + const {margin, side} = options; + const adjust = margin + side; + if (pointX < (x - adjust)) { + return 'left'; + } else if (pointX > (x + width + adjust)) { + return 'right'; + } else if (pointY < (y - adjust)) { + return 'top'; + } else if (pointY > (y + height + adjust)) { + return 'bottom'; + } +} + +const {drawPoint} = ChartJsV3.helpers; + +class PointAnnotation extends Element { + + inRange(mouseX, mouseY, useFinalPosition) { + const {width} = this.getProps(['width'], useFinalPosition); + return inPointRange({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), width / 2, this.options.borderWidth); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + const borderWidth = options.borderWidth; + if (options.radius < 0.1) { + return; + } + ctx.save(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + options.borderWidth = 0; + drawPoint(ctx, options, this.x, this.y); + if (stroke && !isImageOrCanvas(options.pointStyle)) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); } + ctx.restore(); + options.borderWidth = borderWidth; + } - return { - x, - y, - width: options.radius * 2, - height: options.radius * 2 - }; + resolveElementProperties(chart, options) { + return resolvePointPosition(chart, options); } } PointAnnotation.id = 'pointAnnotation'; PointAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', borderWidth: 1, + display: true, + pointStyle: 'circle', radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xAdjust: 0, + xMax: undefined, + xMin: undefined, xScaleID: 'x', xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, yScaleID: 'y', yValue: undefined }; @@ -852,32 +1579,175 @@ PointAnnotation.defaultRoutes = { backgroundColor: 'color' }; -var version = "1.0.2"; +const {PI, RAD_PER_DEG} = ChartJsV3.helpers; -const {clipArea, unclipArea, isFinite, valueOrDefault, isObject, isArray} = ChartJsV3.helpers; +class PolygonAnnotation extends Element { + inRange(mouseX, mouseY, useFinalPosition) { + return this.options.radius >= 0.1 && this.elements.length > 1 && pointIsInPolygon(this.elements, mouseX, mouseY, useFinalPosition); + } -const chartStates = new Map(); + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {elements, options} = this; + ctx.save(); + ctx.beginPath(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + let first = true; + for (const el of elements) { + if (first) { + ctx.moveTo(el.x, el.y); + first = false; + } else { + ctx.lineTo(el.x, el.y); + } + } + ctx.closePath(); + ctx.fill(); + // If no border, don't draw it + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + } + + resolveElementProperties(chart, options) { + const {x, y, width, height} = resolvePointPosition(chart, options); + const {sides, radius, rotation, borderWidth} = options; + const halfBorder = borderWidth / 2; + const elements = []; + const angle = (2 * PI) / sides; + let rad = rotation * RAD_PER_DEG; + for (let i = 0; i < sides; i++, rad += angle) { + const sin = Math.sin(rad); + const cos = Math.cos(rad); + elements.push({ + type: 'point', + optionScope: 'point', + properties: { + x: x + sin * radius, + y: y - cos * radius, + bX: x + sin * (radius + halfBorder), + bY: y - cos * (radius + halfBorder) + } + }); + } + return {x, y, width, height, elements, initProperties: {x, y}}; + } +} + +PolygonAnnotation.id = 'polygonAnnotation'; + +PolygonAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + point: { + radius: 0 + }, + radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + sides: 3, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: 'x', + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: 'y', + yValue: undefined +}; + +PolygonAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + + +function pointIsInPolygon(points, x, y, useFinalPosition) { + let isInside = false; + let A = points[points.length - 1].getProps(['bX', 'bY'], useFinalPosition); + for (const point of points) { + const B = point.getProps(['bX', 'bY'], useFinalPosition); + if ((B.bY > y) !== (A.bY > y) && x < (A.bX - B.bX) * (y - B.bY) / (A.bY - B.bY) + B.bX) { + isInside = !isInside; + } + A = B; + } + return isInside; +} const annotationTypes = { box: BoxAnnotation, - line: LineAnnotation, ellipse: EllipseAnnotation, - point: PointAnnotation + label: LabelAnnotation, + line: LineAnnotation, + point: PointAnnotation, + polygon: PolygonAnnotation }; +/** + * Register fallback for annotation elements + * For example lineAnnotation options would be looked through: + * - the annotation object (options.plugins.annotation.annotations[id]) + * - element options (options.elements.lineAnnotation) + * - element defaults (defaults.elements.lineAnnotation) + * - annotation plugin defaults (defaults.plugins.annotation, this is what we are registering here) + */ Object.keys(annotationTypes).forEach(key => { defaults.describe(`elements.${annotationTypes[key].id}`, { _fallback: 'plugins.annotation' }); }); +var name = "chartjs-plugin-annotation"; +var version = "1.2.2"; + +const {clipArea, unclipArea, isObject, isArray} = ChartJsV3.helpers; + +const chartStates = new Map(); + var annotation = { id: 'annotation', version, + /* TODO: enable in v2 + beforeRegister() { + requireVersion('chart.js', '3.7', Chart.version); + }, + */ + afterRegister() { Chart.register(annotationTypes); + + // TODO: Remove this check, warning and workaround in v2 + if (!requireVersion('chart.js', '3.7', Chart.version, false)) { + console.warn(`${name} has known issues with chart.js versions prior to 3.7, please consider upgrading.`); + + // Workaround for https://github.com/chartjs/chartjs-plugin-annotation/issues/572 + Chart.defaults.set('elements.lineAnnotation', { + callout: {}, + font: {}, + padding: 6 + }); + } }, afterUnregister() { @@ -888,6 +1758,7 @@ var annotation = { chartStates.set(chart, { annotations: [], elements: [], + visibleElements: [], listeners: {}, listened: false, moveListened: false @@ -910,6 +1781,7 @@ var annotation = { } else if (isArray(annotationOptions)) { annotations.push(...annotationOptions); } + verifyScaleOptions(annotations, chart.scales); }, afterDataLimits(chart, args) { @@ -921,27 +1793,28 @@ var annotation = { const state = chartStates.get(chart); updateListeners(chart, state, options); updateElements(chart, state, options, args.mode); + state.visibleElements = state.elements.filter(el => !el.skip && el.options.display); }, - beforeDatasetsDraw(chart) { - draw(chart, 'beforeDatasetsDraw'); + beforeDatasetsDraw(chart, _args, options) { + draw(chart, 'beforeDatasetsDraw', options.clip); }, - afterDatasetsDraw(chart) { - draw(chart, 'afterDatasetsDraw'); + afterDatasetsDraw(chart, _args, options) { + draw(chart, 'afterDatasetsDraw', options.clip); }, - beforeDraw(chart) { - draw(chart, 'beforeDraw'); + beforeDraw(chart, _args, options) { + draw(chart, 'beforeDraw', options.clip); }, - afterDraw(chart) { - draw(chart, 'afterDraw'); + afterDraw(chart, _args, options) { + draw(chart, 'afterDraw', options.clip); }, beforeEvent(chart, args, options) { const state = chartStates.get(chart); - handleEvent(chart, state, args.event, options); + handleEvent(state, args.event, options); }, destroy(chart) { @@ -953,14 +1826,15 @@ var annotation = { }, defaults: { - drawTime: 'afterDatasetsDraw', - dblClickSpeed: 350, // ms animations: { numbers: { - properties: ['x', 'y', 'x2', 'y2', 'width', 'height'], + properties: ['x', 'y', 'x2', 'y2', 'width', 'height', 'pointX', 'pointY', 'labelX', 'labelY', 'labelWidth', 'labelHeight', 'radius'], type: 'number' }, }, + clip: true, + dblClickSpeed: 350, // ms + drawTime: 'afterDatasetsDraw', label: { drawTime: null } @@ -971,7 +1845,7 @@ var annotation = { _scriptable: (prop) => !hooks.includes(prop), annotations: { _allKeys: false, - _fallback: (prop, opts) => `elements.${annotationTypes[opts.type || 'line'].id}`, + _fallback: (prop, opts) => `elements.${annotationTypes[resolveType(opts.type)].id}`, }, }, @@ -989,6 +1863,14 @@ function resolveAnimations(chart, animOpts, mode) { return new Animations(chart, animOpts); } +function resolveType(type = 'line') { + if (annotationTypes[type]) { + return type; + } + console.warn(`Unknown annotation type: '${type}', defaulting to 'line'`); + return 'line'; +} + function updateElements(chart, state, options, mode) { const animations = resolveAnimations(chart, options.animations, mode); @@ -996,27 +1878,60 @@ function updateElements(chart, state, options, mode) { const elements = resyncElements(state.elements, annotations); for (let i = 0; i < annotations.length; i++) { - const annotation = annotations[i]; - let el = elements[i]; - const elType = annotationTypes[annotation.type] || annotationTypes.line; - if (!el || !(el instanceof elType)) { - el = elements[i] = new elType(); - } - const opts = resolveAnnotationOptions(annotation.setContext(getContext(chart, el, annotation))); - const properties = el.resolveElementProperties(chart, opts); + const annotationOptions = annotations[i]; + const element = getOrCreateElement(elements, i, annotationOptions.type); + const resolver = annotationOptions.setContext(getContext(chart, element, annotationOptions)); + const resolvedOptions = resolveAnnotationOptions(resolver); + const properties = element.resolveElementProperties(chart, resolvedOptions); + properties.skip = isNaN(properties.x) || isNaN(properties.y); - properties.options = opts; - animations.update(el, properties); + properties.options = resolvedOptions; + + if ('elements' in properties) { + updateSubElements(element, properties, resolver, animations); + // Remove the sub-element definitions from properties, so the actual elements + // are not overwritten by their definitions + delete properties.elements; + } + + animations.update(element, properties); + } +} + +function updateSubElements(mainElement, {elements, initProperties}, resolver, animations) { + const subElements = mainElement.elements || (mainElement.elements = []); + subElements.length = elements.length; + for (let i = 0; i < elements.length; i++) { + const definition = elements[i]; + const properties = definition.properties; + const subElement = getOrCreateElement(subElements, i, definition.type, initProperties); + const subResolver = resolver[definition.optionScope].override(definition); + properties.options = resolveAnnotationOptions(subResolver); + animations.update(subElement, properties); } } +function getOrCreateElement(elements, index, type, initProperties) { + const elementClass = annotationTypes[resolveType(type)]; + let element = elements[index]; + if (!element || !(element instanceof elementClass)) { + element = elements[index] = new elementClass(); + if (isObject(initProperties)) { + Object.assign(element, initProperties); + } + } + return element; +} + function resolveAnnotationOptions(resolver) { - const elType = annotationTypes[resolver.type] || annotationTypes.line; + const elementClass = annotationTypes[resolveType(resolver.type)]; const result = {}; result.id = resolver.id; result.type = resolver.type; result.drawTime = resolver.drawTime; - Object.assign(result, resolveObj(resolver, elType.defaults), resolveObj(resolver, elType.defaultRoutes)); + Object.assign(result, + resolveObj(resolver, elementClass.defaults), + resolveObj(resolver, elementClass.defaultRoutes)); for (const hook of hooks) { result[hook] = resolver[hook]; } @@ -1025,10 +1940,10 @@ function resolveAnnotationOptions(resolver) { function resolveObj(resolver, defs) { const result = {}; - for (const name of Object.keys(defs)) { - const optDefs = defs[name]; - const value = resolver[name]; - result[name] = isObject(optDefs) ? resolveObj(value, optDefs) : value; + for (const prop of Object.keys(defs)) { + const optDefs = defs[prop]; + const value = resolver[prop]; + result[prop] = isObject(optDefs) ? resolveObj(value, optDefs) : value; } return result; } @@ -1054,74 +1969,46 @@ function resyncElements(elements, annotations) { return elements; } -function draw(chart, caller) { +function draw(chart, caller, clip) { const {ctx, chartArea} = chart; - const state = chartStates.get(chart); - const elements = state.elements.filter(el => !el.skip && el.options.display); + const {visibleElements} = chartStates.get(chart); - clipArea(ctx, chartArea); - elements.forEach(el => { - if (el.options.drawTime === caller) { - el.draw(ctx); - } - }); - unclipArea(ctx); + if (clip) { + clipArea(ctx, chartArea); + } + + drawElements(ctx, visibleElements, caller); + drawSubElements(ctx, visibleElements, caller); + + if (clip) { + unclipArea(ctx); + } - elements.forEach(el => { - if ('drawLabel' in el && el.options.label && (el.options.label.drawTime || el.options.drawTime) === caller) { + visibleElements.forEach(el => { + if (!('drawLabel' in el)) { + return; + } + const label = el.options.label; + if (label && label.enabled && label.content && (label.drawTime || el.options.drawTime) === caller) { el.drawLabel(ctx, chartArea); } }); } -function adjustScaleRange(chart, scale, annotations) { - const range = getScaleLimits(scale, annotations); - let changed = false; - if (isFinite(range.min) && - typeof scale.options.min === 'undefined' && - typeof scale.options.suggestedMin === 'undefined') { - changed = scale.min !== range.min; - scale.min = range.min; - } - if (isFinite(range.max) && - typeof scale.options.max === 'undefined' && - typeof scale.options.suggestedMax === 'undefined') { - changed = scale.max !== range.max; - scale.max = range.max; - } - if (changed && typeof scale.handleTickRangeOptions === 'function') { - scale.handleTickRangeOptions(); +function drawElements(ctx, elements, caller) { + for (const el of elements) { + if (el.options.drawTime === caller) { + el.draw(ctx); + } } } -function getScaleLimits(scale, annotations) { - const axis = scale.axis; - const scaleID = scale.id; - const scaleIDOption = axis + 'ScaleID'; - let min = valueOrDefault(scale.min, Number.NEGATIVE_INFINITY); - let max = valueOrDefault(scale.max, Number.POSITIVE_INFINITY); - for (const annotation of annotations) { - if (annotation.scaleID === scaleID) { - for (const prop of ['value', 'endValue']) { - const raw = annotation[prop]; - if (raw) { - const value = scale.parse(raw); - min = Math.min(min, value); - max = Math.max(max, value); - } - } - } else if (annotation[scaleIDOption] === scaleID) { - for (const prop of [axis + 'Min', axis + 'Max', axis + 'Value']) { - const raw = annotation[prop]; - if (raw) { - const value = scale.parse(raw); - min = Math.min(min, value); - max = Math.max(max, value); - } - } +function drawSubElements(ctx, elements, caller) { + for (const el of elements) { + if (isArray(el.elements)) { + drawElements(ctx, el.elements, caller); } } - return {min, max}; } export { annotation as default }; diff --git a/dist/chartjs-plugin-annotation.js b/dist/chartjs-plugin-annotation.js index e7095ead0..e81221fe5 100644 --- a/dist/chartjs-plugin-annotation.js +++ b/dist/chartjs-plugin-annotation.js @@ -1,28 +1,26 @@ /*! -* chartjs-plugin-annotation v1.0.2 +* chartjs-plugin-annotation v1.2.2 * https://www.chartjs.org/chartjs-plugin-annotation/index - * (c) 2021 chartjs-plugin-annotation Contributors + * (c) 2022 chartjs-plugin-annotation Contributors * Released under the MIT License */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js-v3'), require('canvas')) : typeof define === 'function' && define.amd ? define(['chart.js-v3', 'canvas'], factory) : -(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global['chartjs-plugin-annotation'] = factory(global.ChartJsV3, global.canvas)); -}(this, (function (ChartJsV3, canvas) { 'use strict'; +(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global["chartjs-plugin-annotation"] = factory(global.ChartJsV3, global.canvas)); +})(this, (function (ChartJsV3, canvas) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var ChartJsV3__default = /*#__PURE__*/_interopDefaultLegacy(ChartJsV3); -const {distanceBetweenPoints} = ChartJsV3__default['default'].helpers; -const callHandler = ChartJsV3__default['default'].helpers.callback; +const {distanceBetweenPoints, defined: defined$2, callback} = ChartJsV3__default["default"].helpers; const clickHooks = ['click', 'dblclick']; const moveHooks = ['enter', 'leave']; const hooks = clickHooks.concat(moveHooks); function updateListeners(chart, state, options) { - const annotations = state.annotations || []; state.listened = false; state.moveListened = false; @@ -30,6 +28,8 @@ function updateListeners(chart, state, options) { if (typeof options[hook] === 'function') { state.listened = true; state.listeners[hook] = options[hook]; + } else if (defined$2(state.listeners[hook])) { + delete state.listeners[hook]; } }); moveHooks.forEach(hook => { @@ -39,7 +39,7 @@ function updateListeners(chart, state, options) { }); if (!state.listened || !state.moveListened) { - annotations.forEach(scope => { + state.annotations.forEach(scope => { if (!state.listened) { clickHooks.forEach(hook => { if (typeof scope[hook] === 'function') { @@ -59,21 +59,21 @@ function updateListeners(chart, state, options) { } } -function handleEvent(chart, state, event, options) { +function handleEvent(state, event, options) { if (state.listened) { switch (event.type) { case 'mousemove': case 'mouseout': - handleMoveEvents(chart, state, event); + handleMoveEvents(state, event); break; case 'click': - handleClickEvents(chart, state, event, options); + handleClickEvents(state, event, options); break; } } } -function handleMoveEvents(chart, state, event) { +function handleMoveEvents(state, event) { if (!state.moveListened) { return; } @@ -87,20 +87,20 @@ function handleMoveEvents(chart, state, event) { const previous = state.hovered; state.hovered = element; - dispatchMoveEvents(chart, state, {previous, element}, event); + dispatchMoveEvents(state, {previous, element}, event); } -function dispatchMoveEvents(chart, state, elements, event) { +function dispatchMoveEvents(state, elements, event) { const {previous, element} = elements; if (previous && previous !== element) { - dispatchEvent(chart, previous.options.leave || state.listeners.leave, previous, event); + dispatchEvent(previous.options.leave || state.listeners.leave, previous, event); } if (element && element !== previous) { - dispatchEvent(chart, element.options.enter || state.listeners.enter, element, event); + dispatchEvent(element.options.enter || state.listeners.enter, element, event); } } -function handleClickEvents(chart, state, event, options) { +function handleClickEvents(state, event, options) { const listeners = state.listeners; const element = getNearestItem(state.elements, event); if (element) { @@ -111,22 +111,22 @@ function handleClickEvents(chart, state, event, options) { // 2nd click before timeout, so its a double click clearTimeout(element.clickTimeout); delete element.clickTimeout; - dispatchEvent(chart, dblclick, element, event); + dispatchEvent(dblclick, element, event); } else if (dblclick) { // if there is a dblclick handler, wait for dblClickSpeed ms before deciding its a click element.clickTimeout = setTimeout(() => { delete element.clickTimeout; - dispatchEvent(chart, click, element, event); + dispatchEvent(click, element, event); }, options.dblClickSpeed); } else { // no double click handler, just call the click handler directly - dispatchEvent(chart, click, element, event); + dispatchEvent(click, element, event); } } } -function dispatchEvent(chart, handler, element, event) { - callHandler(handler, [{chart, element}, event]); +function dispatchEvent(handler, element, event) { + callback(handler, [element.$context, event]); } function getNearestItem(elements, position) { @@ -152,7 +152,71 @@ function getNearestItem(elements, position) { .slice(0, 1)[0]; // return only the top item } -const {isFinite: isFinite$1} = ChartJsV3__default['default'].helpers; +const {isFinite: isFinite$1, valueOrDefault: valueOrDefault$2, defined: defined$1} = ChartJsV3__default["default"].helpers; + +function adjustScaleRange(chart, scale, annotations) { + const range = getScaleLimits(scale, annotations); + let changed = changeScaleLimit(scale, range, 'min', 'suggestedMin'); + changed = changeScaleLimit(scale, range, 'max', 'suggestedMax') || changed; + if (changed && typeof scale.handleTickRangeOptions === 'function') { + scale.handleTickRangeOptions(); + } +} + +function verifyScaleOptions(annotations, scales) { + for (const annotation of annotations) { + verifyScaleIDs(annotation, scales); + } +} + +function changeScaleLimit(scale, range, limit, suggestedLimit) { + if (isFinite$1(range[limit]) && !scaleLimitDefined(scale.options, limit, suggestedLimit)) { + const changed = scale[limit] !== range[limit]; + scale[limit] = range[limit]; + return changed; + } +} + +function scaleLimitDefined(scaleOptions, limit, suggestedLimit) { + return defined$1(scaleOptions[limit]) || defined$1(scaleOptions[suggestedLimit]); +} + +function verifyScaleIDs(annotation, scales) { + for (const key of ['scaleID', 'xScaleID', 'yScaleID']) { + if (annotation[key] && !scales[annotation[key]]) { + console.warn(`No scale found with id '${annotation[key]}' for annotation '${annotation.id}'`); + } + } +} + +function getScaleLimits(scale, annotations) { + const axis = scale.axis; + const scaleID = scale.id; + const scaleIDOption = axis + 'ScaleID'; + const limits = { + min: valueOrDefault$2(scale.min, Number.NEGATIVE_INFINITY), + max: valueOrDefault$2(scale.max, Number.POSITIVE_INFINITY) + }; + for (const annotation of annotations) { + if (annotation.scaleID === scaleID) { + updateLimits(annotation, scale, ['value', 'endValue'], limits); + } else if (annotation[scaleIDOption] === scaleID) { + updateLimits(annotation, scale, [axis + 'Min', axis + 'Max', axis + 'Value'], limits); + } + } + return limits; +} + +function updateLimits(annotation, scale, props, limits) { + for (const prop of props) { + const raw = annotation[prop]; + if (defined$1(raw)) { + const value = scale.parse(raw); + limits.min = Math.min(limits.min, value); + limits.max = Math.max(limits.max, value); + } + } +} const clamp = (x, from, to) => Math.min(to, Math.max(from, x)); @@ -163,23 +227,249 @@ function clampAll(obj, from, to) { return obj; } -function scaleValue(scale, value, fallback) { - value = typeof value === 'number' ? value : scale.parse(value); - return isFinite$1(value) ? scale.getPixelForValue(value) : fallback; +function inPointRange(point, center, radius, borderWidth) { + if (!point || !center || radius <= 0) { + return false; + } + const hBorderWidth = borderWidth / 2 || 0; + return (Math.pow(point.x - center.x, 2) + Math.pow(point.y - center.y, 2)) <= Math.pow(radius + hBorderWidth, 2); +} + +function inBoxRange(mouseX, mouseY, {x, y, width, height}, borderWidth) { + const hBorderWidth = borderWidth / 2 || 0; + return mouseX >= x - hBorderWidth && + mouseX <= x + width + hBorderWidth && + mouseY >= y - hBorderWidth && + mouseY <= y + height + hBorderWidth; +} + +function getElementCenterPoint(element, useFinalPosition) { + const {x, y} = element.getProps(['x', 'y'], useFinalPosition); + return {x, y}; +} + +const isOlderPart = (act, req) => req > act || (act.length > req.length && act.substr(0, req.length) === req); + +function requireVersion(pkg, min, ver, strict = true) { + const parts = ver.split('.'); + let i = 0; + for (const req of min.split('.')) { + const act = parts[i++]; + if (parseInt(req, 10) < parseInt(act, 10)) { + break; + } + if (isOlderPart(act, req)) { + if (strict) { + throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`); + } else { + return false; + } + } + } + return true; +} + +const {isObject: isObject$1, valueOrDefault: valueOrDefault$1, defined} = ChartJsV3__default["default"].helpers; + +const isPercentString = (s) => typeof s === 'string' && s.endsWith('%'); +const toPercent = (s) => clamp(parseFloat(s) / 100, 0, 1); + +function getRelativePosition(size, positionOption) { + if (positionOption === 'start') { + return 0; + } + if (positionOption === 'end') { + return size; + } + if (isPercentString(positionOption)) { + return toPercent(positionOption) * size; + } + return size / 2; +} + +function getSize(size, value) { + if (typeof value === 'number') { + return value; + } else if (isPercentString(value)) { + return toPercent(value) * size; + } + return size; +} + +function calculateTextAlignment(size, options) { + const {x, width} = size; + const textAlign = options.textAlign; + if (textAlign === 'center') { + return x + width / 2; + } else if (textAlign === 'end' || textAlign === 'right') { + return x + width; + } + return x; +} + +function toPosition(value) { + if (isObject$1(value)) { + return { + x: valueOrDefault$1(value.x, 'center'), + y: valueOrDefault$1(value.y, 'center'), + }; + } + value = valueOrDefault$1(value, 'center'); + return { + x: value, + y: value + }; +} + +function isBoundToPoint(options) { + return options && (defined(options.xValue) || defined(options.yValue)); +} + +const {addRoundedRectPath, isArray: isArray$1, toFont, toTRBLCorners, valueOrDefault} = ChartJsV3__default["default"].helpers; + +const widthCache = new Map(); + +function isImageOrCanvas(content) { + return content instanceof canvas.Image || content instanceof Image || content instanceof HTMLCanvasElement; +} + +/** + * Apply border options to the canvas context before drawing a shape + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options with border configuration + * @returns {boolean} true is the border options have been applied + */ +function setBorderStyle(ctx, options) { + if (options && options.borderWidth) { + ctx.lineCap = options.borderCapStyle; + ctx.setLineDash(options.borderDash); + ctx.lineDashOffset = options.borderDashOffset; + ctx.lineJoin = options.borderJoinStyle; + ctx.lineWidth = options.borderWidth; + ctx.strokeStyle = options.borderColor; + return true; + } +} + +/** + * Apply shadow options to the canvas context before drawing a shape + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options with shadow configuration + */ +function setShadowStyle(ctx, options) { + ctx.shadowColor = options.backgroundShadowColor; + ctx.shadowBlur = options.shadowBlur; + ctx.shadowOffsetX = options.shadowOffsetX; + ctx.shadowOffsetY = options.shadowOffsetY; +} + +/** + * Measure the label size using the label options. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options to configure the label + * @returns {{width: number, height: number}} the measured size of the label + */ +function measureLabelSize(ctx, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + return { + width: getSize(content.width, options.width), + height: getSize(content.height, options.height) + }; + } + const font = toFont(options.font); + const lines = isArray$1(content) ? content : [content]; + 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); +} + +/** + * Draw a box with the size and the styling options. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {{x: number, y: number, width: number, height: number}} rect - rect to draw + * @param {Object} options - options to style the box + * @returns {undefined} + */ +function drawBox(ctx, rect, options) { + const {x, y, width, height} = rect; + ctx.save(); + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + ctx.fillStyle = options.backgroundColor; + ctx.beginPath(); + addRoundedRectPath(ctx, { + x, y, w: width, h: height, + // TODO: v2 remove support for cornerRadius + radius: clampAll(toTRBLCorners(valueOrDefault(options.cornerRadius, options.borderRadius)), 0, Math.min(width, height) / 2) + }); + ctx.closePath(); + ctx.fill(); + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); +} + +function drawLabel(ctx, rect, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + ctx.drawImage(content, rect.x, rect.y, rect.width, rect.height); + return; + } + const labels = isArray$1(content) ? content : [content]; + const font = toFont(options.font); + const lh = font.lineHeight; + const x = calculateTextAlignment(rect, options); + const y = rect.y + (lh / 2); + ctx.font = font.string; + ctx.textBaseline = 'middle'; + ctx.textAlign = options.textAlign; + ctx.fillStyle = options.color; + labels.forEach((l, i) => ctx.fillText(l, x, y + (i * lh))); +} + +/** + * @typedef {import('chart.js').Point} Point + */ + +/** + * @param {{x: number, y: number, width: number, height: number}} rect + * @returns {Point} + */ +function getRectCenterPoint(rect) { + const {x, y, width, height} = rect; + return { + x: x + width / 2, + y: y + height / 2 + }; } /** * Rotate a `point` relative to `center` point by `angle` - * @param {{x: number, y: number}} point - the point to rotate - * @param {{x: number, y: number}} center - center point for rotation + * @param {Point} point - the point to rotate + * @param {Point} center - center point for rotation * @param {number} angle - angle for rotation, in radians - * @returns {{x: number, y: number}} rotated point + * @returns {Point} rotated point */ function rotated(point, center, angle) { - var cos = Math.cos(angle); - var sin = Math.sin(angle); - var cx = center.x; - var cy = center.y; + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const cx = center.x; + const cy = center.y; return { x: cx + cos * (point.x - cx) - sin * (point.y - cy), @@ -187,105 +477,227 @@ function rotated(point, center, angle) { }; } -const {addRoundedRectPath: addRoundedRectPath$1, toTRBLCorners: toTRBLCorners$1, valueOrDefault: valueOrDefault$2} = ChartJsV3__default['default'].helpers; +const {isFinite} = ChartJsV3__default["default"].helpers; -class BoxAnnotation extends ChartJsV3.Element { - inRange(mouseX, mouseY, useFinalPosition) { - const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition); +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").Scale } Scale + * @typedef { import("chart.js").Point } Point + * @typedef { import('../../types/options').CoreAnnotationOptions } CoreAnnotationOptions + * @typedef { import('../../types/options').PointAnnotationOptions } PointAnnotationOptions + */ - return mouseX >= x && - mouseX <= x + width && - mouseY >= y && - mouseY <= y + height; - } +/** + * @param {Scale} scale + * @param {number|string} value + * @param {number} fallback + * @returns {number} + */ +function scaleValue(scale, value, fallback) { + value = typeof value === 'number' ? value : scale.parse(value); + return isFinite(value) ? scale.getPixelForValue(value) : fallback; +} - getCenterPoint(useFinalPosition) { - const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition); +/** + * @param {Scale} scale + * @param {{start: number, end: number}} options + * @returns {{start: number, end: number}} + */ +function getChartDimensionByScale(scale, options) { + if (scale) { + const min = scaleValue(scale, options.min, options.start); + const max = scaleValue(scale, options.max, options.end); return { - x: x + width / 2, - y: y + height / 2 + start: Math.min(min, max), + end: Math.max(min, max) }; } + return { + start: options.start, + end: options.end + }; +} - draw(ctx) { - const {x, y, width, height, options} = this; +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {Point} + */ +function getChartPoint(chart, options) { + const {chartArea, scales} = chart; + const xScale = scales[options.xScaleID]; + const yScale = scales[options.yScaleID]; + let x = chartArea.width / 2; + let y = chartArea.height / 2; + + if (xScale) { + x = scaleValue(xScale, options.xValue, x); + } - ctx.save(); + if (yScale) { + y = scaleValue(yScale, options.yValue, y); + } + return {x, y}; +} - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.fillStyle = options.backgroundColor; +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {{x?:number, y?: number, x2?: number, y2?: number, width?: number, height?: number}} + */ +function getChartRect(chart, options) { + const xScale = chart.scales[options.xScaleID]; + const yScale = chart.scales[options.yScaleID]; + let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; + if (!xScale && !yScale) { + return {}; + } - ctx.beginPath(); - addRoundedRectPath$1(ctx, { - x, y, w: width, h: height, - // TODO: v2 remove support for cornerRadius - radius: clampAll(toTRBLCorners$1(valueOrDefault$2(options.cornerRadius, options.borderRadius)), 0, Math.min(width, height) / 2) - }); - ctx.closePath(); - ctx.fill(); + const xDim = getChartDimensionByScale(xScale, {min: options.xMin, max: options.xMax, start: x, end: x2}); + x = xDim.start; + x2 = xDim.end; + const yDim = getChartDimensionByScale(yScale, {min: options.yMin, max: options.yMax, start: y, end: y2}); + y = yDim.start; + y2 = yDim.end; - // If no border, don't draw it - if (options.borderWidth) { - ctx.stroke(); - } + return { + x, + y, + x2, + y2, + width: x2 - x, + height: y2 - y + }; +} - ctx.restore(); +/** + * @param {Chart} chart + * @param {PointAnnotationOptions} options + */ +function getChartCircle(chart, options) { + const point = getChartPoint(chart, options); + return { + x: point.x + options.xAdjust, + y: point.y + options.yAdjust, + width: options.radius * 2, + height: options.radius * 2 + }; +} + +/** + * @param {Chart} chart + * @param {PointAnnotationOptions} options + * @returns + */ +function resolvePointPosition(chart, options) { + if (!isBoundToPoint(options)) { + const box = getChartRect(chart, options); + const point = getRectCenterPoint(box); + let radius = options.radius; + if (!radius || isNaN(radius)) { + radius = Math.min(box.width, box.height) / 2; + options.radius = radius; + } + return { + x: point.x + options.xAdjust, + y: point.y + options.yAdjust, + width: radius * 2, + height: radius * 2 + }; } + return getChartCircle(chart, options); +} - resolveElementProperties(chart, options) { - const xScale = chart.scales[options.xScaleID]; - const yScale = chart.scales[options.yScaleID]; - let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea; - let min, max; +const {toPadding: toPadding$2} = ChartJsV3__default["default"].helpers; - if (!xScale && !yScale) { - return {options: {}}; - } +class BoxAnnotation extends ChartJsV3.Element { + inRange(mouseX, mouseY, useFinalPosition) { + return inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition), this.options.borderWidth); + } - if (xScale) { - min = scaleValue(xScale, options.xMin, x); - max = scaleValue(xScale, options.xMax, x2); - x = Math.min(min, max); - x2 = Math.max(min, max); - } + getCenterPoint(useFinalPosition) { + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); + } - if (yScale) { - min = scaleValue(yScale, options.yMin, y2); - max = scaleValue(yScale, options.yMax, y); - y = Math.min(min, max); - y2 = Math.max(min, max); - } + draw(ctx) { + ctx.save(); + drawBox(ctx, this, this.options); + ctx.restore(); + } - return { - x, - y, - x2, - y2, - width: x2 - x, - height: y2 - y + drawLabel(ctx) { + const {x, y, width, height, options} = this; + const {label, borderWidth} = options; + const halfBorder = borderWidth / 2; + const position = toPosition(label.position); + const padding = toPadding$2(label.padding); + const labelSize = measureLabelSize(ctx, label); + const labelRect = { + x: calculateX(this, labelSize, position, padding), + y: calculateY(this, labelSize, position, padding), + width: labelSize.width, + height: labelSize.height }; + + ctx.save(); + ctx.beginPath(); + ctx.rect(x + halfBorder + padding.left, y + halfBorder + padding.top, + width - borderWidth - padding.width, height - borderWidth - padding.height); + ctx.clip(); + drawLabel(ctx, labelRect, label); + ctx.restore(); + } + + resolveElementProperties(chart, options) { + return getChartRect(chart, options); } } BoxAnnotation.id = 'boxAnnotation'; BoxAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0, - borderWidth: 1, + borderJoinStyle: 'miter', borderRadius: 0, - xScaleID: 'x', - xMin: undefined, + borderShadowColor: 'transparent', + borderWidth: 1, + cornerRadius: undefined, // TODO: v2 remove support for cornerRadius + display: true, + label: { + borderWidth: undefined, + color: 'black', + content: null, + drawTime: undefined, + enabled: false, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: 'bold' + }, + height: undefined, + padding: 6, + position: 'center', + textAlign: 'start', + xAdjust: 0, + yAdjust: 0, + width: undefined + }, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' }; BoxAnnotation.defaultRoutes = { @@ -293,13 +705,47 @@ BoxAnnotation.defaultRoutes = { backgroundColor: 'color' }; -const {addRoundedRectPath, isArray: isArray$1, toFontString, toRadians: toRadians$1, toTRBLCorners, valueOrDefault: valueOrDefault$1} = ChartJsV3__default['default'].helpers; +BoxAnnotation.descriptors = { + label: { + _fallback: true + } +}; + +function calculateX(box, labelSize, position, padding) { + const {x: start, x2: end, width: size, options} = box; + const {xAdjust: adjust, borderWidth} = options.label; + return calculatePosition$1({start, end, size}, { + position: position.x, + padding: {start: padding.left, end: padding.right}, + adjust, borderWidth, + size: labelSize.width + }); +} + +function calculateY(box, labelSize, position, padding) { + const {y: start, y2: end, height: size, options} = box; + const {yAdjust: adjust, borderWidth} = options.label; + return calculatePosition$1({start, end, size}, { + position: position.y, + padding: {start: padding.top, end: padding.bottom}, + adjust, borderWidth, + size: labelSize.height + }); +} + +function calculatePosition$1(boxOpts, labelOpts) { + const {start, end} = boxOpts; + const {position, padding: {start: padStart, end: padEnd}, adjust, borderWidth} = labelOpts; + const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; + return start + borderWidth / 2 + adjust + padStart + getRelativePosition(availableSize, position); +} + +const {PI: PI$2, toRadians: toRadians$1, toPadding: toPadding$1} = ChartJsV3__default["default"].helpers; -const PI = Math.PI; 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; const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p2.x - p1.x))).y; -const toPercent = (s) => typeof s === 'string' && s.endsWith('%') && parseFloat(s) / 100; +const sqr = v => v * v; function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) { return !( @@ -337,10 +783,11 @@ function limitLineToArea(p1, p2, area) { } class LineAnnotation extends ChartJsV3.Element { - intersects(x, y, epsilon = 0.001) { + + // TODO: make private in v2 + intersects(x, y, epsilon = 0.001, useFinalPosition) { // Adapted from https://stackoverflow.com/a/6853926/25507 - const sqr = v => v * v; - const {x: x1, y: y1, x2, y2} = this; + const {x: x1, y: y1, x2, y2} = this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); const dx = x2 - x1; const dy = y2 - y1; const lenSq = sqr(dx) + sqr(dy); @@ -359,29 +806,37 @@ class LineAnnotation extends ChartJsV3.Element { return (sqr(x - xx) + sqr(y - yy)) < epsilon; } - labelIsVisible(chartArea) { - const label = this.options.label; - - const inside = !chartArea || isLineInArea(this, chartArea); - return inside && label && label.enabled && label.content; + /** + * @todo make private in v2 + * @param {boolean} useFinalPosition - use the element's animation target instead of current position + * @param {top, right, bottom, left} [chartArea] - optional, area of the chart + * @returns {boolean} true if the label is visible + */ + labelIsVisible(useFinalPosition, chartArea) { + const labelOpts = this.options.label; + if (!labelOpts || !labelOpts.enabled) { + return false; + } + return !chartArea || isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea); } - isOnLabel(mouseX, mouseY) { - const {labelRect} = this; - if (!labelRect || !this.labelIsVisible()) { + // TODO: make private in v2 + isOnLabel(mouseX, mouseY, useFinalPosition) { + if (!this.labelIsVisible(useFinalPosition)) { return false; } - - const {x, y} = rotated({x: mouseX, y: mouseY}, labelRect, -labelRect.rotation); - const w2 = labelRect.width / 2; - const h2 = labelRect.height / 2; - return x >= labelRect.x - w2 && x <= labelRect.x + w2 && - y >= labelRect.y - h2 && y <= labelRect.y + h2; + const {labelX, labelY, labelWidth, labelHeight, labelRotation} = this.getProps(['labelX', 'labelY', 'labelWidth', 'labelHeight', 'labelRotation'], useFinalPosition); + const {x, y} = rotated({x: mouseX, y: mouseY}, {x: labelX, y: labelY}, -labelRotation); + const hBorderWidth = this.options.label.borderWidth / 2 || 0; + const w2 = labelWidth / 2 + hBorderWidth; + const h2 = labelHeight / 2 + hBorderWidth; + return x >= labelX - w2 && x <= labelX + w2 && + y >= labelY - h2 && y <= labelY + h2; } - inRange(x, y) { - const epsilon = this.options.borderWidth || 1; - return this.intersects(x, y, epsilon) || this.isOnLabel(x, y); + inRange(mouseX, mouseY, useFinalPosition) { + const epsilon = sqr(this.options.borderWidth / 2); + return this.intersects(mouseX, mouseY, epsilon, useFinalPosition) || this.isOnLabel(mouseX, mouseY, useFinalPosition); } getCenterPoint() { @@ -393,28 +848,55 @@ class LineAnnotation extends ChartJsV3.Element { draw(ctx) { const {x, y, x2, y2, options} = this; - ctx.save(); - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; + ctx.save(); + if (!setBorderStyle(ctx, options)) { + // no border width, then line is not drawn + return ctx.restore(); + } + setShadowStyle(ctx, options); + const angle = Math.atan2(y2 - y, x2 - x); + const length = Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)); + const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(this); - // Draw + ctx.translate(x, y); + ctx.rotate(angle); ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x2, y2); + ctx.moveTo(0 + startAdjust, 0); + ctx.lineTo(length - endAdjust, 0); + ctx.shadowColor = options.borderShadowColor; ctx.stroke(); - + drawArrowHead(ctx, 0, startAdjust, startOpts); + drawArrowHead(ctx, length, -endAdjust, endOpts); ctx.restore(); } drawLabel(ctx, chartArea) { - if (this.labelIsVisible(chartArea)) { - ctx.save(); - drawLabel(ctx, this, chartArea); - ctx.restore(); + if (!this.labelIsVisible(false, chartArea)) { + return; } + const {labelX, labelY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options: {label}} = this; + + ctx.save(); + ctx.translate(labelX, labelY); + ctx.rotate(labelRotation); + + const boxRect = { + x: -(labelWidth / 2), + y: -(labelHeight / 2), + width: labelWidth, + height: labelHeight + }; + drawBox(ctx, boxRect, label); + + const labelTextRect = { + x: -(labelWidth / 2) + labelPadding.left + label.borderWidth / 2, + y: -(labelHeight / 2) + labelPadding.top + label.borderWidth / 2, + width: labelTextSize.width, + height: labelTextSize.height + }; + drawLabel(ctx, labelTextRect, label); + ctx.restore(); } resolveElementProperties(chart, options) { @@ -447,189 +929,164 @@ class LineAnnotation extends ChartJsV3.Element { } } const inside = isLineInArea({x, y, x2, y2}, chart.chartArea); - return inside + const properties = inside ? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea) : {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; + + const label = options.label; + if (label && label.content) { + return loadLabelRect(properties, chart, label); + } + return properties; } } LineAnnotation.id = 'lineAnnotation'; + +const arrowHeadsDefaults = { + backgroundColor: undefined, + backgroundShadowColor: undefined, + borderColor: undefined, + borderDash: undefined, + borderDashOffset: undefined, + borderShadowColor: undefined, + borderWidth: undefined, + enabled: undefined, + fill: undefined, + length: undefined, + shadowBlur: undefined, + shadowOffsetX: undefined, + shadowOffsetY: undefined, + width: undefined +}; + LineAnnotation.defaults = { - display: true, adjustScaleRange: true, - borderWidth: 2, + arrowHeads: { + enabled: false, + end: Object.assign({}, arrowHeadsDefaults), + fill: false, + length: 12, + start: Object.assign({}, arrowHeadsDefaults), + width: 6 + }, borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 2, + display: true, + endValue: undefined, label: { backgroundColor: 'rgba(0,0,0,0.8)', + backgroundShadowColor: 'transparent', borderCapStyle: 'butt', borderColor: 'black', borderDash: [], borderDashOffset: 0, borderJoinStyle: 'miter', borderRadius: 6, + borderShadowColor: 'transparent', borderWidth: 0, + color: '#fff', + content: null, + cornerRadius: undefined, // TODO: v2 remove support for cornerRadius drawTime: undefined, + enabled: false, font: { family: undefined, lineHeight: undefined, size: undefined, - style: 'bold', - weight: undefined + style: undefined, + weight: 'bold' }, - color: '#fff', - xPadding: 6, - yPadding: 6, - rotation: 0, + height: undefined, + padding: 6, position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + width: undefined, xAdjust: 0, + xPadding: undefined, // TODO: v2 remove support for xPadding yAdjust: 0, - textAlign: 'center', - enabled: false, - content: null + yPadding: undefined, // TODO: v2 remove support for yPadding }, - value: undefined, - endValue: undefined, scaleID: undefined, - xScaleID: 'x', - xMin: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + value: undefined, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' +}; + +LineAnnotation.descriptors = { + arrowHeads: { + start: { + _fallback: true + }, + end: { + _fallback: true + }, + _fallback: true + } }; LineAnnotation.defaultRoutes = { borderColor: 'color' }; +function loadLabelRect(line, chart, options) { + // TODO: v2 remove support for xPadding and yPadding + const {padding: lblPadding, xPadding, yPadding, borderWidth} = options; + const padding = getPadding(lblPadding, xPadding, yPadding); + const textSize = measureLabelSize(chart.ctx, options); + const width = textSize.width + padding.width + borderWidth; + const height = textSize.height + padding.height + borderWidth; + const labelRect = calculateLabelPosition(line, options, {width, height, padding}, chart.chartArea); + line.labelX = labelRect.x; + line.labelY = labelRect.y; + line.labelWidth = labelRect.width; + line.labelHeight = labelRect.height; + line.labelRotation = labelRect.rotation; + line.labelPadding = padding; + line.labelTextSize = textSize; + return line; +} + function calculateAutoRotation(line) { const {x, y, x2, y2} = line; const rotation = Math.atan2(y2 - y, x2 - x); // Flip the rotation if it goes > PI/2 or < -PI/2, so label stays upright - return rotation > PI / 2 ? rotation - PI : rotation < PI / -2 ? rotation + PI : rotation; + return rotation > PI$2 / 2 ? rotation - PI$2 : rotation < PI$2 / -2 ? rotation + PI$2 : rotation; } -function drawLabel(ctx, line, chartArea) { - const label = line.options.label; - - ctx.font = toFontString(label.font); - - const {width, height} = measureLabel(ctx, label); - const rect = line.labelRect = calculateLabelPosition(line, width, height, chartArea); - - ctx.translate(rect.x, rect.y); - ctx.rotate(rect.rotation); - - ctx.fillStyle = label.backgroundColor; - const stroke = setBorderStyle(ctx, label); - - ctx.beginPath(); - addRoundedRectPath(ctx, { - x: -(width / 2), y: -(height / 2), w: width, h: height, - // TODO: v2 remove support for cornerRadius - radius: clampAll(toTRBLCorners(valueOrDefault$1(label.cornerRadius, label.borderRadius)), 0, Math.min(width, height) / 2) - }); - ctx.closePath(); - ctx.fill(); - if (stroke) { - ctx.stroke(); - } - - ctx.fillStyle = label.color; - if (isArray$1(label.content)) { - ctx.textAlign = label.textAlign; - const x = calculateLabelXAlignment(label, width); - let textYPosition = -(height / 2) + label.yPadding; - for (let i = 0; i < label.content.length; i++) { - ctx.textBaseline = 'top'; - ctx.fillText( - label.content[i], - x, - textYPosition - ); - textYPosition += label.font.size + label.yPadding; - } - } else if (label.content instanceof canvas.Image) { - const x = -(width / 2) + label.xPadding; - const y = -(height / 2) + label.yPadding; - ctx.drawImage(label.content, x, y, width - (2 * label.xPadding), height - (2 * label.yPadding)); - } else { - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(label.content, 0, 0); +// TODO: v2 remove support for xPadding and yPadding +function getPadding(padding, xPadding, yPadding) { + let tempPadding = padding; + if (xPadding || yPadding) { + tempPadding = {x: xPadding || 6, y: yPadding || 6}; } + return toPadding$1(tempPadding); } -function setBorderStyle(ctx, options) { - if (options.borderWidth) { - ctx.lineCap = options.borderCapStyle; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; - ctx.lineJoin = options.borderJoinStyle; - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - return true; - } -} - -function calculateLabelXAlignment(label, width) { - const {textAlign, xPadding} = label; - if (textAlign === 'start') { - return -(width / 2) + xPadding; - } else if (textAlign === 'end') { - return +(width / 2) - xPadding; - } - return 0; -} - -function getImageSize(size, value) { - if (typeof value === 'number') { - return value; - } else if (typeof value === 'string') { - return toPercent(value) * size; - } - return size; -} - -const widthCache = new Map(); -function measureLabel(ctx, label) { - const content = label.content; - if (content instanceof canvas.Image) { - return { - width: getImageSize(content.width, label.width) + 2 * label.xPadding, - height: getImageSize(content.height, label.height) + 2 * label.yPadding - }; - } - const lines = isArray$1(content) ? content : [content]; - const count = lines.length; - let width = 0; - for (let i = 0; i < count; i++) { - const text = lines[i]; - if (!widthCache.has(text)) { - widthCache.set(text, ctx.measureText(text).width); - } - width = Math.max(width, widthCache.get(text)); - } - width += 2 * label.xPadding; - - return { - width, - height: count * label.font.size + ((count + 1) * label.yPadding) - }; -} - -function calculateLabelPosition(line, width, height, chartArea) { - const label = line.options.label; - const {xAdjust, yAdjust, xPadding, yPadding, position} = label; +function calculateLabelPosition(line, label, sizes, chartArea) { + const {width, height, padding} = sizes; + const {xAdjust, yAdjust} = label; const p1 = {x: line.x, y: line.y}; const p2 = {x: line.x2, y: line.y2}; const rotation = label.rotation === 'auto' ? calculateAutoRotation(line) : toRadians$1(label.rotation); const size = rotatedSize(width, height, rotation); - const t = calculateT(line, position, size, chartArea); + const t = calculateT(line, label, {labelSize: size, padding}, chartArea); const pt = pointInLine(p1, p2, t); - const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: xPadding}; - const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: yPadding}; + const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: padding.left}; + const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: padding.top}; return { x: adjustLabelCoordinate(pt.x, xCoordinateSizes) + xAdjust, @@ -649,24 +1106,25 @@ function rotatedSize(width, height, rotation) { }; } -function calculateT(line, position, rotSize, chartArea) { - let t = 0.5; +function calculateT(line, label, sizes, chartArea) { + let t; const space = spaceAround(line, chartArea); - const label = line.options.label; - if (position === 'start') { - t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, rotSize, label, space); - } else if (position === 'end') { - t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, rotSize, label, space); + if (label.position === 'start') { + t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, sizes, label, space); + } else if (label.position === 'end') { + t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, sizes, label, space); + } else { + t = getRelativePosition(1, label.position); } return t; } -function calculateTAdjust(lineSize, labelSize, label, space) { - const {xPadding, yPadding} = label; +function calculateTAdjust(lineSize, sizes, label, space) { + const {labelSize, padding} = sizes; const lineW = lineSize.w * space.dx; const lineH = lineSize.h * space.dy; - const x = (lineW > 0) && ((labelSize.w / 2 + xPadding - space.x) / lineW); - const y = (lineH > 0) && ((labelSize.h / 2 + yPadding - space.y) / lineH); + const x = (lineW > 0) && ((labelSize.w / 2 + padding.left - space.x) / lineW); + const y = (lineH > 0) && ((labelSize.h / 2 + padding.top - space.y) / lineH); return clamp(Math.max(x, y), 0, 0.25); } @@ -679,37 +1137,83 @@ function spaceAround(line, chartArea) { return { x: Math.min(l, r), y: Math.min(t, b), - dx: l < r ? 1 : -1, - dy: t < b ? 1 : -1 + dx: l <= r ? 1 : -1, + dy: t <= b ? 1 : -1 }; } function adjustLabelCoordinate(coordinate, labelSizes) { const {size, min, max, padding} = labelSizes; const halfSize = size / 2; - if (size > max - min) { // if it does not fit, display as much as possible return (max + min) / 2; } - if (min >= (coordinate - padding - halfSize)) { coordinate = min + padding + halfSize; } - if (max <= (coordinate + padding + halfSize)) { coordinate = max - padding - halfSize; } + return coordinate; +} + +function getArrowHeads(line) { + const options = line.options; + const arrowStartOpts = options.arrowHeads && options.arrowHeads.start; + const arrowEndOpts = options.arrowHeads && options.arrowHeads.end; + return { + startOpts: arrowStartOpts, + endOpts: arrowEndOpts, + startAdjust: getLineAdjust(line, arrowStartOpts), + endAdjust: getLineAdjust(line, arrowEndOpts) + }; +} + +function getLineAdjust(line, arrowOpts) { + if (!arrowOpts || !arrowOpts.enabled) { + return 0; + } + const {length, width} = arrowOpts; + const adjust = line.options.borderWidth / 2; + const p1 = {x: length, y: width + adjust}; + const p2 = {x: 0, y: adjust}; + return Math.abs(interpolateX(0, p1, p2)); +} - return coordinate; +function drawArrowHead(ctx, offset, adjust, arrowOpts) { + if (!arrowOpts || !arrowOpts.enabled) { + return; + } + const {length, width, fill, backgroundColor, borderColor} = arrowOpts; + const arrowOffsetX = Math.abs(offset - length) + adjust; + ctx.beginPath(); + setShadowStyle(ctx, arrowOpts); + setBorderStyle(ctx, arrowOpts); + ctx.moveTo(arrowOffsetX, -width); + ctx.lineTo(offset + adjust, 0); + ctx.lineTo(arrowOffsetX, width); + if (fill === true) { + ctx.fillStyle = backgroundColor || borderColor; + ctx.closePath(); + ctx.fill(); + ctx.shadowColor = 'transparent'; + } else { + ctx.shadowColor = arrowOpts.borderShadowColor; + } + ctx.stroke(); } -const {toRadians} = ChartJsV3__default['default'].helpers; +const {PI: PI$1, toRadians} = ChartJsV3__default["default"].helpers; -class EllipseAnnotation extends BoxAnnotation { +class EllipseAnnotation extends ChartJsV3.Element { - inRange(x, y) { - return pointInEllipse({x, y}, this); + inRange(mouseX, mouseY, useFinalPosition) { + return pointInEllipse({x: mouseX, y: mouseY}, this.getProps(['width', 'height'], useFinalPosition), this.options.rotation, this.options.borderWidth); + } + + getCenterPoint(useFinalPosition) { + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); } draw(ctx) { @@ -722,40 +1226,45 @@ class EllipseAnnotation extends BoxAnnotation { if (options.rotation) { ctx.rotate(toRadians(options.rotation)); } - + setShadowStyle(ctx, this.options); ctx.beginPath(); - - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; ctx.fillStyle = options.backgroundColor; - - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; - - ctx.ellipse(0, 0, height / 2, width / 2, Math.PI / 2, 0, 2 * Math.PI); - + const stroke = setBorderStyle(ctx, options); + ctx.ellipse(0, 0, height / 2, width / 2, PI$1 / 2, 0, 2 * PI$1); ctx.fill(); - ctx.stroke(); - + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } ctx.restore(); } + + resolveElementProperties(chart, options) { + return getChartRect(chart, options); + } + } EllipseAnnotation.id = 'ellipseAnnotation'; EllipseAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', borderWidth: 1, + display: true, rotation: 0, - xScaleID: 'x', - xMin: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' }; EllipseAnnotation.defaultRoutes = { @@ -763,7 +1272,7 @@ EllipseAnnotation.defaultRoutes = { backgroundColor: 'color' }; -function pointInEllipse(p, ellipse) { +function pointInEllipse(p, ellipse, rotation, borderWidth) { const {width, height} = ellipse; const center = ellipse.getCenterPoint(true); const xRadius = width / 2; @@ -772,84 +1281,302 @@ function pointInEllipse(p, ellipse) { if (xRadius <= 0 || yRadius <= 0) { return false; } - - return (Math.pow(p.x - center.x, 2) / Math.pow(xRadius, 2)) + (Math.pow(p.y - center.y, 2) / Math.pow(yRadius, 2)) <= 1.0; + // https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm + const angle = toRadians(rotation || 0); + const hBorderWidth = borderWidth / 2 || 0; + const cosAngle = Math.cos(angle); + const sinAngle = Math.sin(angle); + const a = Math.pow(cosAngle * (p.x - center.x) + sinAngle * (p.y - center.y), 2); + const b = Math.pow(sinAngle * (p.x - center.x) - cosAngle * (p.y - center.y), 2); + return (a / Math.pow(xRadius + hBorderWidth, 2)) + (b / Math.pow(yRadius + hBorderWidth, 2)) <= 1.0001; } -class PointAnnotation extends ChartJsV3.Element { - - inRange(x, y) { - const {width, options} = this; - const center = this.getCenterPoint(true); - const radius = width / 2 + options.borderWidth; +const {color, toPadding} = ChartJsV3__default["default"].helpers; - if (radius <= 0) { - return false; - } +class LabelAnnotation extends ChartJsV3.Element { - return (Math.pow(x - center.x, 2) + Math.pow(y - center.y, 2)) <= Math.pow(radius, 2); + inRange(mouseX, mouseY, useFinalPosition) { + return inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition), this.options.borderWidth); } getCenterPoint(useFinalPosition) { - const {x, y} = this.getProps(['x', 'y'], useFinalPosition); - return {x, y}; + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); } draw(ctx) { - const {x, y, width, options} = this; + if (!this.options.content) { + return; + } + const {labelX, labelY, labelWidth, labelHeight, options} = this; + drawCallout(ctx, this); + if (this.boxVisible) { + drawBox(ctx, this, options); + } + drawLabel(ctx, {x: labelX, y: labelY, width: labelWidth, height: labelHeight}, options); + } - ctx.save(); + // TODO: make private in v2 + resolveElementProperties(chart, options) { + const point = !isBoundToPoint(options) ? getRectCenterPoint(getChartRect(chart, options)) : getChartPoint(chart, options); + const padding = toPadding(options.padding); + const labelSize = measureLabelSize(chart.ctx, options); + const boxSize = measureRect(point, labelSize, options, padding); + const bgColor = color(options.backgroundColor); + const boxVisible = options.borderWidth > 0 || (bgColor && bgColor.valid && bgColor.rgb.a > 0); + + const properties = { + boxVisible, + pointX: point.x, + pointY: point.y, + ...boxSize, + labelX: boxSize.x + padding.left + (options.borderWidth / 2), + labelY: boxSize.y + padding.top + (options.borderWidth / 2), + labelWidth: labelSize.width, + labelHeight: labelSize.height + }; + properties.calloutPosition = options.callout.enabled && resolveCalloutPosition(properties, options.callout); + return properties; + } +} - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.fillStyle = options.backgroundColor; +LabelAnnotation.id = 'labelAnnotation'; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; +LabelAnnotation.defaults = { + adjustScaleRange: true, + backgroundColor: 'transparent', + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 0, + borderShadowColor: 'transparent', + borderWidth: 0, + callout: { + borderCapStyle: 'butt', + borderColor: undefined, + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 1, + enabled: false, + margin: 5, + position: 'auto', + side: 5, + start: '50%', + }, + color: 'black', + content: null, + display: true, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: undefined + }, + height: undefined, + padding: 6, + position: 'center', + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + width: undefined, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: 'x', + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: 'y', + yValue: undefined +}; - ctx.beginPath(); - ctx.arc(x, y, width / 2, 0, Math.PI * 2); - ctx.fill(); - ctx.stroke(); +LabelAnnotation.defaultRoutes = { + borderColor: 'color' +}; - ctx.restore(); +function measureRect(point, size, options, padding) { + const width = size.width + padding.width + options.borderWidth; + const height = size.height + padding.height + options.borderWidth; + const position = toPosition(options.position); + + return { + x: calculatePosition(point.x, width, options.xAdjust, position.x), + y: calculatePosition(point.y, height, options.yAdjust, position.y), + width, + height + }; +} + +function calculatePosition(start, size, adjust = 0, position) { + return start - getRelativePosition(size, position) + adjust; +} + +function drawCallout(ctx, element) { + const {pointX, pointY, calloutPosition, options} = element; + if (!calloutPosition) { + return; } + const callout = options.callout; - resolveElementProperties(chart, options) { - const {chartArea, scales} = chart; - const xScale = scales[options.xScaleID]; - const yScale = scales[options.yScaleID]; - let x = chartArea.width / 2; - let y = chartArea.height / 2; - - if (xScale) { - x = scaleValue(xScale, options.xValue, x); - } + ctx.save(); + ctx.beginPath(); + const stroke = setBorderStyle(ctx, callout); + if (!stroke) { + return ctx.restore(); + } + const {separatorStart, separatorEnd} = getCalloutSeparatorCoord(element, calloutPosition); + const {sideStart, sideEnd} = getCalloutSideCoord(element, calloutPosition, separatorStart); + if (callout.margin > 0 || options.borderWidth === 0) { + ctx.moveTo(separatorStart.x, separatorStart.y); + ctx.lineTo(separatorEnd.x, separatorEnd.y); + } + ctx.moveTo(sideStart.x, sideStart.y); + ctx.lineTo(sideEnd.x, sideEnd.y); + ctx.lineTo(pointX, pointY); + ctx.stroke(); + ctx.restore(); +} - if (yScale) { - y = scaleValue(yScale, options.yValue, y); +function getCalloutSeparatorCoord(element, position) { + const {x, y, width, height} = element; + const adjust = getCalloutSeparatorAdjust(element, position); + let separatorStart, separatorEnd; + if (position === 'left' || position === 'right') { + separatorStart = {x: x + adjust, y}; + separatorEnd = {x: separatorStart.x, y: separatorStart.y + height}; + } else { + // position 'top' or 'bottom' + separatorStart = {x, y: y + adjust}; + separatorEnd = {x: separatorStart.x + width, y: separatorStart.y}; + } + return {separatorStart, separatorEnd}; +} + +function getCalloutSeparatorAdjust(element, position) { + const {width, height, options} = element; + const adjust = options.callout.margin + options.borderWidth / 2; + if (position === 'right') { + return width + adjust; + } else if (position === 'bottom') { + return height + adjust; + } + return -adjust; +} + +function getCalloutSideCoord(element, position, separatorStart) { + const {y, width, height, options} = element; + const start = options.callout.start; + const side = getCalloutSideAdjust(position, options.callout); + let sideStart, sideEnd; + if (position === 'left' || position === 'right') { + sideStart = {x: separatorStart.x, y: y + getSize(height, start)}; + sideEnd = {x: sideStart.x + side, y: sideStart.y}; + } else { + // position 'top' or 'bottom' + sideStart = {x: separatorStart.x + getSize(width, start), y: separatorStart.y}; + sideEnd = {x: sideStart.x, y: sideStart.y + side}; + } + return {sideStart, sideEnd}; +} + +function getCalloutSideAdjust(position, options) { + const side = options.side; + if (position === 'left' || position === 'top') { + return -side; + } + return side; +} + +function resolveCalloutPosition(element, options) { + const position = options.position; + if (position === 'left' || position === 'right' || position === 'top' || position === 'bottom') { + return position; + } + return resolveCalloutAutoPosition(element, options); +} + +function resolveCalloutAutoPosition(element, options) { + const {x, y, width, height, pointX, pointY} = element; + const {margin, side} = options; + const adjust = margin + side; + if (pointX < (x - adjust)) { + return 'left'; + } else if (pointX > (x + width + adjust)) { + return 'right'; + } else if (pointY < (y - adjust)) { + return 'top'; + } else if (pointY > (y + height + adjust)) { + return 'bottom'; + } +} + +const {drawPoint} = ChartJsV3__default["default"].helpers; + +class PointAnnotation extends ChartJsV3.Element { + + inRange(mouseX, mouseY, useFinalPosition) { + const {width} = this.getProps(['width'], useFinalPosition); + return inPointRange({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), width / 2, this.options.borderWidth); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + const borderWidth = options.borderWidth; + if (options.radius < 0.1) { + return; + } + ctx.save(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + options.borderWidth = 0; + drawPoint(ctx, options, this.x, this.y); + if (stroke && !isImageOrCanvas(options.pointStyle)) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); } + ctx.restore(); + options.borderWidth = borderWidth; + } - return { - x, - y, - width: options.radius * 2, - height: options.radius * 2 - }; + resolveElementProperties(chart, options) { + return resolvePointPosition(chart, options); } } PointAnnotation.id = 'pointAnnotation'; PointAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', borderWidth: 1, + display: true, + pointStyle: 'circle', radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xAdjust: 0, + xMax: undefined, + xMin: undefined, xScaleID: 'x', xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, yScaleID: 'y', yValue: undefined }; @@ -859,32 +1586,175 @@ PointAnnotation.defaultRoutes = { backgroundColor: 'color' }; -var version = "1.0.2"; +const {PI, RAD_PER_DEG} = ChartJsV3__default["default"].helpers; -const {clipArea, unclipArea, isFinite, valueOrDefault, isObject, isArray} = ChartJsV3__default['default'].helpers; +class PolygonAnnotation extends ChartJsV3.Element { + inRange(mouseX, mouseY, useFinalPosition) { + return this.options.radius >= 0.1 && this.elements.length > 1 && pointIsInPolygon(this.elements, mouseX, mouseY, useFinalPosition); + } -const chartStates = new Map(); + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {elements, options} = this; + ctx.save(); + ctx.beginPath(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + let first = true; + for (const el of elements) { + if (first) { + ctx.moveTo(el.x, el.y); + first = false; + } else { + ctx.lineTo(el.x, el.y); + } + } + ctx.closePath(); + ctx.fill(); + // If no border, don't draw it + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + } + + resolveElementProperties(chart, options) { + const {x, y, width, height} = resolvePointPosition(chart, options); + const {sides, radius, rotation, borderWidth} = options; + const halfBorder = borderWidth / 2; + const elements = []; + const angle = (2 * PI) / sides; + let rad = rotation * RAD_PER_DEG; + for (let i = 0; i < sides; i++, rad += angle) { + const sin = Math.sin(rad); + const cos = Math.cos(rad); + elements.push({ + type: 'point', + optionScope: 'point', + properties: { + x: x + sin * radius, + y: y - cos * radius, + bX: x + sin * (radius + halfBorder), + bY: y - cos * (radius + halfBorder) + } + }); + } + return {x, y, width, height, elements, initProperties: {x, y}}; + } +} + +PolygonAnnotation.id = 'polygonAnnotation'; + +PolygonAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + point: { + radius: 0 + }, + radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + sides: 3, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: 'x', + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: 'y', + yValue: undefined +}; + +PolygonAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + + +function pointIsInPolygon(points, x, y, useFinalPosition) { + let isInside = false; + let A = points[points.length - 1].getProps(['bX', 'bY'], useFinalPosition); + for (const point of points) { + const B = point.getProps(['bX', 'bY'], useFinalPosition); + if ((B.bY > y) !== (A.bY > y) && x < (A.bX - B.bX) * (y - B.bY) / (A.bY - B.bY) + B.bX) { + isInside = !isInside; + } + A = B; + } + return isInside; +} const annotationTypes = { box: BoxAnnotation, - line: LineAnnotation, ellipse: EllipseAnnotation, - point: PointAnnotation + label: LabelAnnotation, + line: LineAnnotation, + point: PointAnnotation, + polygon: PolygonAnnotation }; +/** + * Register fallback for annotation elements + * For example lineAnnotation options would be looked through: + * - the annotation object (options.plugins.annotation.annotations[id]) + * - element options (options.elements.lineAnnotation) + * - element defaults (defaults.elements.lineAnnotation) + * - annotation plugin defaults (defaults.plugins.annotation, this is what we are registering here) + */ Object.keys(annotationTypes).forEach(key => { ChartJsV3.defaults.describe(`elements.${annotationTypes[key].id}`, { _fallback: 'plugins.annotation' }); }); +var name = "chartjs-plugin-annotation"; +var version = "1.2.2"; + +const {clipArea, unclipArea, isObject, isArray} = ChartJsV3__default["default"].helpers; + +const chartStates = new Map(); + var Annotation = { id: 'annotation', version, + /* TODO: enable in v2 + beforeRegister() { + requireVersion('chart.js', '3.7', Chart.version); + }, + */ + afterRegister() { ChartJsV3.Chart.register(annotationTypes); + + // TODO: Remove this check, warning and workaround in v2 + if (!requireVersion('chart.js', '3.7', ChartJsV3.Chart.version, false)) { + console.warn(`${name} has known issues with chart.js versions prior to 3.7, please consider upgrading.`); + + // Workaround for https://github.com/chartjs/chartjs-plugin-annotation/issues/572 + ChartJsV3.Chart.defaults.set('elements.lineAnnotation', { + callout: {}, + font: {}, + padding: 6 + }); + } }, afterUnregister() { @@ -895,6 +1765,7 @@ var Annotation = { chartStates.set(chart, { annotations: [], elements: [], + visibleElements: [], listeners: {}, listened: false, moveListened: false @@ -917,6 +1788,7 @@ var Annotation = { } else if (isArray(annotationOptions)) { annotations.push(...annotationOptions); } + verifyScaleOptions(annotations, chart.scales); }, afterDataLimits(chart, args) { @@ -928,27 +1800,28 @@ var Annotation = { const state = chartStates.get(chart); updateListeners(chart, state, options); updateElements(chart, state, options, args.mode); + state.visibleElements = state.elements.filter(el => !el.skip && el.options.display); }, - beforeDatasetsDraw(chart) { - draw(chart, 'beforeDatasetsDraw'); + beforeDatasetsDraw(chart, _args, options) { + draw(chart, 'beforeDatasetsDraw', options.clip); }, - afterDatasetsDraw(chart) { - draw(chart, 'afterDatasetsDraw'); + afterDatasetsDraw(chart, _args, options) { + draw(chart, 'afterDatasetsDraw', options.clip); }, - beforeDraw(chart) { - draw(chart, 'beforeDraw'); + beforeDraw(chart, _args, options) { + draw(chart, 'beforeDraw', options.clip); }, - afterDraw(chart) { - draw(chart, 'afterDraw'); + afterDraw(chart, _args, options) { + draw(chart, 'afterDraw', options.clip); }, beforeEvent(chart, args, options) { const state = chartStates.get(chart); - handleEvent(chart, state, args.event, options); + handleEvent(state, args.event, options); }, destroy(chart) { @@ -960,14 +1833,15 @@ var Annotation = { }, defaults: { - drawTime: 'afterDatasetsDraw', - dblClickSpeed: 350, // ms animations: { numbers: { - properties: ['x', 'y', 'x2', 'y2', 'width', 'height'], + properties: ['x', 'y', 'x2', 'y2', 'width', 'height', 'pointX', 'pointY', 'labelX', 'labelY', 'labelWidth', 'labelHeight', 'radius'], type: 'number' }, }, + clip: true, + dblClickSpeed: 350, // ms + drawTime: 'afterDatasetsDraw', label: { drawTime: null } @@ -978,7 +1852,7 @@ var Annotation = { _scriptable: (prop) => !hooks.includes(prop), annotations: { _allKeys: false, - _fallback: (prop, opts) => `elements.${annotationTypes[opts.type || 'line'].id}`, + _fallback: (prop, opts) => `elements.${annotationTypes[resolveType(opts.type)].id}`, }, }, @@ -996,6 +1870,14 @@ function resolveAnimations(chart, animOpts, mode) { return new ChartJsV3.Animations(chart, animOpts); } +function resolveType(type = 'line') { + if (annotationTypes[type]) { + return type; + } + console.warn(`Unknown annotation type: '${type}', defaulting to 'line'`); + return 'line'; +} + function updateElements(chart, state, options, mode) { const animations = resolveAnimations(chart, options.animations, mode); @@ -1003,27 +1885,60 @@ function updateElements(chart, state, options, mode) { const elements = resyncElements(state.elements, annotations); for (let i = 0; i < annotations.length; i++) { - const annotation = annotations[i]; - let el = elements[i]; - const elType = annotationTypes[annotation.type] || annotationTypes.line; - if (!el || !(el instanceof elType)) { - el = elements[i] = new elType(); - } - const opts = resolveAnnotationOptions(annotation.setContext(getContext(chart, el, annotation))); - const properties = el.resolveElementProperties(chart, opts); + const annotationOptions = annotations[i]; + const element = getOrCreateElement(elements, i, annotationOptions.type); + const resolver = annotationOptions.setContext(getContext(chart, element, annotationOptions)); + const resolvedOptions = resolveAnnotationOptions(resolver); + const properties = element.resolveElementProperties(chart, resolvedOptions); + properties.skip = isNaN(properties.x) || isNaN(properties.y); - properties.options = opts; - animations.update(el, properties); + properties.options = resolvedOptions; + + if ('elements' in properties) { + updateSubElements(element, properties, resolver, animations); + // Remove the sub-element definitions from properties, so the actual elements + // are not overwritten by their definitions + delete properties.elements; + } + + animations.update(element, properties); + } +} + +function updateSubElements(mainElement, {elements, initProperties}, resolver, animations) { + const subElements = mainElement.elements || (mainElement.elements = []); + subElements.length = elements.length; + for (let i = 0; i < elements.length; i++) { + const definition = elements[i]; + const properties = definition.properties; + const subElement = getOrCreateElement(subElements, i, definition.type, initProperties); + const subResolver = resolver[definition.optionScope].override(definition); + properties.options = resolveAnnotationOptions(subResolver); + animations.update(subElement, properties); } } +function getOrCreateElement(elements, index, type, initProperties) { + const elementClass = annotationTypes[resolveType(type)]; + let element = elements[index]; + if (!element || !(element instanceof elementClass)) { + element = elements[index] = new elementClass(); + if (isObject(initProperties)) { + Object.assign(element, initProperties); + } + } + return element; +} + function resolveAnnotationOptions(resolver) { - const elType = annotationTypes[resolver.type] || annotationTypes.line; + const elementClass = annotationTypes[resolveType(resolver.type)]; const result = {}; result.id = resolver.id; result.type = resolver.type; result.drawTime = resolver.drawTime; - Object.assign(result, resolveObj(resolver, elType.defaults), resolveObj(resolver, elType.defaultRoutes)); + Object.assign(result, + resolveObj(resolver, elementClass.defaults), + resolveObj(resolver, elementClass.defaultRoutes)); for (const hook of hooks) { result[hook] = resolver[hook]; } @@ -1032,10 +1947,10 @@ function resolveAnnotationOptions(resolver) { function resolveObj(resolver, defs) { const result = {}; - for (const name of Object.keys(defs)) { - const optDefs = defs[name]; - const value = resolver[name]; - result[name] = isObject(optDefs) ? resolveObj(value, optDefs) : value; + for (const prop of Object.keys(defs)) { + const optDefs = defs[prop]; + const value = resolver[prop]; + result[prop] = isObject(optDefs) ? resolveObj(value, optDefs) : value; } return result; } @@ -1061,78 +1976,50 @@ function resyncElements(elements, annotations) { return elements; } -function draw(chart, caller) { +function draw(chart, caller, clip) { const {ctx, chartArea} = chart; - const state = chartStates.get(chart); - const elements = state.elements.filter(el => !el.skip && el.options.display); + const {visibleElements} = chartStates.get(chart); - clipArea(ctx, chartArea); - elements.forEach(el => { - if (el.options.drawTime === caller) { - el.draw(ctx); - } - }); - unclipArea(ctx); + if (clip) { + clipArea(ctx, chartArea); + } + + drawElements(ctx, visibleElements, caller); + drawSubElements(ctx, visibleElements, caller); + + if (clip) { + unclipArea(ctx); + } - elements.forEach(el => { - if ('drawLabel' in el && el.options.label && (el.options.label.drawTime || el.options.drawTime) === caller) { + visibleElements.forEach(el => { + if (!('drawLabel' in el)) { + return; + } + const label = el.options.label; + if (label && label.enabled && label.content && (label.drawTime || el.options.drawTime) === caller) { el.drawLabel(ctx, chartArea); } }); } -function adjustScaleRange(chart, scale, annotations) { - const range = getScaleLimits(scale, annotations); - let changed = false; - if (isFinite(range.min) && - typeof scale.options.min === 'undefined' && - typeof scale.options.suggestedMin === 'undefined') { - changed = scale.min !== range.min; - scale.min = range.min; - } - if (isFinite(range.max) && - typeof scale.options.max === 'undefined' && - typeof scale.options.suggestedMax === 'undefined') { - changed = scale.max !== range.max; - scale.max = range.max; - } - if (changed && typeof scale.handleTickRangeOptions === 'function') { - scale.handleTickRangeOptions(); +function drawElements(ctx, elements, caller) { + for (const el of elements) { + if (el.options.drawTime === caller) { + el.draw(ctx); + } } } -function getScaleLimits(scale, annotations) { - const axis = scale.axis; - const scaleID = scale.id; - const scaleIDOption = axis + 'ScaleID'; - let min = valueOrDefault(scale.min, Number.NEGATIVE_INFINITY); - let max = valueOrDefault(scale.max, Number.POSITIVE_INFINITY); - for (const annotation of annotations) { - if (annotation.scaleID === scaleID) { - for (const prop of ['value', 'endValue']) { - const raw = annotation[prop]; - if (raw) { - const value = scale.parse(raw); - min = Math.min(min, value); - max = Math.max(max, value); - } - } - } else if (annotation[scaleIDOption] === scaleID) { - for (const prop of [axis + 'Min', axis + 'Max', axis + 'Value']) { - const raw = annotation[prop]; - if (raw) { - const value = scale.parse(raw); - min = Math.min(min, value); - max = Math.max(max, value); - } - } +function drawSubElements(ctx, elements, caller) { + for (const el of elements) { + if (isArray(el.elements)) { + drawElements(ctx, el.elements, caller); } } - return {min, max}; } ChartJsV3.Chart.register(Annotation); return Annotation; -}))); +})); diff --git a/dist/chartjs-plugin-annotation.min.js b/dist/chartjs-plugin-annotation.min.js index fbd240324..82fe16f7b 100644 --- a/dist/chartjs-plugin-annotation.min.js +++ b/dist/chartjs-plugin-annotation.min.js @@ -1,7 +1,7 @@ /*! -* chartjs-plugin-annotation v1.0.2 +* chartjs-plugin-annotation v1.2.2 * https://www.chartjs.org/chartjs-plugin-annotation/index - * (c) 2021 chartjs-plugin-annotation Contributors + * (c) 2022 chartjs-plugin-annotation Contributors * Released under the MIT License */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("chart.js-v3"),require("canvas")):"function"==typeof define&&define.amd?define(["chart.js-v3","canvas"],t):(e="undefined"!=typeof globalThis?globalThis:e||self)["chartjs-plugin-annotation"]=t(e.ChartJsV3,e.canvas)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=n(e);const{distanceBetweenPoints:i}=o.default.helpers,a=o.default.helpers.callback,s=["click","dblclick"],r=["enter","leave"],l=s.concat(r);function d(e,t,n,o){if(t.listened)switch(n.type){case"mousemove":case"mouseout":!function(e,t,n){if(!t.moveListened)return;let o;"mousemove"===n.type&&(o=h(t.elements,n));const i=t.hovered;t.hovered=o,function(e,t,n,o){const{previous:i,element:a}=n;i&&i!==a&&c(e,i.options.leave||t.listeners.leave,i,o);a&&a!==i&&c(e,a.options.enter||t.listeners.enter,a,o)}(e,t,{previous:i,element:o},n)}(e,t,n);break;case"click":!function(e,t,n,o){const i=t.listeners,a=h(t.elements,n);if(a){const t=a.options,s=t.dblclick||i.dblclick,r=t.click||i.click;a.clickTimeout?(clearTimeout(a.clickTimeout),delete a.clickTimeout,c(e,s,a,n)):s?a.clickTimeout=setTimeout((()=>{delete a.clickTimeout,c(e,r,a,n)}),o.dblClickSpeed):c(e,r,a,n)}}(e,t,n,o)}}function c(e,t,n,o){a(t,[{chart:e,element:n},o])}function h(e,t){let n=Number.POSITIVE_INFINITY;return e.filter((e=>e.options.display&&e.inRange(t.x,t.y))).reduce(((e,o)=>{const a=o.getCenterPoint(),s=i(t,a);return se._index-t._index)).slice(0,1)[0]}const{isFinite:f}=o.default.helpers,u=(e,t,n)=>Math.min(n,Math.max(t,e));function x(e,t,n){for(const o of Object.keys(e))e[o]=u(e[o],t,n);return e}function y(e,t,n){return t="number"==typeof t?t:e.parse(t),f(t)?e.getPixelForValue(t):n}const{addRoundedRectPath:b,toTRBLCorners:p,valueOrDefault:g}=o.default.helpers;class m extends e.Element{inRange(e,t,n){const{x:o,y:i,width:a,height:s}=this.getProps(["x","y","width","height"],n);return e>=o&&e<=o+a&&t>=i&&t<=i+s}getCenterPoint(e){const{x:t,y:n,width:o,height:i}=this.getProps(["x","y","width","height"],e);return{x:t+o/2,y:n+i/2}}draw(e){const{x:t,y:n,width:o,height:i,options:a}=this;e.save(),e.lineWidth=a.borderWidth,e.strokeStyle=a.borderColor,e.fillStyle=a.backgroundColor,e.setLineDash(a.borderDash),e.lineDashOffset=a.borderDashOffset,e.beginPath(),b(e,{x:t,y:n,w:o,h:i,radius:x(p(g(a.cornerRadius,a.borderRadius)),0,Math.min(o,i)/2)}),e.closePath(),e.fill(),a.borderWidth&&e.stroke(),e.restore()}resolveElementProperties(e,t){const n=e.scales[t.xScaleID],o=e.scales[t.yScaleID];let i,a,{top:s,left:r,bottom:l,right:d}=e.chartArea;return n||o?(n&&(i=y(n,t.xMin,r),a=y(n,t.xMax,d),r=Math.min(i,a),d=Math.max(i,a)),o&&(i=y(o,t.yMin,l),a=y(o,t.yMax,s),s=Math.min(i,a),l=Math.max(i,a)),{x:r,y:s,x2:d,y2:l,width:d-r,height:l-s}):{options:{}}}}m.id="boxAnnotation",m.defaults={display:!0,adjustScaleRange:!0,borderDash:[],borderDashOffset:0,borderWidth:1,borderRadius:0,xScaleID:"x",xMin:void 0,xMax:void 0,yScaleID:"y",yMin:void 0,yMax:void 0},m.defaultRoutes={borderColor:"color",backgroundColor:"color"};const{addRoundedRectPath:w,isArray:M,toFontString:v,toRadians:D,toTRBLCorners:P,valueOrDefault:k}=o.default.helpers,I=Math.PI,S=(e,t,n)=>({x:e.x+n*(t.x-e.x),y:e.y+n*(t.y-e.y)}),C=(e,t,n)=>S(t,n,Math.abs((e-t.y)/(n.y-t.y))).x,O=(e,t,n)=>S(t,n,Math.abs((e-t.x)/(n.x-t.x))).y;function R({x:e,y:t,x2:n,y2:o},{top:i,right:a,bottom:s,left:r}){return!(ea&&n>a||ts&&o>s)}function T({x:e,y:t},n,{top:o,right:i,bottom:a,left:s}){return ei&&(t=O(i,{x:e,y:t},n),e=i),ta&&(e=C(a,{x:e,y:t},n),t=a),{x:e,y:t}}class A extends e.Element{intersects(e,t,n=.001){const o=e=>e*e,{x:i,y:a,x2:s,y2:r}=this,l=s-i,d=r-a,c=o(l)+o(d),h=0===c?-1:((e-i)*l+(t-a)*d)/c;let f,u;return h<0?(f=i,u=a):h>1?(f=s,u=r):(f=i+h*l,u=a+h*d),o(e-f)+o(t-u)=n.x-f&&o<=n.x+f&&i>=n.y-u&&i<=n.y+u}inRange(e,t){const n=this.options.borderWidth||1;return this.intersects(e,t,n)||this.isOnLabel(e,t)}getCenterPoint(){return{x:(this.x2+this.x)/2,y:(this.y2+this.y)/2}}draw(e){const{x:t,y:n,x2:o,y2:i,options:a}=this;e.save(),e.lineWidth=a.borderWidth,e.strokeStyle=a.borderColor,e.setLineDash(a.borderDash),e.lineDashOffset=a.borderDashOffset,e.beginPath(),e.moveTo(t,n),e.lineTo(o,i),e.stroke(),e.restore()}drawLabel(e,n){this.labelIsVisible(n)&&(e.save(),function(e,n,o){const i=n.options.label;e.font=v(i.font);const{width:a,height:s}=function(e,n){const o=n.content;if(o instanceof t.Image)return{width:j(o.width,n.width)+2*n.xPadding,height:j(o.height,n.height)+2*n.yPadding};const i=M(o)?o:[o],a=i.length;let s=0;for(let t=0;tI/2?a-I:a0&&(t.w/2+i-o.x)/s,d=r>0&&(t.h/2+a-o.y)/r;return u(Math.max(l,d),0,.25)}function W(e,t){const{size:n,min:o,max:i,padding:a}=t,s=n/2;return n>i-o?(i+o)/2:(o>=e-a-s&&(e=o+a+s),i<=e+a+s&&(e=i-a-s),e)}const{toRadians:N}=o.default.helpers;class V extends m{inRange(e,t){return function(e,t){const{width:n,height:o}=t,i=t.getCenterPoint(!0),a=n/2,s=o/2;if(a<=0||s<=0)return!1;return Math.pow(e.x-i.x,2)/Math.pow(a,2)+Math.pow(e.y-i.y,2)/Math.pow(s,2)<=1}({x:e,y:t},this)}draw(e){const{width:t,height:n,options:o}=this,i=this.getCenterPoint();e.save(),e.translate(i.x,i.y),o.rotation&&e.rotate(N(o.rotation)),e.beginPath(),e.lineWidth=o.borderWidth,e.strokeStyle=o.borderColor,e.fillStyle=o.backgroundColor,e.setLineDash(o.borderDash),e.lineDashOffset=o.borderDashOffset,e.ellipse(0,0,n/2,t/2,Math.PI/2,0,2*Math.PI),e.fill(),e.stroke(),e.restore()}}V.id="ellipseAnnotation",V.defaults={display:!0,adjustScaleRange:!0,borderDash:[],borderDashOffset:0,borderWidth:1,rotation:0,xScaleID:"x",xMin:void 0,xMax:void 0,yScaleID:"y",yMin:void 0,yMax:void 0},V.defaultRoutes={borderColor:"color",backgroundColor:"color"};class _ extends e.Element{inRange(e,t){const{width:n,options:o}=this,i=this.getCenterPoint(!0),a=n/2+o.borderWidth;return!(a<=0)&&Math.pow(e-i.x,2)+Math.pow(t-i.y,2)<=Math.pow(a,2)}getCenterPoint(e){const{x:t,y:n}=this.getProps(["x","y"],e);return{x:t,y:n}}draw(e){const{x:t,y:n,width:o,options:i}=this;e.save(),e.lineWidth=i.borderWidth,e.strokeStyle=i.borderColor,e.fillStyle=i.backgroundColor,e.setLineDash(i.borderDash),e.lineDashOffset=i.borderDashOffset,e.beginPath(),e.arc(t,n,o/2,0,2*Math.PI),e.fill(),e.stroke(),e.restore()}resolveElementProperties(e,t){const{chartArea:n,scales:o}=e,i=o[t.xScaleID],a=o[t.yScaleID];let s=n.width/2,r=n.height/2;return i&&(s=y(i,t.xValue,s)),a&&(r=y(a,t.yValue,r)),{x:s,y:r,width:2*t.radius,height:2*t.radius}}}_.id="pointAnnotation",_.defaults={display:!0,adjustScaleRange:!0,borderDash:[],borderDashOffset:0,borderWidth:1,radius:10,xScaleID:"x",xValue:void 0,yScaleID:"y",yValue:void 0},_.defaultRoutes={borderColor:"color",backgroundColor:"color"};const{clipArea:z,unclipArea:F,isFinite:B,valueOrDefault:J,isObject:$,isArray:U}=o.default.helpers,Y=new Map,q={box:m,line:A,ellipse:V,point:_};Object.keys(q).forEach((t=>{e.defaults.describe(`elements.${q[t].id}`,{_fallback:"plugins.annotation"})}));var H={id:"annotation",version:"1.0.2",afterRegister(){e.Chart.register(q)},afterUnregister(){e.Chart.unregister(q)},beforeInit(e){Y.set(e,{annotations:[],elements:[],listeners:{},listened:!1,moveListened:!1})},beforeUpdate(e,t,n){const o=Y.get(e).annotations=[];let i=n.annotations;$(i)?Object.keys(i).forEach((e=>{const t=i[e];$(t)&&(t.id=e,o.push(t))})):U(i)&&o.push(...i)},afterDataLimits(e,t){const n=Y.get(e);!function(e,t,n){const o=function(e,t){const n=e.axis,o=e.id,i=n+"ScaleID";let a=J(e.min,Number.NEGATIVE_INFINITY),s=J(e.max,Number.POSITIVE_INFINITY);for(const r of t)if(r.scaleID===o)for(const t of["value","endValue"]){const n=r[t];if(n){const t=e.parse(n);a=Math.min(a,t),s=Math.max(s,t)}}else if(r[i]===o)for(const t of[n+"Min",n+"Max",n+"Value"]){const n=r[t];if(n){const t=e.parse(n);a=Math.min(a,t),s=Math.max(s,t)}}return{min:a,max:s}}(t,n);let i=!1;B(o.min)&&void 0===t.options.min&&void 0===t.options.suggestedMin&&(i=t.min!==o.min,t.min=o.min);B(o.max)&&void 0===t.options.max&&void 0===t.options.suggestedMax&&(i=t.max!==o.max,t.max=o.max);i&&"function"==typeof t.handleTickRangeOptions&&t.handleTickRangeOptions()}(0,t.scale,n.annotations.filter((e=>e.display&&e.adjustScaleRange)))},afterUpdate(t,n,o){const i=Y.get(t);!function(e,t,n){const o=t.annotations||[];t.listened=!1,t.moveListened=!1,l.forEach((e=>{"function"==typeof n[e]&&(t.listened=!0,t.listeners[e]=n[e])})),r.forEach((e=>{"function"==typeof n[e]&&(t.moveListened=!0)})),t.listened&&t.moveListened||o.forEach((e=>{t.listened||s.forEach((n=>{"function"==typeof e[n]&&(t.listened=!0)})),t.moveListened||r.forEach((n=>{"function"==typeof e[n]&&(t.listened=!0,t.moveListened=!0)}))}))}(0,i,o),function(t,n,o,i){const a=function(t,n,o){if("reset"===o||"none"===o||"resize"===o)return G;return new e.Animations(t,n)}(t,o.animations,i),s=n.annotations,r=function(e,t){const n=t.length,o=e.length;if(on&&e.splice(n,o-n);return e}(n.elements,s);for(let e=0;eY.get(e),defaults:{drawTime:"afterDatasetsDraw",dblClickSpeed:350,animations:{numbers:{properties:["x","y","x2","y2","width","height"],type:"number"}},label:{drawTime:null}},descriptors:{_indexable:!1,_scriptable:e=>!l.includes(e),annotations:{_allKeys:!1,_fallback:(e,t)=>`elements.${q[t.type||"line"].id}`}},additionalOptionScopes:[""]};const G={update:Object.assign};function K(e){const t=q[e.type]||q.line,n={};n.id=e.id,n.type=e.type,n.drawTime=e.drawTime,Object.assign(n,Q(e,t.defaults),Q(e,t.defaultRoutes));for(const t of l)n[t]=e[t];return n}function Q(e,t){const n={};for(const o of Object.keys(t)){const i=t[o],a=e[o];n[o]=$(i)?Q(a,i):a}return n}function X(e,t,n){return t.$context||(t.$context=Object.assign(Object.create(e.getContext()),{element:t,id:n.id,type:"annotation"}))}function Z(e,t){const{ctx:n,chartArea:o}=e,i=Y.get(e).elements.filter((e=>!e.skip&&e.options.display));z(n,o),i.forEach((e=>{e.options.drawTime===t&&e.draw(n)})),F(n),i.forEach((e=>{"drawLabel"in e&&e.options.label&&(e.options.label.drawTime||e.options.drawTime)===t&&e.drawLabel(n,o)}))}return e.Chart.register(H),H})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("chart.js-v3"),require("canvas")):"function"==typeof define&&define.amd?define(["chart.js-v3","canvas"],e):(t="undefined"!=typeof globalThis?globalThis:t||self)["chartjs-plugin-annotation"]=e(t.ChartJsV3,t.canvas)}(this,(function(t,e){"use strict";function o(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var n=o(t);const{distanceBetweenPoints:i,defined:r,callback:s}=n.default.helpers,a=["click","dblclick"],d=["enter","leave"],l=a.concat(d);function h(t,e,o){if(t.listened)switch(e.type){case"mousemove":case"mouseout":!function(t,e){if(!t.moveListened)return;let o;"mousemove"===e.type&&(o=u(t.elements,e));const n=t.hovered;t.hovered=o,function(t,e,o){const{previous:n,element:i}=e;n&&n!==i&&c(n.options.leave||t.listeners.leave,n,o);i&&i!==n&&c(i.options.enter||t.listeners.enter,i,o)}(t,{previous:n,element:o},e)}(t,e);break;case"click":!function(t,e,o){const n=t.listeners,i=u(t.elements,e);if(i){const t=i.options,r=t.dblclick||n.dblclick,s=t.click||n.click;i.clickTimeout?(clearTimeout(i.clickTimeout),delete i.clickTimeout,c(r,i,e)):r?i.clickTimeout=setTimeout((()=>{delete i.clickTimeout,c(s,i,e)}),o.dblClickSpeed):c(s,i,e)}}(t,e,o)}}function c(t,e,o){s(t,[e.$context,o])}function u(t,e){let o=Number.POSITIVE_INFINITY;return t.filter((t=>t.options.display&&t.inRange(e.x,e.y))).reduce(((t,n)=>{const r=n.getCenterPoint(),s=i(e,r);return st._index-e._index)).slice(0,1)[0]}const{isFinite:f,valueOrDefault:b,defined:p}=n.default.helpers;function x(t,e,o){const n=function(t,e){const o=t.axis,n=t.id,i=o+"ScaleID",r={min:b(t.min,Number.NEGATIVE_INFINITY),max:b(t.max,Number.POSITIVE_INFINITY)};for(const s of e)s.scaleID===n?w(s,t,["value","endValue"],r):s[i]===n&&w(s,t,[o+"Min",o+"Max",o+"Value"],r);return r}(e,o);let i=y(e,n,"min","suggestedMin");i=y(e,n,"max","suggestedMax")||i,i&&"function"==typeof e.handleTickRangeOptions&&e.handleTickRangeOptions()}function y(t,e,o,n){if(f(e[o])&&!function(t,e,o){return p(t[e])||p(t[o])}(t.options,o,n)){const n=t[o]!==e[o];return t[o]=e[o],n}}function g(t,e){for(const o of["scaleID","xScaleID","yScaleID"])t[o]&&!e[t[o]]&&console.warn(`No scale found with id '${t[o]}' for annotation '${t.id}'`)}function w(t,e,o,n){for(const i of o){const o=t[i];if(p(o)){const t=e.parse(o);n.min=Math.min(n.min,t),n.max=Math.max(n.max,t)}}}const m=(t,e,o)=>Math.min(o,Math.max(e,t));function v(t,e,o){for(const n of Object.keys(t))t[n]=m(t[n],e,o);return t}function M(t,e,{x:o,y:n,width:i,height:r},s){const a=s/2||0;return t>=o-a&&t<=o+i+a&&e>=n-a&&e<=n+r+a}function S(t,e){const{x:o,y:n}=t.getProps(["x","y"],e);return{x:o,y:n}}const C=(t,e)=>e>t||t.length>e.length&&t.substr(0,e.length)===e;const{isObject:D,valueOrDefault:k,defined:P}=n.default.helpers,O=t=>"string"==typeof t&&t.endsWith("%"),j=t=>m(parseFloat(t)/100,0,1);function A(t,e){return"start"===e?0:"end"===e?t:O(e)?j(e)*t:t/2}function I(t,e){return"number"==typeof e?e:O(e)?j(e)*t:t}function W(t){return D(t)?{x:k(t.x,"center"),y:k(t.y,"center")}:{x:t=k(t,"center"),y:t}}function T(t){return t&&(P(t.xValue)||P(t.yValue))}const{addRoundedRectPath:R,isArray:E,toFont:Y,toTRBLCorners:X,valueOrDefault:V}=n.default.helpers,H=new Map;function N(t){return t instanceof e.Image||t instanceof Image||t instanceof HTMLCanvasElement}function _(t,e){if(e&&e.borderWidth)return t.lineCap=e.borderCapStyle,t.setLineDash(e.borderDash),t.lineDashOffset=e.borderDashOffset,t.lineJoin=e.borderJoinStyle,t.lineWidth=e.borderWidth,t.strokeStyle=e.borderColor,!0}function z(t,e){t.shadowColor=e.backgroundShadowColor,t.shadowBlur=e.shadowBlur,t.shadowOffsetX=e.shadowOffsetX,t.shadowOffsetY=e.shadowOffsetY}function L(t,e){const o=e.content;if(N(o))return{width:I(o.width,e.width),height:I(o.height,e.height)};const n=Y(e.font),i=E(o)?o:[o],r=i.join()+n.string+(t._measureText?"-spriting":"");if(!H.has(r)){t.save(),t.font=n.string;const e=i.length;let o=0;for(let n=0;nt.fillText(e,a,d+o*s)))}function F(t){const{x:e,y:o,width:n,height:i}=t;return{x:e+n/2,y:o+i/2}}const{isFinite:J}=n.default.helpers;function q(t,e,o){return e="number"==typeof e?e:t.parse(e),J(e)?t.getPixelForValue(e):o}function U(t,e){if(t){const o=q(t,e.min,e.start),n=q(t,e.max,e.end);return{start:Math.min(o,n),end:Math.max(o,n)}}return{start:e.start,end:e.end}}function G(t,e){const{chartArea:o,scales:n}=t,i=n[e.xScaleID],r=n[e.yScaleID];let s=o.width/2,a=o.height/2;return i&&(s=q(i,e.xValue,s)),r&&(a=q(r,e.yValue,a)),{x:s,y:a}}function K(t,e){const o=t.scales[e.xScaleID],n=t.scales[e.yScaleID];let{top:i,left:r,bottom:s,right:a}=t.chartArea;if(!o&&!n)return{};const d=U(o,{min:e.xMin,max:e.xMax,start:r,end:a});r=d.start,a=d.end;const l=U(n,{min:e.yMin,max:e.yMax,start:i,end:s});return i=l.start,s=l.end,{x:r,y:i,x2:a,y2:s,width:a-r,height:s-i}}function Q(t,e){if(!T(e)){const o=K(t,e),n=F(o);let i=e.radius;return i&&!isNaN(i)||(i=Math.min(o.width,o.height)/2,e.radius=i),{x:n.x+e.xAdjust,y:n.y+e.yAdjust,width:2*i,height:2*i}}return function(t,e){const o=G(t,e);return{x:o.x+e.xAdjust,y:o.y+e.yAdjust,width:2*e.radius,height:2*e.radius}}(t,e)}const{toPadding:Z}=n.default.helpers;class tt extends t.Element{inRange(t,e,o){return M(t,e,this.getProps(["x","y","width","height"],o),this.options.borderWidth)}getCenterPoint(t){return F(this.getProps(["x","y","width","height"],t))}draw(t){t.save(),B(t,this,this.options),t.restore()}drawLabel(t){const{x:e,y:o,width:n,height:i,options:r}=this,{label:s,borderWidth:a}=r,d=a/2,l=W(s.position),h=Z(s.padding),c=L(t,s),u={x:et(this,c,l,h),y:ot(this,c,l,h),width:c.width,height:c.height};t.save(),t.beginPath(),t.rect(e+d+h.left,o+d+h.top,n-a-h.width,i-a-h.height),t.clip(),$(t,u,s),t.restore()}resolveElementProperties(t,e){return K(t,e)}}function et(t,e,o,n){const{x:i,x2:r,width:s,options:a}=t,{xAdjust:d,borderWidth:l}=a.label;return nt({start:i,end:r,size:s},{position:o.x,padding:{start:n.left,end:n.right},adjust:d,borderWidth:l,size:e.width})}function ot(t,e,o,n){const{y:i,y2:r,height:s,options:a}=t,{yAdjust:d,borderWidth:l}=a.label;return nt({start:i,end:r,size:s},{position:o.y,padding:{start:n.top,end:n.bottom},adjust:d,borderWidth:l,size:e.height})}function nt(t,e){const{start:o,end:n}=t,{position:i,padding:{start:r,end:s},adjust:a,borderWidth:d}=e;return o+d/2+a+r+A(n-d-o-r-s-e.size,i)}tt.id="boxAnnotation",tt.defaults={adjustScaleRange:!0,backgroundShadowColor:"transparent",borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderRadius:0,borderShadowColor:"transparent",borderWidth:1,cornerRadius:void 0,display:!0,label:{borderWidth:void 0,color:"black",content:null,drawTime:void 0,enabled:!1,font:{family:void 0,lineHeight:void 0,size:void 0,style:void 0,weight:"bold"},height:void 0,padding:6,position:"center",textAlign:"start",xAdjust:0,yAdjust:0,width:void 0},shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,xMax:void 0,xMin:void 0,xScaleID:"x",yMax:void 0,yMin:void 0,yScaleID:"y"},tt.defaultRoutes={borderColor:"color",backgroundColor:"color"},tt.descriptors={label:{_fallback:!0}};const{PI:it,toRadians:rt,toPadding:st}=n.default.helpers,at=(t,e,o)=>({x:t.x+o*(e.x-t.x),y:t.y+o*(e.y-t.y)}),dt=(t,e,o)=>at(e,o,Math.abs((t-e.y)/(o.y-e.y))).x,lt=(t,e,o)=>at(e,o,Math.abs((t-e.x)/(o.x-e.x))).y,ht=t=>t*t;function ct({x:t,y:e,x2:o,y2:n},{top:i,right:r,bottom:s,left:a}){return!(tr&&o>r||es&&n>s)}function ut({x:t,y:e},o,{top:n,right:i,bottom:r,left:s}){return ti&&(e=lt(i,{x:t,y:e},o),t=i),er&&(t=dt(r,{x:t,y:e},o),e=r),{x:t,y:e}}class ft extends t.Element{intersects(t,e,o=.001,n){const{x:i,y:r,x2:s,y2:a}=this.getProps(["x","y","x2","y2"],n),d=s-i,l=a-r,h=ht(d)+ht(l),c=0===h?-1:((t-i)*d+(e-r)*l)/h;let u,f;return c<0?(u=i,f=r):c>1?(u=s,f=a):(u=i+c*d,f=r+c*l),ht(t-u)+ht(e-f)=n-c&&d<=n+c&&l>=i-u&&l<=i+u}inRange(t,e,o){const n=ht(this.options.borderWidth/2);return this.intersects(t,e,n,o)||this.isOnLabel(t,e,o)}getCenterPoint(){return{x:(this.x2+this.x)/2,y:(this.y2+this.y)/2}}draw(t){const{x:e,y:o,x2:n,y2:i,options:r}=this;if(t.save(),!_(t,r))return t.restore();z(t,r);const s=Math.atan2(i-o,n-e),a=Math.sqrt(Math.pow(n-e,2)+Math.pow(i-o,2)),{startOpts:d,endOpts:l,startAdjust:h,endAdjust:c}=function(t){const e=t.options,o=e.arrowHeads&&e.arrowHeads.start,n=e.arrowHeads&&e.arrowHeads.end;return{startOpts:o,endOpts:n,startAdjust:yt(t,o),endAdjust:yt(t,n)}}(this);t.translate(e,o),t.rotate(s),t.beginPath(),t.moveTo(0+h,0),t.lineTo(a-c,0),t.shadowColor=r.borderShadowColor,t.stroke(),gt(t,0,h,d),gt(t,a,-c,l),t.restore()}drawLabel(t,e){if(!this.labelIsVisible(!1,e))return;const{labelX:o,labelY:n,labelWidth:i,labelHeight:r,labelRotation:s,labelPadding:a,labelTextSize:d,options:{label:l}}=this;t.save(),t.translate(o,n),t.rotate(s);B(t,{x:-i/2,y:-r/2,width:i,height:r},l);$(t,{x:-i/2+a.left+l.borderWidth/2,y:-r/2+a.top+l.borderWidth/2,width:d.width,height:d.height},l),t.restore()}resolveElementProperties(t,e){const o=t.scales[e.scaleID];let n,i,{top:r,left:s,bottom:a,right:d}=t.chartArea;if(o)n=q(o,e.value,NaN),i=q(o,e.endValue,n),o.isHorizontal()?(s=n,d=i):(r=n,a=i);else{const o=t.scales[e.xScaleID],n=t.scales[e.yScaleID];o&&(s=q(o,e.xMin,s),d=q(o,e.xMax,d)),n&&(r=q(n,e.yMin,r),a=q(n,e.yMax,a))}const l=ct({x:s,y:r,x2:d,y2:a},t.chartArea)?function(t,e,o){const{x:n,y:i}=ut(t,e,o),{x:r,y:s}=ut(e,t,o);return{x:n,y:i,x2:r,y2:s,width:Math.abs(r-n),height:Math.abs(s-i)}}({x:s,y:r},{x:d,y:a},t.chartArea):{x:s,y:r,x2:d,y2:a,width:Math.abs(d-s),height:Math.abs(a-r)},h=e.label;return h&&h.content?function(t,e,o){const{padding:n,xPadding:i,yPadding:r,borderWidth:s}=o,a=function(t,e,o){let n=t;(e||o)&&(n={x:e||6,y:o||6});return st(n)}(n,i,r),d=L(e.ctx,o),l=d.width+a.width+s,h=d.height+a.height+s,c=function(t,e,o,n){const{width:i,height:r,padding:s}=o,{xAdjust:a,yAdjust:d}=e,l={x:t.x,y:t.y},h={x:t.x2,y:t.y2},c="auto"===e.rotation?function(t){const{x:e,y:o,x2:n,y2:i}=t,r=Math.atan2(i-o,n-e);return r>it/2?r-it:r0&&(i.w/2+r.left-n.x)/s,l=a>0&&(i.h/2+r.top-n.y)/a;return m(Math.max(d,l),0,.25)}function xt(t,e){const{size:o,min:n,max:i,padding:r}=e,s=o/2;return o>i-n?(i+n)/2:(n>=t-r-s&&(t=n+r+s),i<=t+r+s&&(t=i-r-s),t)}function yt(t,e){if(!e||!e.enabled)return 0;const{length:o,width:n}=e,i=t.options.borderWidth/2,r={x:o,y:n+i},s={x:0,y:i};return Math.abs(dt(0,r,s))}function gt(t,e,o,n){if(!n||!n.enabled)return;const{length:i,width:r,fill:s,backgroundColor:a,borderColor:d}=n,l=Math.abs(e-i)+o;t.beginPath(),z(t,n),_(t,n),t.moveTo(l,-r),t.lineTo(e+o,0),t.lineTo(l,r),!0===s?(t.fillStyle=a||d,t.closePath(),t.fill(),t.shadowColor="transparent"):t.shadowColor=n.borderShadowColor,t.stroke()}ft.defaults={adjustScaleRange:!0,arrowHeads:{enabled:!1,end:Object.assign({},bt),fill:!1,length:12,start:Object.assign({},bt),width:6},borderDash:[],borderDashOffset:0,borderShadowColor:"transparent",borderWidth:2,display:!0,endValue:void 0,label:{backgroundColor:"rgba(0,0,0,0.8)",backgroundShadowColor:"transparent",borderCapStyle:"butt",borderColor:"black",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderRadius:6,borderShadowColor:"transparent",borderWidth:0,color:"#fff",content:null,cornerRadius:void 0,drawTime:void 0,enabled:!1,font:{family:void 0,lineHeight:void 0,size:void 0,style:void 0,weight:"bold"},height:void 0,padding:6,position:"center",rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,textAlign:"center",width:void 0,xAdjust:0,xPadding:void 0,yAdjust:0,yPadding:void 0},scaleID:void 0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,value:void 0,xMax:void 0,xMin:void 0,xScaleID:"x",yMax:void 0,yMin:void 0,yScaleID:"y"},ft.descriptors={arrowHeads:{start:{_fallback:!0},end:{_fallback:!0},_fallback:!0}},ft.defaultRoutes={borderColor:"color"};const{PI:wt,toRadians:mt}=n.default.helpers;class vt extends t.Element{inRange(t,e,o){return function(t,e,o,n){const{width:i,height:r}=e,s=e.getCenterPoint(!0),a=i/2,d=r/2;if(a<=0||d<=0)return!1;const l=mt(o||0),h=n/2||0,c=Math.cos(l),u=Math.sin(l),f=Math.pow(c*(t.x-s.x)+u*(t.y-s.y),2),b=Math.pow(u*(t.x-s.x)-c*(t.y-s.y),2);return f/Math.pow(a+h,2)+b/Math.pow(d+h,2)<=1.0001}({x:t,y:e},this.getProps(["width","height"],o),this.options.rotation,this.options.borderWidth)}getCenterPoint(t){return F(this.getProps(["x","y","width","height"],t))}draw(t){const{width:e,height:o,options:n}=this,i=this.getCenterPoint();t.save(),t.translate(i.x,i.y),n.rotation&&t.rotate(mt(n.rotation)),z(t,this.options),t.beginPath(),t.fillStyle=n.backgroundColor;const r=_(t,n);t.ellipse(0,0,o/2,e/2,wt/2,0,2*wt),t.fill(),r&&(t.shadowColor=n.borderShadowColor,t.stroke()),t.restore()}resolveElementProperties(t,e){return K(t,e)}}vt.id="ellipseAnnotation",vt.defaults={adjustScaleRange:!0,backgroundShadowColor:"transparent",borderDash:[],borderDashOffset:0,borderShadowColor:"transparent",borderWidth:1,display:!0,rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,xMax:void 0,xMin:void 0,xScaleID:"x",yMax:void 0,yMin:void 0,yScaleID:"y"},vt.defaultRoutes={borderColor:"color",backgroundColor:"color"};const{color:Mt,toPadding:St}=n.default.helpers;class Ct extends t.Element{inRange(t,e,o){return M(t,e,this.getProps(["x","y","width","height"],o),this.options.borderWidth)}getCenterPoint(t){return F(this.getProps(["x","y","width","height"],t))}draw(t){if(!this.options.content)return;const{labelX:e,labelY:o,labelWidth:n,labelHeight:i,options:r}=this;!function(t,e){const{pointX:o,pointY:n,calloutPosition:i,options:r}=e;if(!i)return;const s=r.callout;t.save(),t.beginPath();if(!_(t,s))return t.restore();const{separatorStart:a,separatorEnd:d}=function(t,e){const{x:o,y:n,width:i,height:r}=t,s=function(t,e){const{width:o,height:n,options:i}=t,r=i.callout.margin+i.borderWidth/2;if("right"===e)return o+r;if("bottom"===e)return n+r;return-r}(t,e);let a,d;"left"===e||"right"===e?(a={x:o+s,y:n},d={x:a.x,y:a.y+r}):(a={x:o,y:n+s},d={x:a.x+i,y:a.y});return{separatorStart:a,separatorEnd:d}}(e,i),{sideStart:l,sideEnd:h}=function(t,e,o){const{y:n,width:i,height:r,options:s}=t,a=s.callout.start,d=function(t,e){const o=e.side;if("left"===t||"top"===t)return-o;return o}(e,s.callout);let l,h;"left"===e||"right"===e?(l={x:o.x,y:n+I(r,a)},h={x:l.x+d,y:l.y}):(l={x:o.x+I(i,a),y:o.y},h={x:l.x,y:l.y+d});return{sideStart:l,sideEnd:h}}(e,i,a);(s.margin>0||0===r.borderWidth)&&(t.moveTo(a.x,a.y),t.lineTo(d.x,d.y));t.moveTo(l.x,l.y),t.lineTo(h.x,h.y),t.lineTo(o,n),t.stroke(),t.restore()}(t,this),this.boxVisible&&B(t,this,r),$(t,{x:e,y:o,width:n,height:i},r)}resolveElementProperties(t,e){const o=T(e)?G(t,e):F(K(t,e)),n=St(e.padding),i=L(t.ctx,e),r=function(t,e,o,n){const i=e.width+n.width+o.borderWidth,r=e.height+n.height+o.borderWidth,s=W(o.position);return{x:Dt(t.x,i,o.xAdjust,s.x),y:Dt(t.y,r,o.yAdjust,s.y),width:i,height:r}}(o,i,e,n),s=Mt(e.backgroundColor),a={boxVisible:e.borderWidth>0||s&&s.valid&&s.rgb.a>0,pointX:o.x,pointY:o.y,...r,labelX:r.x+n.left+e.borderWidth/2,labelY:r.y+n.top+e.borderWidth/2,labelWidth:i.width,labelHeight:i.height};return a.calloutPosition=e.callout.enabled&&function(t,e){const o=e.position;if("left"===o||"right"===o||"top"===o||"bottom"===o)return o;return function(t,e){const{x:o,y:n,width:i,height:r,pointX:s,pointY:a}=t,{margin:d,side:l}=e,h=d+l;if(so+i+h)return"right";if(an+r+h)return"bottom"}(t,e)}(a,e.callout),a}}function Dt(t,e,o=0,n){return t-A(e,n)+o}Ct.id="labelAnnotation",Ct.defaults={adjustScaleRange:!0,backgroundColor:"transparent",backgroundShadowColor:"transparent",borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderRadius:0,borderShadowColor:"transparent",borderWidth:0,callout:{borderCapStyle:"butt",borderColor:void 0,borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:1,enabled:!1,margin:5,position:"auto",side:5,start:"50%"},color:"black",content:null,display:!0,font:{family:void 0,lineHeight:void 0,size:void 0,style:void 0,weight:void 0},height:void 0,padding:6,position:"center",shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,textAlign:"center",width:void 0,xAdjust:0,xMax:void 0,xMin:void 0,xScaleID:"x",xValue:void 0,yAdjust:0,yMax:void 0,yMin:void 0,yScaleID:"y",yValue:void 0},Ct.defaultRoutes={borderColor:"color"};const{drawPoint:kt}=n.default.helpers;class Pt extends t.Element{inRange(t,e,o){const{width:n}=this.getProps(["width"],o);return function(t,e,o,n){if(!t||!e||o<=0)return!1;const i=n/2||0;return Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2)<=Math.pow(o+i,2)}({x:t,y:e},this.getCenterPoint(o),n/2,this.options.borderWidth)}getCenterPoint(t){return S(this,t)}draw(t){const e=this.options,o=e.borderWidth;if(e.radius<.1)return;t.save(),t.fillStyle=e.backgroundColor,z(t,e);const n=_(t,e);e.borderWidth=0,kt(t,e,this.x,this.y),n&&!N(e.pointStyle)&&(t.shadowColor=e.borderShadowColor,t.stroke()),t.restore(),e.borderWidth=o}resolveElementProperties(t,e){return Q(t,e)}}Pt.id="pointAnnotation",Pt.defaults={adjustScaleRange:!0,backgroundShadowColor:"transparent",borderDash:[],borderDashOffset:0,borderShadowColor:"transparent",borderWidth:1,display:!0,pointStyle:"circle",radius:10,rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,xAdjust:0,xMax:void 0,xMin:void 0,xScaleID:"x",xValue:void 0,yAdjust:0,yMax:void 0,yMin:void 0,yScaleID:"y",yValue:void 0},Pt.defaultRoutes={borderColor:"color",backgroundColor:"color"};const{PI:Ot,RAD_PER_DEG:jt}=n.default.helpers;class At extends t.Element{inRange(t,e,o){return this.options.radius>=.1&&this.elements.length>1&&function(t,e,o,n){let i=!1,r=t[t.length-1].getProps(["bX","bY"],n);for(const s of t){const t=s.getProps(["bX","bY"],n);t.bY>o!=r.bY>o&&e<(r.bX-t.bX)*(o-t.bY)/(r.bY-t.bY)+t.bX&&(i=!i),r=t}return i}(this.elements,t,e,o)}getCenterPoint(t){return S(this,t)}draw(t){const{elements:e,options:o}=this;t.save(),t.beginPath(),t.fillStyle=o.backgroundColor,z(t,o);const n=_(t,o);let i=!0;for(const o of e)i?(t.moveTo(o.x,o.y),i=!1):t.lineTo(o.x,o.y);t.closePath(),t.fill(),n&&(t.shadowColor=o.borderShadowColor,t.stroke()),t.restore()}resolveElementProperties(t,e){const{x:o,y:n,width:i,height:r}=Q(t,e),{sides:s,radius:a,rotation:d,borderWidth:l}=e,h=l/2,c=[],u=2*Ot/s;let f=d*jt;for(let t=0;t{t.defaults.describe(`elements.${It[e].id}`,{_fallback:"plugins.annotation"})}));const{clipArea:Wt,unclipArea:Tt,isObject:Rt,isArray:Et}=n.default.helpers,Yt=new Map;var Xt={id:"annotation",version:"1.2.2",afterRegister(){t.Chart.register(It),function(t,e,o,n=!0){const i=o.split(".");let r=0;for(const s of e.split(".")){const a=i[r++];if(parseInt(s,10){const e=i[t];Rt(e)&&(e.id=t,n.push(e))})):Et(i)&&n.push(...i),function(t,e){for(const o of t)g(o,e)}(n,t.scales)},afterDataLimits(t,e){const o=Yt.get(t);x(0,e.scale,o.annotations.filter((t=>t.display&&t.adjustScaleRange)))},afterUpdate(e,o,n){const i=Yt.get(e);!function(t,e,o){e.listened=!1,e.moveListened=!1,l.forEach((t=>{"function"==typeof o[t]?(e.listened=!0,e.listeners[t]=o[t]):r(e.listeners[t])&&delete e.listeners[t]})),d.forEach((t=>{"function"==typeof o[t]&&(e.moveListened=!0)})),e.listened&&e.moveListened||e.annotations.forEach((t=>{e.listened||a.forEach((o=>{"function"==typeof t[o]&&(e.listened=!0)})),e.moveListened||d.forEach((o=>{"function"==typeof t[o]&&(e.listened=!0,e.moveListened=!0)}))}))}(0,i,n),function(e,o,n,i){const r=function(e,o,n){if("reset"===n||"none"===n||"resize"===n)return Vt;return new t.Animations(e,o)}(e,n.animations,i),s=o.annotations,a=function(t,e){const o=e.length,n=t.length;if(no&&t.splice(o,n-o);return t}(o.elements,s);for(let t=0;t!t.skip&&t.options.display))},beforeDatasetsDraw(t,e,o){$t(t,"beforeDatasetsDraw",o.clip)},afterDatasetsDraw(t,e,o){$t(t,"afterDatasetsDraw",o.clip)},beforeDraw(t,e,o){$t(t,"beforeDraw",o.clip)},afterDraw(t,e,o){$t(t,"afterDraw",o.clip)},beforeEvent(t,e,o){h(Yt.get(t),e.event,o)},destroy(t){Yt.delete(t)},_getState:t=>Yt.get(t),defaults:{animations:{numbers:{properties:["x","y","x2","y2","width","height","pointX","pointY","labelX","labelY","labelWidth","labelHeight","radius"],type:"number"}},clip:!0,dblClickSpeed:350,drawTime:"afterDatasetsDraw",label:{drawTime:null}},descriptors:{_indexable:!1,_scriptable:t=>!l.includes(t),annotations:{_allKeys:!1,_fallback:(t,e)=>`elements.${It[Ht(e.type)].id}`}},additionalOptionScopes:[""]};const Vt={update:Object.assign};function Ht(t="line"){return It[t]?t:(console.warn(`Unknown annotation type: '${t}', defaulting to 'line'`),"line")}function Nt(t,{elements:e,initProperties:o},n,i){const r=t.elements||(t.elements=[]);r.length=e.length;for(let t=0;t{if(!("drawLabel"in t))return;const o=t.options.label;o&&o.enabled&&o.content&&(o.drawTime||t.options.drawTime)===e&&t.drawLabel(n,i)}))}function Ft(t,e,o){for(const n of e)n.options.drawTime===o&&n.draw(t)}return t.Chart.register(Xt),Xt})); diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 0334a4c2a..0bd571bf3 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,19 +1,64 @@ +const docsVersion = "VERSION"; +const base = process.env.NODE_ENV === "development" ? '/chartjs-plugin-annotation/master/' : `/chartjs-plugin-annotation/${docsVersion}/`; + module.exports = { dest: 'dist/docs', title: 'chartjs-plugin-annotation', description: 'Annotations for Chart.js', theme: 'chartjs', - base: '/chartjs-plugin-annotation/', + base, head: [ ['link', {rel: 'icon', href: '/favicon.png'}], ], plugins: [ + ['flexsearch'], ['redirect', { redirectors: [ // Default sample page when accessing /samples. - {base: '/samples', alternative: ['types/line']}, + {base: '/samples', alternative: ['intro']}, ], }], + ['@simonbrunel/vuepress-plugin-versions', { + filters: { + suffix: (tag) => tag ? ` (${tag})` : '', + title: (v, vars) => window.location.href.includes('master') ? 'Development (master)' : v + (vars.tag ? ` (${tag})` : ''), + }, + menu: { + text: '{{version|title}}', + items: [ + { + text: 'Documentation', + items: [ + { + text: 'Development (master)', + link: '/chartjs-plugin-annotation/master/', + target: '_self', + }, + { + type: 'versions', + text: '{{version}}{{tag|suffix}}', + link: '/chartjs-plugin-annotation/{{version}}/', + exclude: /^[0]\.[0-4]\./, + group: 'minor', + target: '_self', + } + ] + }, + { + text: 'Release notes (5 latest)', + items: [ + { + type: 'versions', + limit: 5, + target: '_blank', + group: 'patch', + link: 'https://github.com/chartjs/chartjs-plugin-annotation/releases/tag/v{{version}}' + } + ] + } + ] + }, + }], ], themeConfig: { repo: 'chartjs/chartjs-plugin-annotation', @@ -40,33 +85,80 @@ module.exports = { 'integration', 'usage', 'options', - 'interaction', + 'configuration', { title: 'Annotations', collapsable: false, children: [ 'types/box', 'types/ellipse', + 'types/label', 'types/line', - 'types/point' + 'types/point', + 'types/polygon' ] } ], '/samples/': [ 'intro', { - title: 'Types', - collapsable: false, + title: 'Box annotations', children: [ - 'types/box', - 'types/ellipse', - 'types/line', - 'types/point' - ], + 'box/basic', + 'box/quarters', + 'box/disclosure', + ] + }, + { + title: 'Ellipse annotations', + children: [ + 'ellipse/basic', + 'ellipse/rotation', + ] + }, + { + title: 'Label annotations', + children: [ + 'label/basic', + 'label/point', + 'label/callout', + 'label/lowerUpper', + ] + }, + { + title: 'Line annotations', + children: [ + 'line/basic', + 'line/lowerUpper', + 'line/limited', + 'line/average', + 'line/standardDeviation', + 'line/visibility', + 'line/labelVisibility', + 'line/datasetBars', + 'line/animation', + ] + }, + { + title: 'Point annotations', + children: [ + 'point/basic', + 'point/combined', + 'point/outsideChartArea', + 'point/shadow', + ] + }, + { + title: 'Polygon annotations', + children: [ + 'polygon/basic', + 'polygon/stop', + 'polygon/outsideChartArea', + 'polygon/shadow', + ] }, { title: 'Charts', - collapsable: false, children: [ 'charts/bar', 'charts/line', diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md new file mode 100644 index 000000000..c9ac4ae1c --- /dev/null +++ b/docs/guide/configuration.md @@ -0,0 +1,69 @@ +--- +title: Configuration +--- + +## Top level options + +The following options are available at the top level. They apply to all annotations unless they are overwritten on a per-annotation basis. + +| Name | Type | [Scriptable](options#scriptable-options) | Default | Notes +| ---- | ---- | :----: | ---- | ---- +| [`animations`](#animations) | `object` | No | [see here](#default-animations) | To configure which element properties are animated and how. +| `clip` | `boolean` | No | `true` | Are the annotations clipped to the chartArea. +| `dblClickSpeed` | `number` | Yes | `350` | Time to detect a double click in ms. +| `drawTime` | `string` | Yes | `'afterDatasetsDraw'` | See [drawTime](options#draw-time). + +:::warning + +Setting `clip` to `false`, you can enable the possibility to draw part of the annotation outside of the chart area. + +Nevertheless events are only catched over the chartArea. + +::: + +## Animations + +Animations options configures which element properties are animated and how, with the same configuration of [chart.js](https://www.chartjs.org/docs/latest/configuration/animations.html#animations-2). + +```javascript +const options = { + plugins: { + annotation: { + animations: { + numbers: { + properties: ['x', 'y', 'x2', 'y2', 'width', 'height', 'radius'], + type: 'number' + }, + }, + annotations: { + box1: { + type: 'box', + xMin: 1, + xMax: 2, + yMin: 50, + yMax: 70, + backgroundColor: 'rgba(255, 99, 132, 0.5)' + } + } + } + } +}; +``` + +### Default animations + +| Name | Option | Value +| ---- | ---- | ---- +| `numbers` | `properties` | `['x', 'y', 'x2', 'y2', 'width', 'height', 'pointX', 'pointY', 'labelX', 'labelY', 'labelWidth', 'labelHeight', 'radius']` +| `numbers` | `type` | `number` + +## Events + +The following options are available for all annotation types. These options can be specified per annotation, or at the top level which apply to all annotations. + +| Name | Type | [Scriptable](options#scriptable-options) | Notes +| ---- | ---- | :----: | ---- +| `click` | `(context, event) => void` | No | Called when a single click occurs on the annotation. +| `dblClick` | `(context, event) => void` | No | Called when a double click occurs on the annotation. +| `enter` | `(context, event) => void` | No | Called when the mouse enters the annotation. +| `leave` | `(context, event) => void` | No | Called when the mouse leaves the annotation. diff --git a/docs/guide/index.md b/docs/guide/index.md index a8835bae7..e660ba4fe 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -8,7 +8,7 @@ This plugin needs to be registered. It does not function as inline plugin. An annotation plugin for Chart.js >= 3.0.0 -This plugin draws lines, boxes, points and ellipses on the chart area. Annotations work with line, bar, scatter and bubble charts that use linear, logarithmic, time, or category scales. Annotations will not work on any chart that does not have exactly two axes, including pie, radar, and polar area charts. +This plugin draws lines, boxes, labels, points, polygons and ellipses on the chart area. Annotations work with line, bar, scatter and bubble charts that use linear, logarithmic, time, or category scales. Annotations will not work on any chart that does not have exactly two axes, including pie, radar, and polar area charts. ![Banner](./banner.png) diff --git a/docs/guide/interaction.md b/docs/guide/interaction.md deleted file mode 100644 index e07b04023..000000000 --- a/docs/guide/interaction.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Interactions ---- - -## Configuration - -The following options are available at the top level. They apply to all annotations unless they are overwritten on a per-annotation basis. - -| Name | Type | [Scriptable](options#scriptable-options) | Default | Notes -| ---- | ---- | :----: | ---- | ---- -| `drawTime` | `string` | Yes | `'afterDatasetsDraw'` | See [drawTime](options#draw-time) -| `dblClickSpeed` | `number` | Yes | `350` | Time to detect a double click in ms. - -The following options are available for all annotation types. These options can be specified per annotation, or at the top level which apply to all annotations. - -| Name | Type | [Scriptable](options#scriptable-options) | Notes -| ---- | ---- | :----: | ---- -| `enter` | `(context, event) => void` | No | Called when the mouse enters the annotation. -| `leave` | `(context, event) => void` | No | Called when the mouse leaves the annotation. -| `click` | `(context, event) => void` | No | Called when a single click occurs on the annotation. -| `dblClick` | `(context, event) => void` | No | Called when a double click occurs on the annotation. diff --git a/docs/guide/options.md b/docs/guide/options.md index 39bb75123..3b97291df 100644 --- a/docs/guide/options.md +++ b/docs/guide/options.md @@ -8,6 +8,14 @@ Any color supported by [chart.js](https://www.chartjs.org/docs/master/general/co Fonts use the same format as [chart.js](https://www.chartjs.org/docs/master/general/fonts). +## Padding + +Paddings use the same format as [chart.js](https://www.chartjs.org/docs/master/general/padding.html). + +## Point Style + +Point styles use the same format as [chart.js](https://www.chartjs.org/docs/master/configuration/elements.html#point-styles). + ## Scriptable Options As with most options in chart.js, the annotation plugin options are scriptable. This means that a function can be passed which returns the value as needed. In the example below, the annotation is hidden when the screen is less than 1000px wide. diff --git a/docs/guide/types/box.md b/docs/guide/types/box.md index 624b7962a..de9981cf8 100644 --- a/docs/guide/types/box.md +++ b/docs/guide/types/box.md @@ -51,49 +51,96 @@ The following options are available for box annotations. | Name | Type | [Scriptable](../options#scriptable-options) | Default | ---- | ---- | :----: | ---- -| [`display`](#general) | `boolean` | Yes | `true` | [`adjustScaleRange`](#general) | `boolean` | Yes | `true` +| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`backgroundShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderCapStyle`](#styling) | `string` | Yes | `'butt'` +| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`borderDash`](#styling) | `number[]` | Yes | `[]` +| [`borderDashOffset`](#styling) | `number` | Yes | `0` +| [`borderJoinStyle`](#styling) | `string` | Yes | `'miter'` +| [`borderRadius`](#styling) | `number` \| `object` | Yes | `0` +| [`borderShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderWidth`](#styling) | `number`| Yes | `1` +| [`display`](#general) | `boolean` | Yes | `true` | [`drawTime`](#general) | `string` | Yes | `'afterDatasetsDraw'` -| [`xScaleID`](#general) | `string` | Yes | `'x'` -| [`yScaleID`](#general) | `string` | Yes | `'y'` -| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`label`](#label) | `object` | Yes | +| [`shadowBlur`](#styling) | `number` | Yes | `0` +| [`shadowOffsetX`](#styling) | `number` | Yes | `0` +| [`shadowOffsetY`](#styling) | `number` | Yes | `0` | [`xMax`](#general) | `number` \| `string` | Yes | `undefined` +| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`xScaleID`](#general) | `string` | Yes | `'x'` | [`yMin`](#general) | `number` \| `string` | Yes | `undefined` | [`yMax`](#general) | `number` \| `string` | Yes | `undefined` -| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` -| [`borderWidth`](#styling) | `number`| Yes | `1` -| [`borderDash`](#styling) | `number[]`| Yes | `[]` -| [`borderDashOffset`](#styling) | `number`| Yes | `0` -| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` -| [`borderRadius`](#styling) | `number` \| `object` | Yes | `0` +| [`yScaleID`](#general) | `string` | Yes | `'y'` ### General If one of the axes does not match an axis in the chart, the box will take the entire chart dimension. The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction. -| Name | Description | -| ---- | ---- | -| `display` | Whether or not this annotation is visible -| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range -| `drawTime` | See [drawTime](../options#draw-time) -| `xScaleID` | ID of the X scale to bind onto, default is 'x'. -| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. -| `xMin` | Left edge of the box in units along the x axis. +| Name | Description +| ---- | ---- +| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range. +| `display` | Whether or not this annotation is visible. +| `drawTime` | See [drawTime](../options#draw-time). | `xMax` | Right edge of the box in units along the x axis. -| `yMin` | Top edge of the box in units along the y axis. +| `xMin` | Left edge of the box in units along the x axis. +| `xScaleID` | ID of the X scale to bind onto, default is 'x'. | `yMax` | Bottom edge of the box in units along the y axis. +| `yMin` | Top edge of the box in units along the y axis. +| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. ### Styling -| Name | Description | -| ---- | ---- | -| `borderColor` | Stroke color -| `borderWidth` | Stroke width +| Name | Description +| ---- | ---- +| `backgroundColor` | Fill color. +| `backgroundShadowColor` | The color of shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderCapStyle` | Cap style of the border line. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap). +| `borderColor` | Stroke color. | `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). -| `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). -| `backgroundColor` | Fill color -| [`borderRadius`](#borderRadius) | Radius of box rectangle (in pixels) +| `borderDashOffset` | Offset for border line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). +| `borderJoinStyle` | Border line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). +| [`borderRadius`](#borderradius) | Radius of box rectangle (in pixels). +| `borderShadowColor` | The color of the border shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderWidth` | Border line width (in pixels). +| `shadowBlur` | The amount of blur applied to shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `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). #### borderRadius 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. +| `drawTime` | `string` | `options.drawTime` | See [drawTime](../options#draw-time). Defaults to the box annotation draw time if unset +| `enabled` | `boolean` | `false` | Whether or not the label is shown. +| `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. +| `textAlign` | `string` | `'start'` | Text alignment of label content when there's more than one line. Possible options are: `'left'`, `'start'`, `'center'`, `'end'`, `'right'`. +| `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. + +### 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'`. diff --git a/docs/guide/types/ellipse.md b/docs/guide/types/ellipse.md index 29078ac99..737c86010 100644 --- a/docs/guide/types/ellipse.md +++ b/docs/guide/types/ellipse.md @@ -9,7 +9,7 @@ const options = { autocolors: false, annotation: { annotations: { - box1: { + ellipse1: { type: 'ellipse', xMin: 1, xMax: 2, @@ -51,45 +51,55 @@ The following options are available for ellipse annotations. | Name | Type | [Scriptable](../options#scriptable-options) | Default | ---- | ---- | :----: | ---- -| [`display`](#general) | `boolean` | Yes | `true` | [`adjustScaleRange`](#general) | `boolean` | Yes | `true` +| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`backgroundShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`borderDash`](#styling) | `number[]`| Yes | `[]` +| [`borderDashOffset`](#styling) | `number`| Yes | `0` +| [`borderShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderWidth`](#styling) | `number`| Yes | `1` +| [`display`](#general) | `boolean` | Yes | `true` | [`drawTime`](#general) | `string` | Yes | `'afterDatasetsDraw'` | [`rotation`](#general) | `number`| Yes | `0` -| [`xScaleID`](#general) | `string` | Yes | `'x'` -| [`yScaleID`](#general) | `string` | Yes | `'y'` -| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`shadowBlur`](#styling) | `number` | Yes | `0` +| [`shadowOffsetX`](#styling) | `number` | Yes | `0` +| [`shadowOffsetY`](#styling) | `number` | Yes | `0` | [`xMax`](#general) | `number` \| `string` | Yes | `undefined` -| [`yMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`xScaleID`](#general) | `string` | Yes | `'x'` | [`yMax`](#general) | `number` \| `string` | Yes | `undefined` -| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` -| [`borderWidth`](#styling) | `number`| Yes | `1` -| [`borderDash`](#styling) | `number[]`| Yes | `[]` -| [`borderDashOffset`](#styling) | `number`| Yes | `0` -| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`yMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`yScaleID`](#general) | `string` | Yes | `'y'` ### General If one of the axes does not match an axis in the chart, the ellipse will take the entire chart dimension. The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the ellipse is expanded out to the edges in the respective direction. -| Name | Description | -| ---- | ---- | -| `display` | Whether or not this annotation is visible -| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range -| `drawTime` | See [drawTime](../options#draw-time) +| Name | Description +| ---- | ---- +| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range. +| `display` | Whether or not this annotation is visible. +| `drawTime` | See [drawTime](../options#draw-time). | `rotation` | Rotatation of the ellipse in degrees, default is 0. -| `xScaleID` | ID of the X scale to bind onto, default is 'x'. -| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. -| `xMin` | Left edge of the ellipse in units along the x axis. | `xMax` | Right edge of the ellipse in units along the x axis. -| `yMin` | Top edge of the ellipse in units along the y axis. +| `xMin` | Left edge of the ellipse in units along the x axis. +| `xScaleID` | ID of the X scale to bind onto, default is 'x'. | `yMax` | Bottom edge of the ellipse in units along the y axis. +| `yMin` | Top edge of the ellipse in units along the y axis. +| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. ### Styling -| Name | Description | -| ---- | ---- | -| `borderColor` | Stroke color -| `borderWidth` | Stroke width +| Name | Description +| ---- | ---- +| `backgroundColor` | Fill color. +| `backgroundShadowColor` | The color of shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderColor` | Stroke color. | `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). | `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). -| `backgroundColor` | Fill color +| `borderShadowColor` | The color of the border shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderWidth` | Stroke width. +| `shadowBlur` | The amount of blur applied to shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `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). diff --git a/docs/guide/types/label.md b/docs/guide/types/label.md new file mode 100644 index 000000000..ee354140c --- /dev/null +++ b/docs/guide/types/label.md @@ -0,0 +1,225 @@ +# Label Annotations + +Label annotations are used to add contents on the chart area. This can be useful for describing values that are of interest. + +```js chart-editor +/* */ +const options = { + plugins: { + autocolors: false, + annotation: { + annotations: { + label1: { + type: 'label', + xValue: 2.5, + yValue: 60, + backgroundColor: 'rgba(245,245,245)', + content: ['This is my text', 'This is my text, second line'], + font: { + size: 18 + } + } + } + } + } +}; +/* */ + +/* */ +const config = { + type: 'line', + data: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + label: 'My First Dataset', + data: [65, 59, 80, 81, 56, 55, 40], + fill: false, + borderColor: 'rgb(75, 192, 192)', + tension: 0.1 + }] + }, + options +}; +/* */ + +module.exports = { + config +}; +``` + +## Configuration + +The following options are available for label annotations. + +| Name | Type | [Scriptable](../options#scriptable-options) | Default +| ---- | ---- | :----: | ---- +| [`adjustScaleRange`](#general) | `boolean` | Yes | `true` +| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `transparent` +| [`backgroundShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderCapStyle`](#styling) | `string` | Yes | `'butt'` +| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`borderDash`](#styling) | `number[]` | Yes | `[]` +| [`borderDashOffset`](#styling) | `number` | Yes | `0` +| [`borderJoinStyle`](#styling) | `string` | Yes | `'miter'` +| [`borderRadius`](#borderradius) | `number` \| `object` | Yes | `0` +| [`borderShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderWidth`](#styling) | `number`| Yes | `0` +| [`callout`](#callout) | `object` | Yes | +| [`color`](#styling) | [`Color`](../options#color) | Yes | `'black'` +| [`content`](#general) | `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) | Yes | `null` +| [`display`](#general) | `boolean` | Yes | `true` +| [`drawTime`](#general) | `string` | Yes | `'afterDatasetsDraw'` +| [`font`](#styling) | [`Font`](../options#font) | Yes | `{}` +| [`height`](#general) | `number`\|`string` | Yes | `undefined` +| [`padding`](#general) | [`Padding`](../options#padding) | Yes | `6` +| [`position`](#position) | `string`\|`{x: string, y: string}` | Yes | `'center'` +| [`shadowBlur`](#styling) | `number` | Yes | `0` +| [`shadowOffsetX`](#styling) | `number` | Yes | `0` +| [`shadowOffsetY`](#styling) | `number` | Yes | `0` +| [`textAlign`](#general) | `string` | Yes | `'center'` +| [`width`](#general) | `number`\|`string` | Yes | `undefined` +| [`xAdjust`](#general) | `number` | Yes | `0` +| [`xMax`](#general) | `number` \| `string` | Yes | `undefined` +| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`xScaleID`](#general) | `string` | Yes | `'x'` +| [`xValue`](#general) | `number` \| `string` | Yes | `undefined` +| [`yAdjust`](#general) | `number` | Yes | `0` +| [`yMax`](#general) | `number` \| `string` | Yes | `undefined` +| [`yMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`yScaleID`](#general) | `string` | Yes | `'y'` +| [`yValue`](#general) | `number` \| `string` | Yes | `undefined` + +### General + +If one of the axes does not match an axis in the chart, the content will be rendered in the center of the chart. The 2 coordinates, xValue, yValue are optional. If not specified, the content will be rendered in the center of the chart. + +The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction and the box size is used to calculated the center of the point. To enable to use the box positioning, the `radius` must be set to `0` or `NaN`. + +| Name | Description +| ---- | ---- +| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range. +| `content` | The content to show in the text annotation. +| `display` | Whether or not this annotation is visible. +| `drawTime` | See [drawTime](../options#draw-time). +| `height` | 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` | The padding to add around the text label. +| `textAlign` | Text alignment of label content when there's more than one line. Possible options are: `'left'`, `'start'`, `'center'`, `'end'`, `'right'`. +| `width` | 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` | Adjustment along x-axis (left-right) of label relative to computed position. Negative values move the label left, positive right. +| `xMax` | Right edge of the box in units along the x axis. +| `xMin` | Left edge of the box in units along the x axis. +| `xScaleID` | ID of the X scale to bind onto, default is 'x'. +| `xValue` | X coordinate of the point in units along the x axis. +| `yAdjust` | Adjustment along y-axis (top-bottom) of label relative to computed position. Negative values move the label up, positive down. +| `yMax` | Bottom edge of the box in units along the y axis. +| `yMin` | Top edge of the box in units along the y axis. +| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. +| `yValue` | Y coordinate of the point in units along the y axis. + +### Styling + +| Name | Description +| ---- | ---- +| `backgroundColor` | Fill color. +| `backgroundShadowColor` | The color of shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderCapStyle` | Cap style of the border line. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap). +| `borderColor` | Stroke color. +| `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). +| `borderDashOffset` | Offset for border line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). +| `borderJoinStyle` | Border line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). +| `borderShadowColor` | The color of the border shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderWidth` | Stroke width (in pixels). +| `color` | Text color. +| `font` | Text font. +| `shadowBlur` | The amount of blur applied to shadow of the box where the label is located. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `shadowOffsetX` | The distance that shadow, of the box where the label is located, will be offset horizontally. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX). +| `shadowOffsetY` | The distance that shadow, of the box where the label is located, will be offset vertically. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY). + +### 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 label, with respect to the selected point. Similarly, the `y` property defines the vertical alignment in the label, with respect to the selected point. Possible options for both properties are `'start'`, `'center'`, `'end'`, a string in percentage format. Omitted property have value of the default, `'center'`. + +#### borderRadius + +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 connects the label by a line to the selected point. + +Namespace: `options.annotations[annotationID].callout`, it defines options for the callout on the annotation label. + +```js chart-editor +/* */ +const options = { + plugins: { + autocolors: false, + annotation: { + annotations: { + label1: { + type: 'label', + xValue: 2.5, + yValue: 60, + xAdjust: 290, + yAdjust: -100, + backgroundColor: 'rgba(245,245,245)', + content: ['In this point of time,', 'something happened'], + textAlign: 'start', + font: { + size: 18 + }, + callout: { + enabled: true, + side: 10 + } + } + } + } + } +}; +/* */ + +/* */ +const config = { + type: 'line', + data: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + label: 'My First Dataset', + data: [65, 59, 80, 81, 56, 55, 40], + fill: false, + borderColor: 'rgb(75, 192, 192)', + tension: 0.1 + }] + }, + options +}; +/* */ + +module.exports = { + config +}; +``` + +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 joint 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. +| `enabled` | `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. diff --git a/docs/guide/types/line.md b/docs/guide/types/line.md index e6a96ca34..11a1d0b53 100644 --- a/docs/guide/types/line.md +++ b/docs/guide/types/line.md @@ -49,34 +49,46 @@ module.exports = { The following options are available for line annotations. All of these options can be . | Name | Type | [Scriptable](../options#scriptable-options) | Default -| ---- | ---- | ---- | :----: | ---- -| [`display`](#general) | `boolean` | Yes | `true` +| ---- | ---- | :----: | ---- | [`adjustScaleRange`](#general) | `boolean` | Yes | `true` +| [`arrowHeads`](#arrow-heads) | `{start: object, end:object}` | Yes | +| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`borderDash`](#styling) | `number[]` | Yes | `[]` +| [`borderDashOffset`](#styling) | `number` | Yes | `0` +| [`borderShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderWidth`](#styling) | `number` | Yes | `2` +| [`display`](#general) | `boolean` | Yes | `true` | [`drawTime`](#general) | `string` | Yes | `'afterDatasetsDraw'` +| [`endValue`](#positioning) | `number` | Yes | `undefined` +| [`label`](#label) | `object` | Yes | | [`scaleID`](#positioning) | `string` | Yes | `undefined` +| [`shadowBlur`](#styling) | `number` | Yes | `0` +| [`shadowOffsetX`](#styling) | `number` | Yes | `0` +| [`shadowOffsetY`](#styling) | `number` | Yes | `0` | [`value`](#positioning) | `number` | Yes | `undefined` -| [`endValue`](#positioning) | `number` | Yes | `undefined` -| [`xScaleID`](#positioning) | `string` | Yes | `'x'` -| [`yScaleID`](#positioning) | `string` | Yes | `'y'` -| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` | [`xMax`](#general) | `number` \| `string` | Yes | `undefined` -| [`yMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`xScaleID`](#positioning) | `string` | Yes | `'x'` | [`yMax`](#general) | `number` \| `string` | Yes | `undefined` -| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` -| [`borderWidth`](#styling) | `number` | Yes | `1` -| [`borderDash`](#styling) | `number[]` | Yes | `[]` -| [`borderDashOffset`](#styling) | `number` | Yes | `0` -| [`label`](#label) | `object` | Yes | +| [`yMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`yScaleID`](#positioning) | `string` | Yes | `'y'` ### General -If one of the axes does not match an axis in the chart, the box will take the entire chart dimension. The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction. +If one of the axes does not match an axis in the chart and the line behaviors are the following, depending how the line should be drawn: + +1. if `scaleId` is not resolved, the line will take the entire chart dimension, starting top-left vertex to bottom-right vertex of the chart +1. if `xScaleId` is not resolved, the line will take the entire chart width +1. if `yScaleId` is not resolved, the line will take the entire chart height -| Name | Description | -| ---- | ---- | -| `display` | Whether or not this annotation is visible -| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range -| `drawTime` | See [drawTime](../options#draw-time) +The 2 coordinates, start, end, are optional. If not specified, the line is expanded out to the edges in the respective direction. +The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the line is expanded out to the edges in the respective direction. + +| Name | Description +| ---- | ---- +| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range. +| `display` | Whether or not this annotation is visible. +| `drawTime` | See [drawTime](../options#draw-time). ### Positioning @@ -84,54 +96,104 @@ The line can be positioned in two different ways. If `scaleID` is set, then `val If `scaleID` is unset, then `xScaleID` and `yScaleID` are used to draw a line from `(xMin, yMin)` to `(xMax, yMax)`. -| Name | Description | -| ---- | ---- | +| Name | Description +| ---- | ---- +| `endValue` | End two of the line when a single scale is specified. | `scaleID` | ID of the scale in single scale mode. If unset, `xScaleID` and `yScaleID` are used. | `value` | End one of the line when a single scale is specified. -| `endValue` | End two of the line when a single scale is specified. -| `xScaleID` | ID of the X scale to bind onto, default is 'x'. -| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. -| `xMin` | X coordinate of end one of the line in units along the x axis. | `xMax` | X coordinate of end two of the line in units along the x axis. -| `yMin` | Y coordinate of end one of the line in units along the y axis. +| `xMin` | X coordinate of end one of the line in units along the x axis. +| `xScaleID` | ID of the X scale to bind onto, default is 'x'. | `yMax` | Y coordinate of end two of the line in units along the y axis. +| `yMin` | Y coordinate of end one of the line in units along the y axis. +| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. ### Styling -| Name | Description | -| ---- | ---- | -| `borderColor` | Stroke color -| `borderWidth` | Stroke width +| Name | Description +| ---- | ---- +| `borderColor` | Stroke color. | `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). | `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). -| `backgroundColor` | Fill color -| `borderRadius` | Radius of box rectangle +| `borderShadowColor` | The color of shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderWidth` | Stroke width. +| `shadowBlur` | The amount of blur applied to shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `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). -### Label +## Label Namespace: `options.annotations[annotationID].label`, it defines options for the line annotation label. All of these options can be [Scriptable](../options#scriptable-options) | Name | Type | Default | Notes -| ---- | ---- | :----: | ---- | ---- +| ---- | ---- | :----: | ---- | `backgroundColor` | [`Color`](../options#color) | `'rgba(0,0,0,0.8)'` | Background color of the label container. -| `color` | [`Color`](../options#color) | `'#fff'` | Text color. -| `content` | `string`\|[`Image`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image) | `null` | The content to show in the label. +| `backgroundShadowColor` | [`Color`](../options#color) | `'transparent'` | The color of shadow of the box where the label is located. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderCapStyle` | `string` | `'butt'` | Cap style of the border line. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap). +| `borderColor` | [`Color`](../options#color) | `black` | The border line color. +| `borderDash` | `number[]` | `[]` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). +| `borderDashOffset` | `number` | `0` | Offset for border line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). +| `borderJoinStyle` | `string` | `'miter'` | Border line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). | [`borderRadius`](#borderradius) | `number` \| `object` | `6` | Radius of label box corners in pixels. -| `drawTime` | `string` | `options.drawTime` | See [drawTime](../options#draw-time). Defaults to the line annotation draw time if unset +| `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). +| `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. +| `drawTime` | `string` | `options.drawTime` | See [drawTime](../options#draw-time). Defaults to the line annotation draw time if unset. | `enabled` | `boolean` | `false` | Whether or not the label is shown. -| `font` | [`Font`](../options#font) | `{ style: 'bold' }` | Label font -| `xPadding` | `number` | `6` | Padding of label to add left/right. -| `yPadding` | `number` | `6` | Padding of label to add top/bottom. +| `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` | `string` | `'center'` | Anchor position of label on line. Possible options are: `'start'`, `'center'`, `'end'`. It can be set by a string in percentage format `'number%'` which are representing the percentage on the width of the line where the label will be located. +| `rotation` | `number`\|`'auto'` | `0` | Rotation of label, in degrees, or 'auto' to use the degrees of the line. +| `shadowBlur` | `number` | `0` | The amount of blur applied to shadow of the box where the label is located. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `shadowOffsetX` | `number` | `0` | The distance that shadow, of the box where the label is located, will be offset horizontally. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX). +| `shadowOffsetY` | `number` | `0` | The distance that shadow, of the box where the label is located, will be offset vertically. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY). +| `textAlign` | `string` | `'center'` | Text alignment of label content when there's more than one line. Possible options are: `'start'`, `'center'`, `'end'`. +| `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. +| `xPadding` | `number` | `6` | Padding of label to add left/right. This is **deprecated**. Use `padding`. | `yAdjust` | `number` | `0` | Adjustment along y-axis (top-bottom) of label relative to computed position. Negative values move the label up, positive down. -| `position` | `string` | `'center'` | Anchor position of label on line. Possible options are: `'start'`, `'center'`, `'end'`. -| `textAlign` | `string` | `'center'` | Text alignment of label content when there's more than one line. Possible options are: `'start'`, `'center'`, `'end'`. -| `width` | `number`\|`string` | `undefined` | Overrides the width of the image. Could be set in pixel by a number, or in percentage of current width of image by a string. If undefined, uses the width of the image. It is used only when the content is an image. -| `height` | `number`\|`string` | `undefined` | Overrides the height of the image. Could be set in pixel by a number, or in percentage of current height of image by a string. If undefined, uses the height of the image. It is used only when the content is an image. -| `rotation` | `number`\|`'auto'` | `0` | Rotation of label, in degrees, or 'auto' to use the degrees of the line +| `yPadding` | `number` | `6` | Padding of label to add top/bottom. This is **deprecated**. Use `padding`. -#### borderRadius +### borderRadius 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. + +## Arrow heads + +Namespace: `options.annotations[annotationID].arrowHeads`, it defines options for the line annotation arrow heads. + +All of these options can be [Scriptable](../options#scriptable-options) + +| Name | Type | Notes +| ---- | ---- | ---- +| [`end`](#arrow-head-configuration) | `object` | To configure the arrow head at the end of the line. +| [`start`](#arrow-head-configuration) | `object` | To configure the arrow head at the start of the line. + +### Arrow head configuration + +Enabling it, you can add arrow heads at start and/or end of a line. It uses the `borderWidth` of the line options to configure the line width of the arrow head. + +The following options are available for can be specified per (`start` and/or `end`) arrow head, or at the top level (`arrowHeads`) which apply to all arrow heads. + +All of these options can be [Scriptable](../options#scriptable-options) + +| Name | Type | Default | Notes +| ---- | ---- | :----: | ---- +| `backgroundColor` | [`Color`](../options#color) | `lineAnnotation.borderColor` | Background color of the arrow head. +| `backgroundShadowColor` | [`Color`](../options#color) | `'transparent'` | The color of shadow of the arrow head. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderColor` | [`Color`](../options#color) | `lineAnnotation.borderColor` | The border arrow head color. +| `borderDash` | `number[]` | `lineAnnotation.borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). +| `borderDashOffset` | `number` | `lineAnnotation.borderDashOffset` | Offset for border arrow head dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). +| `borderShadowColor` | [`Color`](../options#color) | `lineAnnotation.borderShadowColor` | The color of border shadow of the arrow head. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderWidth` | `number` | `lineAnnotation.borderWidth` | The border line width (in pixels). +| `enabled` | `boolean` | `false` | Whether or not the arrow head is shown. +| `fill` | `boolean` | `false` | Whether or not the arrow head is filled. +| `length` | `number` | `12` | The length of the arrow head in pixels. +| `shadowBlur` | `number` | `lineAnnotation.shadowBlur` | The amount of blur applied to shadow of the arrow head. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `shadowOffsetX` | `number` | `lineAnnotation.shadowOffsetX` | The distance that shadow, of the arrow head, will be offset horizontally. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX). +| `shadowOffsetY` | `number` | `lineAnnotation.shadowOffsetY` | The distance that shadow, of the arrow head, will be offset vertically. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY). +| `width` | `number` | `6` | The width of the arrow head in pixels. diff --git a/docs/guide/types/point.md b/docs/guide/types/point.md index 060cbb07a..4c0c602f6 100644 --- a/docs/guide/types/point.md +++ b/docs/guide/types/point.md @@ -49,41 +49,69 @@ The following options are available for point annotations. | Name | Type | [Scriptable](../options#scriptable-options) | Default | ---- | ---- | :----: | ---- -| [`display`](#general) | `boolean` | Yes | `true` | [`adjustScaleRange`](#general) | `boolean` | Yes | `true` +| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`backgroundShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`borderDash`](#styling) | `number[]`| Yes | `[]` +| [`borderDashOffset`](#styling) | `number`| Yes | `0` +| [`borderShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderWidth`](#styling) | `number`| Yes | `1` +| [`display`](#general) | `boolean` | Yes | `true` | [`drawTime`](#general) | `string` | Yes | `'afterDatasetsDraw'` +| [`pointStyle`](#styling) | [`PointStyle`](../options#point-style) | Yes | `'circle'` +| [`radius`](#general) | `number` | Yes | `10` +| [`rotation`](#general) | `number` | Yes | `0` +| [`shadowBlur`](#styling) | `number` | Yes | `0` +| [`shadowOffsetX`](#styling) | `number` | Yes | `0` +| [`shadowOffsetY`](#styling) | `number` | Yes | `0` +| [`xAdjust`](#general) | `number` | Yes | `0` +| [`xMax`](#general) | `number` \| `string` | Yes | `undefined` +| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` | [`xScaleID`](#general) | `string` | Yes | `'x'` -| [`yScaleID`](#general) | `string` | Yes | `'y'` | [`xValue`](#general) | `number` \| `string` | Yes | `undefined` +| [`yAdjust`](#general) | `number` | Yes | `0` +| [`yMax`](#general) | `number` \| `string` | Yes | `undefined` +| [`yMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`yScaleID`](#general) | `string` | Yes | `'y'` | [`yValue`](#general) | `number` \| `string` | Yes | `undefined` -| [`radius`](#general) | `number` | Yes | `10` -| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` -| [`borderWidth`](#styling) | `number`| Yes | `1` -| [`borderDash`](#styling) | `number[]`| Yes | `[]` -| [`borderDashOffset`](#styling) | `number`| Yes | `0` -| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` ### General If one of the axes does not match an axis in the chart, the point annotation will take the center of the chart as point. The 2 coordinates, xValue, yValue are optional. If not specified, the point annotation will take the center of the chart as point. -| Name | Description | -| ---- | ---- | -| `display` | Whether or not this annotation is visible -| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range -| `drawTime` | See [drawTime](../options#draw-time) +The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction and the box size is used to calculated the center of the point. To enable to use the box positioning, the `radius` must be set to `0` or `NaN`. + +| Name | Description +| ---- | ---- +| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range. +| `display` | Whether or not this annotation is visible. +| `drawTime` | See [drawTime](../options#draw-time). +| `radius` | Size of the point in pixels. +| `rotation` | Rotation of point, in degrees. +| `xAdjust` | Adjustment along x-axis (left-right) of point relative to computed position. Negative values move the point left, positive right. +| `xMax` | Right edge of the box in units along the x axis. +| `xMin` | Left edge of the box in units along the x axis. | `xScaleID` | ID of the X scale to bind onto, default is 'x'. -| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. | `xValue` | X coordinate of the point in units along the x axis. +| `yAdjust` | Adjustment along y-axis (top-bottom) of point relative to computed position. Negative values move the point up, positive down. +| `yMax` | Bottom edge of the box in units along the y axis. +| `yMin` | Top edge of the box in units along the y axis. +| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. | `yValue` | Y coordinate of the point in units along the y axis. -| `radius` | Size of the point in pixels ### Styling -| Name | Description | -| ---- | ---- | -| `borderColor` | Stroke color -| `borderWidth` | Stroke width +| Name | Description +| ---- | ---- +| `backgroundColor` | Fill color. +| `backgroundShadowColor` | The color of shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderColor` | Stroke color. | `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). | `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). -| `backgroundColor` | Fill color +| `borderShadowColor` | The color of border shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderWidth` | Stroke width. +| `pointStyle` | Style of the point. +| `shadowBlur` | The amount of blur applied to shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `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). diff --git a/docs/guide/types/polygon.md b/docs/guide/types/polygon.md new file mode 100644 index 000000000..adabb37c9 --- /dev/null +++ b/docs/guide/types/polygon.md @@ -0,0 +1,179 @@ +# Polygon Annotations + +Polygon annotations are used to mark whatever polygon (for instance triangle, square or pentagon) on the chart area. This can be useful for highlighting values that are of interest. + +```js chart-editor +/* */ +const options = { + plugins: { + autocolors: false, + annotation: { + annotations: { + pentagon: { + type: 'polygon', + xValue: 1, + yValue: 60, + sides: 5, + radius: 60, + backgroundColor: 'rgba(255, 99, 132, 0.25)' + } + } + } + } +}; +/* */ + +/* */ +const config = { + type: 'line', + data: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + label: 'My First Dataset', + data: [65, 59, 80, 81, 56, 55, 40], + fill: false, + borderColor: 'rgb(75, 192, 192)', + tension: 0.1 + }] + }, + options +}; +/* */ + +module.exports = { + config +}; +``` + +## Configuration + +The following options are available for polygon annotations. + +| Name | Type | [Scriptable](../options#scriptable-options) | Default +| ---- | ---- | :----: | ---- +| [`adjustScaleRange`](#general) | `boolean` | Yes | `true` +| [`backgroundColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`backgroundShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderCapStyle`](#styling) | `string` | Yes | `'butt'` +| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color` +| [`borderDash`](#styling) | `number[]`| Yes | `[]` +| [`borderDashOffset`](#styling) | `number`| Yes | `0` +| [`borderJoinStyle`](#styling) | `string` | Yes | `'miter'` +| [`borderShadowColor`](#styling) | [`Color`](../options#color) | Yes | `'transparent'` +| [`borderWidth`](#styling) | `number`| Yes | `1` +| [`display`](#general) | `boolean` | Yes | `true` +| [`drawTime`](#general) | `string` | Yes | `'afterDatasetsDraw'` +| [`point`](#point) | `object` | Yes | `{radius: 0}` +| [`radius`](#general) | `number` | Yes | `10` +| [`rotation`](#general) | `number` | Yes | `0` +| [`shadowBlur`](#styling) | `number` | Yes | `0` +| [`shadowOffsetX`](#styling) | `number` | Yes | `0` +| [`shadowOffsetY`](#styling) | `number` | Yes | `0` +| [`sides`](#general) | `number` | Yes | `3` +| [`xAdjust`](#general) | `number` | Yes | `0` +| [`xMax`](#general) | `number` \| `string` | Yes | `undefined` +| [`xMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`xScaleID`](#general) | `string` | Yes | `'x'` +| [`xValue`](#general) | `number` \| `string` | Yes | `undefined` +| [`yAdjust`](#general) | `number` | Yes | `0` +| [`yScaleID`](#general) | `string` | Yes | `'y'` +| [`yMax`](#general) | `number` \| `string` | Yes | `undefined` +| [`yMin`](#general) | `number` \| `string` | Yes | `undefined` +| [`yValue`](#general) | `number` \| `string` | Yes | `undefined` + +### General + +If one of the axes does not match an axis in the chart, the polygon annotation will take the center of the chart as point. The 2 coordinates, xValue, yValue are optional. If not specified, the polygon annotation will take the center of the chart. + +The 4 coordinates, xMin, xMax, yMin, yMax are optional. If not specified, the box is expanded out to the edges in the respective direction and the box size is used to calculated the center of the point. To enable to use the box positioning, the `radius` must be set to `0` or `NaN`. + +| Name | Description +| ---- | ---- +| `adjustScaleRange` | Should the scale range be adjusted if this annotation is out of range. +| `display` | Whether or not this annotation is visible. +| `drawTime` | See [drawTime](../options#draw-time). +| `radius` | Size of the polygon in pixels. +| `rotation` | Rotation of polygon, in degrees. +| `sides` | Amount of sides of polygon. +| `xAdjust` | Adjustment along x-axis (left-right) of polygon relative to computed position. Negative values move the polygon left, positive right. +| `xMax` | Right edge of the box in units along the x axis. +| `xMin` | Left edge of the box in units along the x axis. +| `xScaleID` | ID of the X scale to bind onto, default is 'x'. +| `xValue` | X coordinate of the polygon in units along the x axis. +| `yAdjust` | Adjustment along y-axis (top-bottom) of polygon relative to computed position. Negative values move the polygon up, positive down. +| `yMax` | Bottom edge of the box in units along the y axis. +| `yMin` | Top edge of the box in units along the y axis. +| `yScaleID` | ID of the Y scale to bind onto, default is 'y'. +| `yValue` | Y coordinate of the polygon in units along the y axis. + +### Styling + +| Name | Description +| ---- | ---- +| `backgroundColor` | Fill color. +| `backgroundShadowColor` | The color of shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderColor` | Stroke color. +| `borderCapStyle` | Cap style of the border of polygon. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap). +| `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). +| `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). +| `borderJoinStyle` | Border line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). +| `borderShadowColor` | The color of the border shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor). +| `borderWidth` | Stroke width. +| `shadowBlur` | The amount of blur applied to shadow. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur). +| `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). + +### Point + +Polygon consists of points. These points are actually [Point Annotations](point) and all of the [styling options](point#styling) can be configured. General options affecting the location of the point are ignored. + +Namespace: `options.annotations[annotationID].point`, it defines options for the callout on the annotation label. + +```js chart-editor +/* */ +const options = { + plugins: { + autocolors: false, + annotation: { + annotations: { + pentagon: { + type: 'polygon', + xValue: 1, + yValue: 60, + sides: 4, + radius: 60, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + point: { + radius: 10, + borderWidth: 2, + borderColor: '#666', + backgroundColor: 'rgba(99, 132, 255, 0.25)', + } + } + } + } + } +}; +/* */ + +/* */ +const config = { + type: 'line', + data: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + label: 'My First Dataset', + data: [65, 59, 80, 81, 56, 55, 40], + fill: false, + borderColor: 'rgb(75, 192, 192)', + tension: 0.1 + }] + }, + options +}; +/* */ + +module.exports = { + config +}; +``` diff --git a/docs/samples/types/box.md b/docs/samples/box/basic.md similarity index 85% rename from docs/samples/types/box.md rename to docs/samples/box/basic.md index fa3fa80a1..f986fe606 100644 --- a/docs/samples/types/box.md +++ b/docs/samples/box/basic.md @@ -1,4 +1,4 @@ -# Box +# Basic ```js chart-editor // @@ -10,9 +10,9 @@ Utils.srand(8); const data = { datasets: [{ - data: Utils.points({count: DATA_COUNT, min: MIN[0], max: MAX[0]}), + data: Utils.points({count: DATA_COUNT, min: MIN[0], max: MAX[0]}) }, { - data: Utils.points({count: DATA_COUNT, min: MIN[1], max: MAX[1]}), + data: Utils.points({count: DATA_COUNT, min: MIN[1], max: MAX[1]}) }] }; // @@ -20,28 +20,28 @@ const data = { // const annotation1 = { type: 'box', - backgroundColor: 'rgba(0,150,0,0.02)', - borderColor: 'rgba(0,150,0,0.2)', - borderWidth: 1, + backgroundColor: 'rgba(0, 150, 0, 0.02)', + borderColor: 'rgba(0, 150, 0, 0.2)', borderRadius: 4, - xMin: (ctx) => min(ctx, 0, 'x') - 2, - yMin: (ctx) => min(ctx, 0, 'y') - 2, + borderWidth: 1, xMax: (ctx) => max(ctx, 0, 'x') + 2, - yMax: (ctx) => max(ctx, 0, 'y') + 2 + xMin: (ctx) => min(ctx, 0, 'x') - 2, + yMax: (ctx) => max(ctx, 0, 'y') + 2, + yMin: (ctx) => min(ctx, 0, 'y') - 2 }; // // const annotation2 = { type: 'box', - backgroundColor: 'rgba(150,0,0,0.02)', - borderColor: 'rgba(150,0,0,0.2)', - borderWidth: 1, + backgroundColor: 'rgba(150, 0, 0, 0.02)', + borderColor: 'rgba(150, 0, 0, 0.2)', borderRadius: 4, - xMin: (ctx) => min(ctx, 1, 'x') - 2, - yMin: (ctx) => min(ctx, 1, 'y') - 2, + borderWidth: 1, xMax: (ctx) => max(ctx, 1, 'x') + 2, - yMax: (ctx) => max(ctx, 1, 'y') + 2 + xMin: (ctx) => min(ctx, 1, 'x') - 2, + yMax: (ctx) => max(ctx, 1, 'y') + 2, + yMin: (ctx) => min(ctx, 1, 'y') - 2 }; // @@ -57,7 +57,7 @@ const config = { annotation2 } } - }, + } } }; /* */ @@ -75,7 +75,7 @@ function max(ctx, datasetIndex, prop) { // -var actions = [ +const actions = [ { name: 'Randomize', handler: function(chart) { @@ -85,7 +85,6 @@ var actions = [ p.y = Utils.rand(MIN[i], MAX[i]); }); }); - chart.update(); } }, @@ -105,7 +104,6 @@ var actions = [ chart.data.datasets.forEach(function(dataset, i) { dataset.data.shift(); }); - chart.update(); } } diff --git a/docs/samples/box/disclosure.md b/docs/samples/box/disclosure.md new file mode 100644 index 000000000..d9d659278 --- /dev/null +++ b/docs/samples/box/disclosure.md @@ -0,0 +1,69 @@ +# Disclosure + +```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: 'box', + backgroundColor: 'transparent', + borderWidth: 0, + label: { + drawTime: 'afterDatasetsDraw', + enabled: true, + color: 'rgba(208, 208, 208, 0.2)', + content: 'Draft', + font: { + size: (ctx) => ctx.chart.chartArea.height / 1.5 + }, + position: 'center' + } +}; +// + +/* */ +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/docs/samples/box/quarters.md b/docs/samples/box/quarters.md new file mode 100644 index 000000000..2cf9e8eb7 --- /dev/null +++ b/docs/samples/box/quarters.md @@ -0,0 +1,137 @@ +# Yearly quarters + +```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 annotation1 = { + type: 'box', + backgroundColor: 'rgba(255, 245, 157, 0.2)', + borderWidth: 0, + xMax: 2.5, + xMin: -0.5, + label: { + drawTime: 'afterDraw', + enabled: true, + content: 'First quarter', + position: { + x: 'center', + y: 'start' + } + } +}; +// + +// +const annotation2 = { + type: 'box', + backgroundColor: 'rgba(188, 170, 164, 0.2)', + borderWidth: 0, + xMax: 5.5, + xMin: 2.5, + label: { + drawTime: 'afterDraw', + enabled: true, + content: 'Second quarter', + position: { + x: 'center', + y: 'start' + } + } +}; +// + +// +const annotation3 = { + type: 'box', + backgroundColor: 'rgba(165, 214, 167, 0.2)', + borderWidth: 0, + xMax: 8.5, + xMin: 5.5, + label: { + drawTime: 'afterDraw', + enabled: true, + content: 'Third quarter', + position: { + x: 'center', + y: 'start' + } + } +}; +// + +// +const annotation4 = { + type: 'box', + backgroundColor: 'rgba(159, 168, 218, 0.2)', + borderWidth: 0, + xMin: 8.5, + label: { + drawTime: 'afterDraw', + enabled: true, + content: 'Fourth quarter', + position: { + x: 'center', + y: 'start' + } + } +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scales: { + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + drawTime: 'beforeDraw', + annotations: { + annotation1, + annotation2, + annotation3, + annotation4 + } + } + } + } +}; +/* */ + +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/docs/samples/charts/bar.md b/docs/samples/charts/bar.md index 831edd8f4..07fe60523 100644 --- a/docs/samples/charts/bar.md +++ b/docs/samples/charts/bar.md @@ -1,7 +1,7 @@ # Bar Chart ```js chart-editor -// +// const DATA_COUNT = 8; const MIN = 10; const MAX = 100; @@ -65,8 +65,8 @@ const annotation3 = { xMax: 3.5, yMin: 0, yMax: 100, - backgroundColor: 'rgba(250,250,0,0.4)', - borderColor: 'rgba(0,150,0,0.2)', + backgroundColor: 'rgba(250, 250, 0, 0.4)', + borderColor: 'rgba(0, 150, 0, 0.2)', drawTime: 'beforeDatasetsDraw', borderWidth: 0, borderRadius: 0, @@ -92,7 +92,7 @@ const config = { }; /* */ -var actions = [ +const actions = [ { name: 'Randomize', handler: function(chart) { diff --git a/docs/samples/charts/line.md b/docs/samples/charts/line.md index 638e17956..050b4660c 100644 --- a/docs/samples/charts/line.md +++ b/docs/samples/charts/line.md @@ -1,14 +1,13 @@ # Line Chart ```js chart-editor -// +// const DATA_COUNT = 8; const MIN = 10; const MAX = 100; Utils.srand(8); - const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX}; const data = { @@ -62,8 +61,8 @@ const annotation3 = { xMax: 85, yMin: 80, yMax: 90, - backgroundColor: 'rgba(250,250,0,0.4)', - borderColor: 'rgba(0,150,0,0.2)', + backgroundColor: 'rgba(250, 250, 0, 0.4)', + borderColor: 'rgba(0, 150, 0, 0.2)', drawTime: 'beforeDatasetsDraw', borderWidth: 0, borderRadius: 0, @@ -95,7 +94,7 @@ const config = { }; /* */ -var actions = [ +const actions = [ { name: 'Zoom out', handler: function(chart) { diff --git a/docs/samples/types/ellipse.md b/docs/samples/ellipse/basic.md similarity index 83% rename from docs/samples/types/ellipse.md rename to docs/samples/ellipse/basic.md index f9064629e..2b4f55c19 100644 --- a/docs/samples/types/ellipse.md +++ b/docs/samples/ellipse/basic.md @@ -1,4 +1,4 @@ -# Ellipse +# Basic ```js chart-editor // @@ -10,9 +10,9 @@ Utils.srand(8); const data = { datasets: [{ - data: Utils.points({count: DATA_COUNT, min: MIN[0], max: MAX[0]}), + data: Utils.points({count: DATA_COUNT, min: MIN[0], max: MAX[0]}) }, { - data: Utils.points({count: DATA_COUNT, min: MIN[1], max: MAX[1]}), + data: Utils.points({count: DATA_COUNT, min: MIN[1], max: MAX[1]}) }] }; // @@ -20,30 +20,26 @@ const data = { // const annotation1 = { type: 'ellipse', - backgroundColor: 'rgba(0,150,0,0.02)', - borderColor: 'rgba(0,150,0,0.2)', + backgroundColor: 'rgba(0, 150, 0, 0.02)', + borderColor: 'rgba(0, 150, 0, 0.2)', borderWidth: 1, - rotation: 0, - borderRadius: 4, - xMin: (ctx) => min(ctx, 0, 'x') - 10, - yMin: (ctx) => min(ctx, 0, 'y') - 10, xMax: (ctx) => max(ctx, 0, 'x') + 10, - yMax: (ctx) => max(ctx, 0, 'y') + 10 + xMin: (ctx) => min(ctx, 0, 'x') - 10, + yMax: (ctx) => max(ctx, 0, 'y') + 10, + yMin: (ctx) => min(ctx, 0, 'y') - 10 }; // // const annotation2 = { type: 'ellipse', - backgroundColor: 'rgba(150,0,0,0.02)', - borderColor: 'rgba(150,0,0,0.2)', + backgroundColor: 'rgba(150, 0, 0, 0.02)', + borderColor: 'rgba(150, 0, 0, 0.2)', borderWidth: 1, - rotation: 0, - borderRadius: 4, - xMin: (ctx) => min(ctx, 1, 'x') - 10, - yMin: (ctx) => min(ctx, 1, 'y') - 10, xMax: (ctx) => max(ctx, 1, 'x') + 10, - yMax: (ctx) => max(ctx, 1, 'y') + 10 + xMin: (ctx) => min(ctx, 1, 'x') - 10, + yMax: (ctx) => max(ctx, 1, 'y') + 10, + yMin: (ctx) => min(ctx, 1, 'y') - 10 }; // @@ -59,7 +55,7 @@ const config = { annotation2 } } - }, + } } }; /* */ @@ -74,10 +70,9 @@ function max(ctx, datasetIndex, prop) { const dataset = ctx.chart.data.datasets[datasetIndex]; return dataset.data.reduce((v, point) => Math.max(point[prop], v), -Infinity); } - // -var actions = [ +const actions = [ { name: 'Randomize', handler: function(chart) { @@ -87,7 +82,6 @@ var actions = [ p.y = Utils.rand(MIN[i], MAX[i]); }); }); - chart.update(); } }, @@ -107,7 +101,6 @@ var actions = [ chart.data.datasets.forEach(function(dataset, i) { dataset.data.shift(); }); - chart.update(); } } diff --git a/docs/samples/ellipse/rotation.md b/docs/samples/ellipse/rotation.md new file mode 100644 index 000000000..adaa3c386 --- /dev/null +++ b/docs/samples/ellipse/rotation.md @@ -0,0 +1,127 @@ +# Rotation + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = [25, 65]; +const MAX = [35, 75]; + +Utils.srand(8); + +const data = { + datasets: [{ + data: Utils.points({count: DATA_COUNT, min: MIN[0], max: MAX[0]}) + }, { + data: Utils.points({count: DATA_COUNT, min: MIN[1], max: MAX[1]}) + }] +}; +// + +// +const annotation1 = { + type: 'ellipse', + backgroundColor: 'rgba(0, 150, 0, 0.02)', + borderColor: 'rgba(0, 150, 0, 0.2)', + borderWidth: 1, + rotation: 90, + xMax: (ctx) => max(ctx, 0, 'x') + 10, + xMin: (ctx) => min(ctx, 0, 'x') - 10, + yMax: (ctx) => max(ctx, 0, 'y') + 10, + yMin: (ctx) => min(ctx, 0, 'y') - 10 +}; +// + +// +const annotation2 = { + type: 'ellipse', + backgroundColor: 'rgba(150, 0, 0, 0.02)', + borderColor: 'rgba(150, 0, 0, 0.2)', + borderWidth: 1, + rotation: 90, + xMax: (ctx) => max(ctx, 1, 'x') + 10, + xMin: (ctx) => min(ctx, 1, 'x') - 10, + yMax: (ctx) => max(ctx, 1, 'y') + 10, + yMin: (ctx) => min(ctx, 1, 'y') - 10 +}; +// + +/* */ +const config = { + type: 'scatter', + data, + options: { + scales: { + x: { + beginAtZero: true, + max: 100, + min: 0 + }, + y: { + beginAtZero: true, + max: 100, + min: 0 + } + }, + plugins: { + annotation: { + annotations: { + annotation1, + annotation2 + } + } + } + } +}; +/* */ + +// +function min(ctx, datasetIndex, prop) { + const dataset = ctx.chart.data.datasets[datasetIndex]; + return dataset.data.reduce((v, point) => Math.min(point[prop], v), Infinity); +} + +function max(ctx, datasetIndex, prop) { + const dataset = ctx.chart.data.datasets[datasetIndex]; + return dataset.data.reduce((v, point) => Math.max(point[prop], v), -Infinity); +} +// + +const actions = [ + { + name: 'Randomize', + handler: function(chart) { + chart.data.datasets.forEach(function(dataset, i) { + dataset.data.forEach(p => { + p.x = Utils.rand(MIN[i], MAX[i]); + p.y = Utils.rand(MIN[i], MAX[i]); + }); + }); + chart.update(); + } + }, + { + name: 'Add data', + handler: function(chart) { + chart.data.datasets.forEach(function(dataset, i) { + dataset.data.push({x: Utils.rand(MIN[i], MAX[i]), y: Utils.rand(MIN[i], MAX[i])}); + }); + 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, +}; +``` diff --git a/docs/samples/intro.md b/docs/samples/intro.md index 84cc2e9f4..4ffee719f 100644 --- a/docs/samples/intro.md +++ b/docs/samples/intro.md @@ -1,7 +1,7 @@ # Intro ```js chart-editor -// +// Utils.srand(8); const data = { @@ -32,37 +32,37 @@ const data = { // const annotation1 = { type: 'line', - scaleID: 'y', - value: Utils.rand(-100, 100), borderColor: 'black', borderWidth: 5, + click: function({chart, element}) { + console.log('Line annotation clicked'); + }, label: { backgroundColor: 'red', content: 'Test Label', enabled: true }, - click: function({chart, element}) { - console.log('Line annotation clicked'); - } + scaleID: 'y', + value: Utils.rand(-100, 100) }; // // const annotation2 = { - drawTime: 'beforeDatasetsDraw', type: 'box', - xScaleID: 'x', - yScaleID: 'y', - xMin: 'February', - xMax: 'April', - yMin: Utils.rand(-100, 100), - yMax: Utils.rand(-100, 100), backgroundColor: 'rgba(101, 33, 171, 0.5)', borderColor: 'rgb(101, 33, 171)', borderWidth: 1, click: function({chart, element}) { console.log('Box annotation clicked'); - } + }, + drawTime: 'beforeDatasetsDraw', + xMax: 'April', + xMin: 'February', + xScaleID: 'x', + yMax: Utils.rand(-100, 100), + yMin: Utils.rand(-100, 100), + yScaleID: 'y' }; // @@ -78,19 +78,18 @@ const config = { annotation2 } } - }, + } } }; /* */ -var actions = [ +const actions = [ { name: 'Randomize', handler: function(chart) { chart.data.datasets.forEach(function(dataset, i) { dataset.data = Utils.numbers({count: 7, min: -100, max: 100}); }); - chart.update(); } }, diff --git a/docs/samples/label/basic.md b/docs/samples/label/basic.md new file mode 100644 index 000000000..5017308aa --- /dev/null +++ b/docs/samples/label/basic.md @@ -0,0 +1,121 @@ +# Basic + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 0; +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 annotation1 = { + type: 'label', + borderColor: (ctx) => ctx.chart.data.datasets[0].backgroundColor, + borderRadius: 6, + borderWidth: 1, + content: ['March', 'annotated'], + position: { + x: 'center', + y: 'end' + }, + xValue: 'March', + yValue: (ctx) => yValue(ctx, 'March') +}; +// + +// +const annotation2 = { + type: 'label', + borderColor: (ctx) => ctx.chart.data.datasets[0].backgroundColor, + borderRadius: 6, + borderWidth: 1, + content: ['June', 'annotated'], + position: { + x: 'center', + y: 'end' + }, + xValue: 'June', + yValue: (ctx) => yValue(ctx, 'June') +}; +// + +// +const annotation3 = { + type: 'label', + borderColor: (ctx) => ctx.chart.data.datasets[0].backgroundColor, + borderRadius: 6, + borderWidth: 1, + content: ['October', 'annotated'], + position: { + x: 'center', + y: 'end' + }, + xValue: 'October', + yValue: (ctx) => yValue(ctx, 'October') +}; +// + +/* */ +const config = { + type: 'bar', + data, + options: { + scales: { + x: { + beginAtZero: true, + max: 120, + min: 0 + }, + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + annotations: { + annotation1, + annotation2, + annotation3 + } + } + } + } +}; +/* */ + +// +function yValue(ctx, label) { + const chart = ctx.chart; + const dataset = chart.data.datasets[0]; + return dataset.data[chart.data.labels.indexOf(label)]; +} +// + +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/docs/samples/label/callout.md b/docs/samples/label/callout.md new file mode 100644 index 000000000..b6c81bf3f --- /dev/null +++ b/docs/samples/label/callout.md @@ -0,0 +1,98 @@ +# Callout + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 0; +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: 'label', + backgroundColor: 'rgba(245, 245, 245)', + callout: { + enabled: true, + borderColor: (ctx) => ctx.chart.data.datasets[0].borderColor + }, + content: (ctx) => 'Maximum value is ' + maxValue(ctx).toFixed(2), + font: { + size: 16 + }, + position: { + x: (ctx) => maxIndex(ctx) <= 3 ? 'start' : maxIndex(ctx) >= 10 ? 'end' : 'center', + y: 'center' + }, + xAdjust: (ctx) => maxIndex(ctx) <= 3 ? 60 : maxIndex(ctx) >= 10 ? -60 : 0, + xValue: (ctx) => maxLabel(ctx), + yAdjust: -60, + yValue: (ctx) => maxValue(ctx) +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scales: { + y: { + beginAtZero: true, + max: 140, + min: 0 + } + }, + plugins: { + annotation: { + annotations: { + annotation + } + } + } + } +}; +/* */ + +// +function maxValue(ctx) { + const values = ctx.chart.data.datasets[0].data; + return Math.max(...values); +} + +function maxIndex(ctx) { + const max = maxValue(ctx); + const dataset = ctx.chart.data.datasets[0]; + return dataset.data.indexOf(max); +} + +function maxLabel(ctx) { + return ctx.chart.data.labels[maxIndex(ctx)]; +} +// + +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/docs/samples/label/lowerUpper.md b/docs/samples/label/lowerUpper.md new file mode 100644 index 000000000..e56b85258 --- /dev/null +++ b/docs/samples/label/lowerUpper.md @@ -0,0 +1,157 @@ +# Lower and upper bounds labels + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +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) + }, { + data: Utils.numbers(numberCfg) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'label', + content: (ctx) => 'Lower bound: ' + minValue(ctx).toFixed(3), + position: { + x: 'start', + y: 'end' + }, + xScaleID: 'x', + xValue: 2, + yScaleID: 'y', + yValue: minValue +}; +// + +// +const annotation2 = { + type: 'label', + content: (ctx) => 'Upper bound: ' + maxValue(ctx).toFixed(3), + position: { + x: 'start', + y: 'start' + }, + xScaleID: 'x', + xValue: 2, + yScaleID: 'y', + yValue: maxValue +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scales: { + y: { + stacked: true + } + }, + plugins: { + annotation: { + annotations: { + annotation1, + annotation2 + } + } + } + } +}; +/* */ + +// +function minValue(ctx) { + const dataset = ctx.chart.data.datasets[0]; + const min = dataset.data.reduce((max, point) => Math.min(point, max), Infinity); + return isFinite(min) ? min : 0; +} + +function maxValue(ctx) { + const datasets = ctx.chart.data.datasets; + const count = datasets[0].data.length; + let max = 0; + for (let i = 0; i < count; i++) { + let sum = 0; + for (const dataset of datasets) { + sum += dataset.data[i]; + } + max = Math.max(max, sum); + } + return max; +} +// + +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(); + } + }, + { + name: 'Cycle x-position', + handler: function(chart) { + const annotations = chart.options.plugins.annotation.annotations; + if (annotations.annotation1.position.x === 'start') { + annotations.annotation1.position.x = 'end'; + annotations.annotation2.position.x = 'end'; + } else if (annotations.annotation1.position.x === 'center') { + annotations.annotation1.position.x = 'start'; + annotations.annotation2.position.x = 'start'; + } else { + annotations.annotation1.position.x = 'center'; + annotations.annotation2.position.x = 'center'; + } + chart.update(); + } + } +]; + +module.exports = { + actions: actions, + config: config, +}; +``` diff --git a/docs/samples/label/point.md b/docs/samples/label/point.md new file mode 100644 index 000000000..820aee1a3 --- /dev/null +++ b/docs/samples/label/point.md @@ -0,0 +1,122 @@ +# Point + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 0; +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 annotation1 = { + type: 'label', + backgroundColor: 'rgba(245, 245, 245, 0.5)', + content: (ctx) => 'Maximum value is ' + maxValue(ctx).toFixed(2), + font: { + size: 16 + }, + padding: { + top: 6, + left: 6, + right: 6, + bottom: 12 + }, + position: { + x: (ctx) => maxIndex(ctx) <= 3 ? 'start' : maxIndex(ctx) >= 10 ? 'end' : 'center', + y: 'end' + }, + xValue: (ctx) => maxLabel(ctx), + yAdjust: -6, + yValue: (ctx) => maxValue(ctx) +}; +// + +// +const annotation2 = { + type: 'point', + backgroundColor: 'transparent', + borderColor: (ctx) => ctx.chart.data.datasets[0].borderColor, + pointStyle: 'rectRounded', + radius: 10, + xValue: (ctx) => maxLabel(ctx), + yValue: (ctx) => maxValue(ctx) +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + layout: { + padding: { + right: 10 + }, + }, + scales: { + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + clip: false, + annotations: { + annotation1, + annotation2 + } + } + } + } +}; +/* */ + +// +function maxValue(ctx) { + let max = 0; + const dataset = ctx.chart.data.datasets[0]; + dataset.data.forEach(function(el) { + max = Math.max(max, el); + }); + return max; +} + +function maxIndex(ctx) { + const max = maxValue(ctx); + const dataset = ctx.chart.data.datasets[0]; + return dataset.data.indexOf(max); +} + +function maxLabel(ctx) { + return ctx.chart.data.labels[maxIndex(ctx)]; +} +// + +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/docs/samples/line/animation.md b/docs/samples/line/animation.md new file mode 100644 index 000000000..66e37d39f --- /dev/null +++ b/docs/samples/line/animation.md @@ -0,0 +1,75 @@ +# Animation + + + +```js chart-editor +// +const uniqueId = new Date().getTime(); + +const data = { + datasets: [{ + backgroundColor: 'rgba(63, 184, 175, 0.3)', + borderColor: 'rgba(255, 0, 0, 0.0)', + pointRadius: 0, // no dots + tension: 0, // straight lines + showLine: true, + fill: true, + data: [ + {x: 0, y: 0}, + {x: 50, y: 0}, + {x: 50, y: 1}, + {x: 100, y: 1} + ] + }] +}; +// + +// +const line = { + type: 'line', + borderColor: 'red', + borderWidth: 3, + label: { + enabled: true, + content: 'Limit', + rotation: 90 + }, + scaleID: 'x', + value: 50 +}; +// + +/* */ +const config = { + type: 'scatter', + data, + options: { + _sampleId: uniqueId, + plugins: { + annotation: { + annotations: { + line + } + } + } + } +}; +/* */ + +// +document.getElementById('update').addEventListener('input', update); + +function update() { + const newValue = +document.querySelector('input[type=range]').value; + const chart = Object.values(Chart.instances).find(c => c.options._sampleId === uniqueId); + chart.data.datasets[0].data[1].x = newValue; + chart.data.datasets[0].data[2].x = newValue; + chart.options.plugins.annotation.annotations.line.value = newValue; + chart.update(); +} +// + +module.exports = { + config: config +}; +``` diff --git a/docs/samples/line/average.md b/docs/samples/line/average.md new file mode 100644 index 000000000..dbbd777da --- /dev/null +++ b/docs/samples/line/average.md @@ -0,0 +1,102 @@ +# Average + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +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) + }] +}; +// + +// +const annotation = { + type: 'line', + borderColor: 'black', + borderDash: [6, 6], + borderDashOffset: 0, + borderWidth: 3, + label: { + enabled: true, + content: (ctx) => 'Average: ' + average(ctx).toFixed(2), + position: 'end' + }, + scaleID: 'y', + value: (ctx) => average(ctx) +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + plugins: { + annotation: { + annotations: { + annotation + } + } + } + } +}; +/* */ + +// +function average(ctx) { + const values = ctx.chart.data.datasets[0].data; + return values.reduce((a, b) => a + b, 0) / values.length; +} +// + +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, +}; +``` diff --git a/docs/samples/line/basic.md b/docs/samples/line/basic.md new file mode 100644 index 000000000..f6e078e71 --- /dev/null +++ b/docs/samples/line/basic.md @@ -0,0 +1,93 @@ +# Basic + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +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) + }] +}; +// + +// +const annotation = { + type: 'line', + borderColor: 'black', + borderWidth: 3, + scaleID: 'y', + value: 50 +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scales: { + y: { + stacked: 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(); + } + }, + { + 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, +}; +``` diff --git a/docs/samples/line/datasetBars.md b/docs/samples/line/datasetBars.md new file mode 100644 index 000000000..5a9401fbb --- /dev/null +++ b/docs/samples/line/datasetBars.md @@ -0,0 +1,161 @@ +# Annotating dataset bars + +```js chart-editor +// +const DATA_COUNT = 4; +const MIN = 20; +const MAX = 100; + +Utils.srand(8); + +const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX}; + +const data = { + labels: ['January', 'February', 'March', 'April'], + datasets: [{ + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'line', + borderColor: 'green', + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'green', + borderRadius: 0, + color: 'white', + content: (ctx) => middleValue(ctx, 0, 0.5).toFixed(0) + }, + xMax: indexToMax(0) + 0.05, + xMin: indexToMin(0) - 0.05, + xScaleID: 'x', + yMax: (ctx) => middleValue(ctx, 0, 0.5), + yMin: (ctx) => middleValue(ctx, 0, 0.5), + yScaleID: 'y' +}; +// + +// +const annotation2 = { + type: 'line', + borderColor: 'green', + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'green', + borderRadius: 0, + color: 'white', + content: (ctx) => middleValue(ctx, 1, 0.75).toFixed(0) + }, + xMax: indexToMax(1) + 0.05, + xMin: indexToMin(1) - 0.05, + xScaleID: 'x', + yMax: (ctx) => middleValue(ctx, 1, 0.75), + yMin: (ctx) => middleValue(ctx, 1, 0.75), + yScaleID: 'y' +}; +// + +// +const annotation3 = { + type: 'line', + borderColor: 'green', + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'green', + borderRadius: 0, + color: 'white', + content: (ctx) => middleValue(ctx, 2, 1).toFixed(0) + }, + xMax: indexToMax(2) + 0.05, + xMin: indexToMin(2) - 0.05, + xScaleID: 'x', + yMax: (ctx) => middleValue(ctx, 2, 1), + yMin: (ctx) => middleValue(ctx, 2, 1), + yScaleID: 'y' +}; +// + +// +const annotation4 = { + type: 'line', + borderColor: 'green', + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'green', + borderRadius: 0, + color: 'white', + content: (ctx) => middleValue(ctx, 3, 0.25).toFixed(0) + }, + xMax: indexToMax(3) + 0.05, + xMin: indexToMin(3) - 0.05, + xScaleID: 'x', + yMax: (ctx) => middleValue(ctx, 3, 0.25), + yMin: (ctx) => middleValue(ctx, 3, 0.25), + yScaleID: 'y' +}; +// + +/* */ +const config = { + type: 'bar', + data, + options: { + plugins: { + annotation: { + annotations: { + annotation1, + annotation2, + annotation3, + annotation4 + } + } + } + } +}; +/* */ + +// +// categoryPercentage is 0.8 by default +// barPercentage is 0.9 by default +// 1 * 0.8 * 0.9 = 0.72 +// 0.72 / 2 = 0.36 + +function indexToMin(index) { + return index - 0.36; +} + +function indexToMax(index) { + return index + 0.36; +} + +function middleValue(ctx, index, perc) { + const chart = ctx.chart; + const dataset = chart.data.datasets[0]; + return dataset.data[index] * perc; +} +// + +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/docs/samples/line/labelVisibility.md b/docs/samples/line/labelVisibility.md new file mode 100644 index 000000000..a2ed727fb --- /dev/null +++ b/docs/samples/line/labelVisibility.md @@ -0,0 +1,150 @@ +# Label visibility + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +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) + }] +}; +// + +// +const annotation1 = { + type: 'line', + borderColor: 'lightGreen', + borderWidth: 10, + label: { + enabled: false, + backgroundColor: 'green', + drawTime: 'afterDatasetsDraw', + content: (ctx) => ['Average of dataset', 'is: ' + average(ctx).toFixed(3)] + }, + scaleID: 'y', + value: (ctx) => average(ctx), + // For simple property changes, you can directly modify the annotation + // element's properties then call chart.draw(). This is faster. + enter({chart, element}, event) { + element.options.label.enabled = true; + chart.draw(); + }, + leave({chart, element}, event) { + element.options.label.enabled = false; + chart.draw(); + } +}; +// + +// +const annotation2 = { + type: 'line', + borderColor: 'lightBlue', + borderWidth: 10, + label: { + enabled: (ctx) => ctx.hovered, + backgroundColor: 'blue', + drawTime: 'afterDatasetsDraw', + content: (ctx) => ['Min of dataset', 'is: ' + min(ctx).toFixed(3)], + position: (ctx) => ctx.hoverPosition + }, + scaleID: 'y', + value: (ctx) => min(ctx), + // For more complex dynamic properties, you can store values on the persistent + // context object then retrieve them via scriptable properties. You'll have + // to call chart.update() to reprocess the chart. + enter(ctx, event) { + ctx.hovered = true; + ctx.hoverPosition = (event.x / ctx.chart.chartArea.width * 100) + '%'; + ctx.chart.update(); + }, + leave(ctx, event) { + ctx.hovered = false; + ctx.chart.update(); + } +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + plugins: { + tooltip: { + enabled: false, + }, + annotation: { + drawTime: 'beforeDatasetsDraw', + annotations: { + annotation1, + annotation2 + } + } + } + } +}; +/* */ + +// +function average(ctx) { + const values = ctx.chart.data.datasets[0].data; + return values.reduce((a, b) => a + b, 0) / values.length; +} + +function min(ctx) { + const values = ctx.chart.data.datasets[0].data; + return values.reduce((a, b) => Math.min(a, b), Infinity); +} +// + +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, +}; +``` diff --git a/docs/samples/line/limited.md b/docs/samples/line/limited.md new file mode 100644 index 000000000..ae1f176a7 --- /dev/null +++ b/docs/samples/line/limited.md @@ -0,0 +1,126 @@ +# Limited lines + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 10; +const MAX = 100; + +Utils.srand(8); + +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) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'line', + borderColor: 'green', + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'lightGreen', + borderRadius: 0, + color: 'green', + content: 'Summer time' + }, + arrowHeads: { + start: { + enabled: true, + borderColor: 'green' + }, + end: { + enabled: true, + borderColor: 'green' + } + }, + xMax: 8, + xMin: 5, + xScaleID: 'x', + yMax: 110, + yMin: 110, + yScaleID: 'y' +}; +// + +// +const annotation2 = { + type: 'line', + borderColor: 'green', + borderDash: [6, 6], + borderWidth: 1, + xMax: 5, + xMin: 5, + xScaleID: 'x', + yMax: 0, + yMin: 110, + yScaleID: 'y' +}; +// + +// +const annotation3 = { + type: 'line', + borderColor: 'green', + borderDash: [6, 6], + borderWidth: 1, + xMax: 8, + xMin: 8, + xScaleID: 'x', + yMax: 0, + yMin: 110, + yScaleID: 'y' +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scale: { + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + drawTime: 'beforeDraw', + annotations: { + annotation1, + annotation2, + annotation3 + } + } + } + } +}; +/* */ + +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/docs/samples/types/line.md b/docs/samples/line/lowerUpper.md similarity index 69% rename from docs/samples/types/line.md rename to docs/samples/line/lowerUpper.md index 40c4f4c4c..e78b8f4cb 100644 --- a/docs/samples/types/line.md +++ b/docs/samples/line/lowerUpper.md @@ -1,4 +1,4 @@ -# Line +# Lower and upper bounds ```js chart-editor // @@ -18,11 +18,11 @@ const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX}; const data = { labels: labels, datasets: [{ - data: Utils.numbers(numberCfg), + data: Utils.numbers(numberCfg) }, { - data: Utils.numbers(numberCfg), + data: Utils.numbers(numberCfg) }, { - data: Utils.numbers(numberCfg), + data: Utils.numbers(numberCfg) }] }; // @@ -30,34 +30,38 @@ const data = { // const annotation1 = { type: 'line', - scaleID: 'y', - borderWidth: 3, borderColor: 'black', - value: minValue, + borderWidth: 3, label: { + enabled: true, + backgroundColor: 'black', + borderColor: 'black', + borderRadius: 10, + borderWidth: 2, content: (ctx) => 'Lower bound: ' + minValue(ctx).toFixed(3), - enabled: true + rotation: 'auto' }, + scaleID: 'y', + value: minValue }; // // const annotation2 = { type: 'line', - scaleID: 'y', borderWidth: 3, borderColor: 'black', - value: maxValue, label: { - rotation: 'auto', + enabled: true, backgroundColor: 'black', - borderColor: 'red', - borderDash: [6, 3], + borderColor: 'black', borderRadius: 10, borderWidth: 2, content: (ctx) => 'Upper bound: ' + maxValue(ctx).toFixed(3), - enabled: true - } + rotation: 'auto' + }, + scaleID: 'y', + value: maxValue }; // @@ -66,6 +70,11 @@ const config = { type: 'line', data, options: { + scales: { + y: { + stacked: true + } + }, plugins: { annotation: { annotations: { @@ -73,12 +82,6 @@ const config = { annotation2 } } - }, - // Core options - scales: { - y: { - stacked: true - } } } }; @@ -106,14 +109,13 @@ function maxValue(ctx) { } // -var actions = [ +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(); } }, @@ -124,7 +126,6 @@ var actions = [ chart.data.datasets.forEach(function(dataset, i) { dataset.data.push(Utils.rand(MIN, MAX)); }); - chart.update(); } }, @@ -135,7 +136,23 @@ var actions = [ chart.data.datasets.forEach(function(dataset, i) { dataset.data.shift(); }); - + chart.update(); + } + }, + { + name: 'Cycle position', + handler: function(chart) { + const annotations = chart.options.plugins.annotation.annotations; + if (annotations.annotation1.label.position === 'start') { + annotations.annotation1.label.position = 'end'; + annotations.annotation2.label.position = 'end'; + } else if (annotations.annotation1.label.position === 'center') { + annotations.annotation1.label.position = 'start'; + annotations.annotation2.label.position = 'start'; + } else { + annotations.annotation1.label.position = 'center'; + annotations.annotation2.label.position = 'center'; + } chart.update(); } } diff --git a/docs/samples/line/standardDeviation.md b/docs/samples/line/standardDeviation.md new file mode 100644 index 000000000..d24c6fbf0 --- /dev/null +++ b/docs/samples/line/standardDeviation.md @@ -0,0 +1,161 @@ +# Standard deviation + +```js chart-editor +// +const DATA_COUNT = 16; +const MIN = 10; +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) + }] +}; +// + +// +const annotation1 = { + type: 'line', + borderColor: 'rgb(100, 149, 237)', + borderDash: [6, 6], + borderDashOffset: 0, + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'rgb(100, 149, 237)', + content: (ctx) => 'Average: ' + average(ctx).toFixed(2) + }, + scaleID: 'y', + value: (ctx) => average(ctx) +}; +// + +// +const annotation2 = { + type: 'line', + borderColor: 'rgba(102, 102, 102, 0.5)', + borderDash: [6, 6], + borderDashOffset: 0, + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'rgba(102, 102, 102, 0.5)', + color: 'black', + content: (ctx) => (average(ctx) + standardDeviation(ctx)).toFixed(2), + position: 'start', + rotation: -90, + yAdjust: -28 + }, + scaleID: 'y', + value: (ctx) => average(ctx) + standardDeviation(ctx) +}; +// + +// +const annotation3 = { + type: 'line', + borderColor: 'rgba(102, 102, 102, 0.5)', + borderDash: [6, 6], + borderDashOffset: 0, + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'rgba(102, 102, 102, 0.5)', + color: 'black', + content: (ctx) => (average(ctx) - standardDeviation(ctx)).toFixed(2), + position: 'end', + rotation: 90, + yAdjust: 28 + }, + scaleID: 'y', + value: (ctx) => average(ctx) - standardDeviation(ctx) +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scale: { + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + annotations: { + annotation1, + annotation2, + annotation3 + } + } + } + } +}; +/* */ + +// +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); +} + +// + +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, +}; +``` diff --git a/docs/samples/line/visibility.md b/docs/samples/line/visibility.md new file mode 100644 index 000000000..81c54b4ad --- /dev/null +++ b/docs/samples/line/visibility.md @@ -0,0 +1,81 @@ +# Line visibility + +```js chart-editor +// +const DATA_COUNT = 6; +const MIN = 10; +const MAX = 100; + +Utils.srand(8); +const jointValue = Utils.rand(MIN, MAX); + +const dataCfg = Utils.numbers({count: DATA_COUNT, min: MIN, max: MAX}); +dataCfg.push(jointValue, NaN, NaN, NaN, NaN, NaN, NaN); + +const futureCfg = Utils.numbers({count: DATA_COUNT - 1, min: MIN, max: MAX}); +futureCfg.splice(0, 0, NaN, NaN, NaN, NaN, NaN, NaN, jointValue); + +const data = { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + datasets: [{ + data: dataCfg + }, { + data: futureCfg, + borderDash: [6, 6] + }] +}; +// + +// +const annotation = { + type: 'line', + borderColor: 'black', + borderWidth: 1, + display: (ctx) => ctx.chart.isDatasetVisible(1), + label: { + enabled: true, + content: 'Now', + position: 'start' + }, + scaleID: 'x', + value: 'July' +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + plugins: { + annotation: { + annotations: { + annotation + } + } + } + } +}; +/* */ + +const actions = [ + { + name: 'Toggle annotation', + handler: function(chart) { + const visible = chart.isDatasetVisible(1); + if (visible) { + chart.options.scales.x.max = 'July'; + } else { + chart.options.scales.x.max = undefined; + } + chart.setDatasetVisibility(1, !visible); + chart.update(); + } + } +]; + +module.exports = { + actions: actions, + config: config, +}; +``` diff --git a/docs/samples/types/point.md b/docs/samples/point/basic.md similarity index 77% rename from docs/samples/types/point.md rename to docs/samples/point/basic.md index 2a6dd09cf..501e608d4 100644 --- a/docs/samples/types/point.md +++ b/docs/samples/point/basic.md @@ -1,7 +1,7 @@ -# Point +# Basic ```js chart-editor -// +// const DATA_COUNT = 8; const MIN = 10; const MAX = 100; @@ -18,11 +18,11 @@ const numberCfg = {count: DATA_COUNT, min: MIN, max: MAX}; const data = { labels: labels, datasets: [{ - data: Utils.numbers(numberCfg), + data: Utils.numbers(numberCfg) }, { - data: Utils.numbers(numberCfg), + data: Utils.numbers(numberCfg) }, { - data: Utils.numbers(numberCfg), + data: Utils.numbers(numberCfg) }] }; // @@ -30,10 +30,10 @@ const data = { // const annotation1 = { type: 'point', - scaleID: 'y', - borderWidth: 3, + backgroundColor: 'rgba(0, 255, 255, 0.4)', borderColor: 'black', - backgroundColor: 'rgba(0,255,255,0.4)', + borderWidth: 3, + scaleID: 'y', xValue: (ctx) => value(ctx, 0, 2, 'x'), yValue: (ctx) => value(ctx, 0, 2, 'y') }; @@ -42,16 +42,31 @@ const annotation1 = { // const annotation2 = { type: 'point', - scaleID: 'y', - borderWidth: 5, - borderColor: 'red', backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 5, + pointStyle: 'triangle', radius: 25, + scaleID: 'y', xValue: (ctx) => value(ctx, 1, 4, 'x'), yValue: (ctx) => value(ctx, 1, 4, 'y') }; // +// +const annotation3 = { + type: 'point', + borderColor: 'orange', + borderWidth: 3, + drawTime: 'beforeDraw', + pointStyle: 'star', + radius: 25, + scaleID: 'y', + xValue: (ctx) => value(ctx, 1, 6, 'x'), + yValue: (ctx) => value(ctx, 1, 6, 'y') +}; +// + /* */ const config = { type: 'line', @@ -61,15 +76,16 @@ const config = { annotation: { annotations: { annotation1, - annotation2 + annotation2, + annotation3 } } - }, + } } }; /* */ -// +// function value(ctx, datasetIndex, index, prop) { const meta = ctx.chart.getDatasetMeta(datasetIndex); const parsed = meta.controller.getParsed(index); @@ -77,14 +93,13 @@ function value(ctx, datasetIndex, index, prop) { } // -var actions = [ +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(); } }, @@ -95,7 +110,6 @@ var actions = [ chart.data.datasets.forEach(function(dataset, i) { dataset.data.push(Utils.rand(MIN, MAX)); }); - chart.update(); } }, @@ -106,7 +120,6 @@ var actions = [ chart.data.datasets.forEach(function(dataset, i) { dataset.data.shift(); }); - chart.update(); } } diff --git a/docs/samples/point/combined.md b/docs/samples/point/combined.md new file mode 100644 index 000000000..9c786643c --- /dev/null +++ b/docs/samples/point/combined.md @@ -0,0 +1,137 @@ +# Combined annotations + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 10; +const MAX = 100; + +Utils.srand(8); + +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) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'line', + borderColor: 'green', + borderDash: [6, 6], + borderWidth: 3, + label: { + enabled: true, + backgroundColor: 'lightGreen', + borderRadius: 0, + color: 'green', + content: 'Project timeline' + }, + arrowHeads: { + end: { + enabled: true, + fill: true, + borderColor: 'green' + } + }, + xMax: 10.5, + xMin: 2.5, + xScaleID: 'x', + yMax: 110, + yMin: 110, + yScaleID: 'y' +}; +// + +// +const annotation2 = { + type: 'line', + borderColor: 'green', + borderDash: [6, 6], + borderWidth: 1, + xMax: 2.5, + xMin: 2.5, + xScaleID: 'x', + yMax: 0, + yMin: 110, + yScaleID: 'y' +}; +// + +// +const annotation3 = { + type: 'line', + borderColor: 'green', + borderDash: [6, 6], + borderWidth: 1, + xMax: 10.5, + xMin: 10.5, + xScaleID: 'x', + yMax: 0, + yMin: 110, + yScaleID: 'y' +}; +// + +// +const annotation4 = { + type: 'point', + backgroundColor: 'green', + borderWidth: 0, + xValue: 2.5, + xScaleID: 'x', + yValue: 110, + yScaleID: 'y' +}; +// + +/* */ +const config = { + type: 'bar', + data, + options: { + scale: { + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + drawTime: 'beforeDraw', + annotations: { + annotation1, + annotation2, + annotation3, + annotation4 + } + } + } + } +}; +/* */ + +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/docs/samples/point/outsideChartArea.md b/docs/samples/point/outsideChartArea.md new file mode 100644 index 000000000..916e41803 --- /dev/null +++ b/docs/samples/point/outsideChartArea.md @@ -0,0 +1,106 @@ +# Points outside of chart area + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 10; +const MAX = 100; + +Utils.srand(8); + +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) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'point', + backgroundColor: 'lime', + borderColor: 'black', + borderWidth: 1, + pointStyle: 'triangle', + radius: 15, + xValue: 3, + xScaleID: 'x', + yAdjust: 5, + yValue: 0, + yScaleID: 'y' +}; +// + +// +const annotation2 = { + type: 'point', + backgroundColor: 'lime', + borderColor: 'black', + borderWidth: 1, + pointStyle: 'triangle', + radius: 15, + rotation: 180, + xValue: 3, + xScaleID: 'x', + yAdjust: -5, + yValue: 100, + yScaleID: 'y' +}; +// + +/* */ +const config = { + type: 'bar', + data, + options: { + layout: { + padding: { + top: 16 + } + }, + scale: { + y: { + beginAtZero: true, + max: 100, + min: 0 + } + }, + plugins: { + annotation: { + clip: false, + drawTime: 'afterDraw', + annotations: { + annotation1, + annotation2 + } + } + } + } +}; +/* */ + +const actions = [ + { + name: 'Randomize', + handler: function(chart) { + chart.data.datasets.forEach(function(dataset, i) { + dataset.data = dataset.data.map(() => Utils.rand(MIN, MAX)); + }); + const xValue = Utils.rand(0, DATA_COUNT - 1); + chart.options.plugins.annotation.annotations.annotation1.xValue = xValue; + chart.options.plugins.annotation.annotations.annotation2.xValue = xValue; + chart.update(); + } + } +]; + +module.exports = { + actions: actions, + config: config, +}; +``` diff --git a/docs/samples/point/shadow.md b/docs/samples/point/shadow.md new file mode 100644 index 000000000..603c7b71d --- /dev/null +++ b/docs/samples/point/shadow.md @@ -0,0 +1,144 @@ +# Shadow + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +const MAX = 100; + +Utils.srand(5); + +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) + }, { + data: Utils.numbers(numberCfg) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'point', + backgroundColor: 'rgba(0, 255, 255, 0.4)', + backgroundShadowColor: 'black', + borderColor: 'black', + borderWidth: 3, + scaleID: 'y', + shadowBlur: 3, + shadowOffsetX: 3, + shadowOffsetY: 10, + xValue: (ctx) => value(ctx, 0, 2, 'x'), + yValue: (ctx) => value(ctx, 0, 2, 'y') +}; +// + +// +const annotation2 = { + type: 'point', + backgroundColor: 'rgba(245, 245, 245, 0.5)', + borderColor: 'red', + borderShadowColor: 'gray', + borderWidth: 5, + pointStyle: 'triangle', + radius: 25, + scaleID: 'y', + shadowBlur: 3, + shadowOffsetX: 3, + shadowOffsetY: 10, + xValue: (ctx) => value(ctx, 1, 4, 'x'), + yValue: (ctx) => value(ctx, 1, 4, 'y') +}; +// + +// +const annotation3 = { + type: 'point', + borderColor: 'orange', + borderShadowColor: 'black', + borderWidth: 3, + drawTime: 'beforeDraw', + pointStyle: 'star', + radius: 25, + scaleID: 'y', + shadowBlur: 3, + shadowOffsetX: 3, + shadowOffsetY: 10, + xValue: (ctx) => value(ctx, 1, 6, 'x'), + yValue: (ctx) => value(ctx, 1, 6, 'y') +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + plugins: { + annotation: { + annotations: { + annotation1, + annotation2, + annotation3 + } + } + } + } +}; +/* */ + +// +function value(ctx, datasetIndex, index, prop) { + const meta = ctx.chart.getDatasetMeta(datasetIndex); + const parsed = meta.controller.getParsed(index); + return parsed ? parsed[prop] : NaN; +} +// + +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 +}; +``` diff --git a/docs/samples/polygon/basic.md b/docs/samples/polygon/basic.md new file mode 100644 index 000000000..7ac8b92dd --- /dev/null +++ b/docs/samples/polygon/basic.md @@ -0,0 +1,147 @@ +# Basic + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +const MAX = 100; + +Utils.srand(5); + +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) + }, { + data: Utils.numbers(numberCfg) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'polygon', + backgroundColor: 'rgba(0, 255, 255, 0.4)', + borderColor: 'black', + borderWidth: 3, + radius: 25, + scaleID: 'y', + xValue: (ctx) => value(ctx, 0, 2, 'x'), + yValue: (ctx) => value(ctx, 0, 2, 'y') +}; +// + +// +const annotation2 = { + type: 'polygon', + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 5, + radius: 25, + scaleID: 'y', + sides: 5, + xValue: (ctx) => value(ctx, 1, 4, 'x'), + yValue: (ctx) => value(ctx, 1, 4, 'y') +}; +// + +// +const annotation3 = { + type: 'polygon', + backgroundColor: 'transparent', + borderColor: 'gray', + borderWidth: 3, + radius: 30, + sides: 8, + scaleID: 'y', + xValue: (ctx) => value(ctx, 1, 6, 'x'), + yValue: (ctx) => value(ctx, 1, 6, 'y') +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + plugins: { + annotation: { + annotations: { + annotation1, + annotation2, + annotation3 + } + } + } + } +}; +/* */ + +// +function value(ctx, datasetIndex, index, prop) { + const meta = ctx.chart.getDatasetMeta(datasetIndex); + const parsed = meta.controller.getParsed(index); + return parsed ? parsed[prop] : NaN; +} +// + +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(); + } + }, + { + name: 'Add a side to annotation 1', + handler: function(chart) { + chart.options.plugins.annotation.annotations.annotation1.sides++; + chart.update(); + } + }, + { + name: 'Remove a side from annotation 1', + handler: function(chart) { + chart.options.plugins.annotation.annotations.annotation1.sides--; + chart.update(); + } + } +]; + +module.exports = { + actions: actions, + config: config +}; +``` diff --git a/docs/samples/polygon/outsideChartArea.md b/docs/samples/polygon/outsideChartArea.md new file mode 100644 index 000000000..141316dda --- /dev/null +++ b/docs/samples/polygon/outsideChartArea.md @@ -0,0 +1,119 @@ +# Polygons outside of chart area + +```js chart-editor +// +const DATA_COUNT = 12; +const MIN = 10; +const MAX = 100; + +Utils.srand(8); + +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) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'line', + borderColor: 'lime', + borderWidth: 2, + scaleID: 'x', + value: 3 +}; +// + +// +const annotation2 = { + type: 'polygon', + backgroundColor: 'lime', + borderColor: 'black', + borderWidth: 1, + radius: 15, + sides: 5, + xValue: 3, + xScaleID: 'x', + yAdjust: 5, + yValue: 0, + yScaleID: 'y' +}; +// + +// +const annotation3 = { + type: 'polygon', + backgroundColor: 'lime', + borderColor: 'black', + borderWidth: 1, + pointStyle: 'triangle', + radius: 15, + rotation: 180, + sides: 5, + xValue: 3, + xScaleID: 'x', + yAdjust: -5, + yValue: 100, + yScaleID: 'y' +}; +// + +/* */ +const config = { + type: 'bar', + data, + options: { + layout: { + padding: { + top: 20 + } + }, + scale: { + y: { + beginAtZero: true, + max: 100, + min: 0 + } + }, + plugins: { + annotation: { + clip: false, + drawTime: 'afterDraw', + annotations: { + annotation1, + annotation2, + annotation3 + } + } + } + } +}; +/* */ + +const actions = [ + { + name: 'Randomize', + handler: function(chart) { + chart.data.datasets.forEach(function(dataset, i) { + dataset.data = dataset.data.map(() => Utils.rand(MIN, MAX)); + }); + const xValue = Utils.rand(0, DATA_COUNT - 1); + chart.options.plugins.annotation.annotations.annotation1.value = xValue; + chart.options.plugins.annotation.annotations.annotation2.xValue = xValue; + chart.options.plugins.annotation.annotations.annotation3.xValue = xValue; + chart.update(); + } + } +]; + +module.exports = { + actions: actions, + config: config, +}; +``` diff --git a/docs/samples/polygon/shadow.md b/docs/samples/polygon/shadow.md new file mode 100644 index 000000000..054c6f273 --- /dev/null +++ b/docs/samples/polygon/shadow.md @@ -0,0 +1,182 @@ +# Shadow + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +const MAX = 100; + +Utils.srand(5); + +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) + }, { + data: Utils.numbers(numberCfg) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'polygon', + backgroundColor: 'white', + backgroundShadowColor: 'black', + borderColor: 'red', + borderJoinStyle: 'round', + borderWidth: 7, + radius: 40, + rotation: 180, + shadowBlur: 3, + shadowOffsetX: 3, + shadowOffsetY: 10, + xValue: (ctx) => value(ctx, 0, 2, 'x'), + yValue: (ctx) => value(ctx, 0, 2, 'y') +}; +// + +// +const annotation2 = { + type: 'polygon', + backgroundColor: 'white', + backgroundShadowColor: 'black', + borderColor: 'red', + borderJoinStyle: 'round', + borderWidth: 7, + radius: 40, + shadowBlur: 3, + shadowOffsetX: 3, + shadowOffsetY: 10, + xValue: (ctx) => value(ctx, 0, 4, 'x'), + yValue: (ctx) => value(ctx, 0, 4, 'y') +}; +// + +// +const annotation3 = { + type: 'label', + color: 'black', + content: '!', + font: { + size: 40, + family: 'Arial', + weight: 'bold' + }, + xValue: (ctx) => value(ctx, 0, 4, 'x'), + yValue: (ctx) => value(ctx, 0, 4, 'y') +}; +// + +// +const annotation4 = { + type: 'polygon', + backgroundColor: 'red', + backgroundShadowColor: 'black', + borderColor: 'white', + borderWidth: 4, + radius: 40, + rotation: 22.5, + shadowBlur: 3, + shadowOffsetX: 3, + shadowOffsetY: 10, + sides: 8, + xValue: (ctx) => value(ctx, 0, 6, 'x'), + yValue: (ctx) => value(ctx, 0, 6, 'y') +}; +// + +// +const annotation5 = { + type: 'label', + color: 'white', + content: 'STOP', + font: { + size: 24 + }, + xValue: (ctx) => value(ctx, 0, 6, 'x'), + yValue: (ctx) => value(ctx, 0, 6, 'y') +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scales: { + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + annotations: { + annotation1, + annotation2, + annotation3, + annotation4, + annotation5 + } + } + } + } +}; +/* */ + +// +function value(ctx, datasetIndex, index, prop) { + const meta = ctx.chart.getDatasetMeta(datasetIndex); + const parsed = meta.controller.getParsed(index); + return parsed ? parsed[prop] : NaN; +} +// + +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 +}; +``` diff --git a/docs/samples/polygon/stop.md b/docs/samples/polygon/stop.md new file mode 100644 index 000000000..9ec51c3fa --- /dev/null +++ b/docs/samples/polygon/stop.md @@ -0,0 +1,170 @@ +# Stop + +```js chart-editor +// +const DATA_COUNT = 8; +const MIN = 10; +const MAX = 100; + +Utils.srand(5); + +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) + }, { + data: Utils.numbers(numberCfg) + }, { + data: Utils.numbers(numberCfg) + }] +}; +// + +// +const annotation1 = { + type: 'polygon', + backgroundColor: 'white', + borderColor: 'red', + borderJoinStyle: 'round', + borderWidth: 7, + radius: 40, + rotation: 180, + xValue: (ctx) => value(ctx, 0, 2, 'x'), + yValue: (ctx) => value(ctx, 0, 2, 'y') +}; +// + +// +const annotation2 = { + type: 'polygon', + backgroundColor: 'white', + borderColor: 'red', + borderJoinStyle: 'round', + borderWidth: 7, + radius: 40, + xValue: (ctx) => value(ctx, 0, 4, 'x'), + yValue: (ctx) => value(ctx, 0, 4, 'y') +}; +// + +// +const annotation3 = { + type: 'label', + color: 'black', + content: '!', + font: { + size: 40, + family: 'Arial', + weight: 'bold' + }, + xValue: (ctx) => value(ctx, 0, 4, 'x'), + yValue: (ctx) => value(ctx, 0, 4, 'y') +}; +// + +// +const annotation4 = { + type: 'polygon', + backgroundColor: 'red', + borderColor: 'white', + borderWidth: 4, + radius: 40, + rotation: 22.5, + sides: 8, + xValue: (ctx) => value(ctx, 0, 6, 'x'), + yValue: (ctx) => value(ctx, 0, 6, 'y') +}; +// + +// +const annotation5 = { + type: 'label', + color: 'white', + content: 'STOP', + font: { + size: 24 + }, + xValue: (ctx) => value(ctx, 0, 6, 'x'), + yValue: (ctx) => value(ctx, 0, 6, 'y') +}; +// + +/* */ +const config = { + type: 'line', + data, + options: { + scales: { + y: { + beginAtZero: true, + max: 120, + min: 0 + } + }, + plugins: { + annotation: { + annotations: { + annotation1, + annotation2, + annotation3, + annotation4, + annotation5 + } + } + } + } +}; +/* */ + +// +function value(ctx, datasetIndex, index, prop) { + const meta = ctx.chart.getDatasetMeta(datasetIndex); + const parsed = meta.controller.getParsed(index); + return parsed ? parsed[prop] : NaN; +} +// + +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 +}; +``` diff --git a/karma.conf.js b/karma.conf.js index e601e25ff..daf662ef2 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,3 +1,4 @@ +const jasmineSeedReporter = require('./test/seed-reporter'); const istanbul = require('rollup-plugin-istanbul'); const json = require('@rollup/plugin-json'); const resolve = require('@rollup/plugin-node-resolve').default; @@ -27,7 +28,8 @@ module.exports = function(karma) { karma.set({ frameworks: ['jasmine'], - reporters: ['progress', 'kjhtml'], + plugins: ['karma-*', jasmineSeedReporter], + reporters: ['progress', 'kjhtml', 'jasmine-seed'], browsers: (args.browsers || 'chrome,firefox').split(','), logLevel: karma.LOG_INFO, @@ -44,7 +46,10 @@ module.exports = function(karma) { chrome: { base: 'Chrome', flags: [ - '--disable-accelerated-2d-canvas' + '--disable-accelerated-2d-canvas', + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-renderer-backgrounding' ] }, firefox: { diff --git a/old_samples/.eslintrc.yml b/old_samples/.eslintrc.yml deleted file mode 100644 index e0de7c41c..000000000 --- a/old_samples/.eslintrc.yml +++ /dev/null @@ -1,8 +0,0 @@ -globals: - Chart: true - randomScalingFactor: true - -rules: - indent: ["error", "tab", {flatTernaryExpressions: true}] - no-console: "off" - consistent-this: "off" diff --git a/old_samples/animation.html b/old_samples/animation.html deleted file mode 100644 index 2dd3bf672..000000000 --- a/old_samples/animation.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - Bar Chart - - - - - - - -
-
- -
- - - - diff --git a/old_samples/autoRotation.html b/old_samples/autoRotation.html deleted file mode 100644 index 074b91097..000000000 --- a/old_samples/autoRotation.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - -AutoRotation on Bar Chart - - - - - - - -
- -
- - - - - - - diff --git a/old_samples/bar.html b/old_samples/bar.html deleted file mode 100644 index 856257e5a..000000000 --- a/old_samples/bar.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - - Bar Chart - - - - - - - -
- -
- - - - - - - - - diff --git a/old_samples/box.html b/old_samples/box.html deleted file mode 100644 index e9ed08117..000000000 --- a/old_samples/box.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - Scatter Chart - - - - - - -
-
- -
-
- - - - diff --git a/old_samples/bubble.html b/old_samples/bubble.html deleted file mode 100644 index 3febedcd0..000000000 --- a/old_samples/bubble.html +++ /dev/null @@ -1,212 +0,0 @@ - - - - - Bubble Chart - - - - - - - -
- -
- - - - - - - - - \ No newline at end of file diff --git a/old_samples/combo-bar-line.html b/old_samples/combo-bar-line.html deleted file mode 100644 index bde537b01..000000000 --- a/old_samples/combo-bar-line.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - Combo Bar-Line Chart - - - - - - - -
- -
- - - - - diff --git a/old_samples/ellipse.html b/old_samples/ellipse.html deleted file mode 100644 index 4fd7e5e6d..000000000 --- a/old_samples/ellipse.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - Scatter Chart - - - - - - -
-
- -
-
- - - - diff --git a/old_samples/horizontal-line.html b/old_samples/horizontal-line.html deleted file mode 100644 index 36cddaffb..000000000 --- a/old_samples/horizontal-line.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - Scatter Chart - - - - - - -
-
- -
-
- - - - diff --git a/old_samples/imageLabels.html b/old_samples/imageLabels.html deleted file mode 100644 index f013fb8f1..000000000 --- a/old_samples/imageLabels.html +++ /dev/null @@ -1,212 +0,0 @@ - - - - - Scatter Chart - - - - - - -
-
- -
-
- - - - diff --git a/old_samples/labels.html b/old_samples/labels.html deleted file mode 100644 index d2a9dc8ed..000000000 --- a/old_samples/labels.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - - Scatter Chart - - - - - - -
-
- -
-
- - - - diff --git a/old_samples/line-logarithmic.html b/old_samples/line-logarithmic.html deleted file mode 100644 index a1803da63..000000000 --- a/old_samples/line-logarithmic.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - Logarithmic Line Chart - - - - - - - -
- -
- - - - - diff --git a/old_samples/line-time-scale.html b/old_samples/line-time-scale.html deleted file mode 100644 index 31989f00a..000000000 --- a/old_samples/line-time-scale.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - Line Chart - - - - - - - - -
- -
-
-
- - - - - - - - - diff --git a/old_samples/pie.html b/old_samples/pie.html deleted file mode 100644 index 0c4df87b8..000000000 --- a/old_samples/pie.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - Pie Chart - - - - - - -
- -
- - - - - - - \ No newline at end of file diff --git a/old_samples/point.html b/old_samples/point.html deleted file mode 100644 index 1e88af20f..000000000 --- a/old_samples/point.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - Scatter Chart - - - - - - -
-
- -
-
- - - - diff --git a/old_samples/polar-area.html b/old_samples/polar-area.html deleted file mode 100644 index 95133d6b9..000000000 --- a/old_samples/polar-area.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - Polar Area Chart - - - - - - - -
- -
- - - - - - - \ No newline at end of file diff --git a/old_samples/radar.html b/old_samples/radar.html deleted file mode 100644 index 2c8743215..000000000 --- a/old_samples/radar.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - Radar Chart - - - - - - - -
- -
- - - - - - - - - \ No newline at end of file diff --git a/old_samples/updates.html b/old_samples/updates.html deleted file mode 100644 index ddf804772..000000000 --- a/old_samples/updates.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - Scatter Chart - - - - - - -
- -
- - - - - - - diff --git a/package-lock.json b/package-lock.json index fbc08d8c0..428bc2ed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,39 +1,39 @@ { "name": "chartjs-plugin-annotation", - "version": "1.0.2", + "version": "1.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.16.0" } }, "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.16.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", + "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", "dev": true }, "@babel/core": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.0.tgz", - "integrity": "sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.0", - "@babel/helper-module-transforms": "^7.15.0", - "@babel/helpers": "^7.14.8", - "@babel/parser": "^7.15.0", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.5.tgz", + "integrity": "sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.5", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helpers": "^7.16.5", + "@babel/parser": "^7.16.5", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -42,15 +42,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -66,12 +57,12 @@ } }, "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz", + "integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==", "dev": true, "requires": { - "@babel/types": "^7.15.0", + "@babel/types": "^7.16.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -85,33 +76,33 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", - "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", + "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz", - "integrity": "sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz", + "integrity": "sha512-3JEA9G5dmmnIWdzaT9d0NmFRgYnWUThLsDaL7982H0XqqWr56lRrsmwheXFMjR+TMl7QMBb6mzy9kvgr1lRLUA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/helper-explode-assignable-expression": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/helper-compilation-targets": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz", - "integrity": "sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", + "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", + "@babel/compat-data": "^7.16.0", "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", + "browserslist": "^4.17.5", "semver": "^6.3.0" }, "dependencies": { @@ -124,33 +115,34 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.0.tgz", - "integrity": "sha512-MdmDXgvTIi4heDVX/e9EFfeGpugqm9fobBVg/iioE8kueXrOHdRDe36FAY7SnE9xXLVeYCoJR/gdrBEIHRC83Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz", + "integrity": "sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-member-expression-to-functions": "^7.15.0", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.0", - "@babel/helper-split-export-declaration": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-member-expression-to-functions": "^7.16.5", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/helper-replace-supers": "^7.16.5", + "@babel/helper-split-export-declaration": "^7.16.0" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz", + "integrity": "sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-annotate-as-pure": "^7.16.0", "regexpu-core": "^4.7.1" } }, "@babel/helper-define-polyfill-provider": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", - "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz", + "integrity": "sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.13.0", @@ -171,147 +163,157 @@ } } }, + "@babel/helper-environment-visitor": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz", + "integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, "@babel/helper-explode-assignable-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz", - "integrity": "sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz", + "integrity": "sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", + "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/helper-get-function-arity": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", + "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", + "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", - "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz", + "integrity": "sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==", "dev": true, "requires": { - "@babel/types": "^7.15.0" + "@babel/types": "^7.16.0" } }, "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-module-transforms": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz", - "integrity": "sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz", + "integrity": "sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.0", - "@babel/helper-simple-access": "^7.14.8", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", + "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", + "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz", - "integrity": "sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz", + "integrity": "sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-wrap-function": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-wrap-function": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helper-replace-supers": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", - "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz", + "integrity": "sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.0", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-member-expression-to-functions": "^7.16.5", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helper-simple-access": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", - "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", + "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", "dev": true, "requires": { - "@babel/types": "^7.14.8" + "@babel/types": "^7.16.0" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz", - "integrity": "sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", + "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" } }, "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/helper-validator-option": { @@ -321,35 +323,35 @@ "dev": true }, "@babel/helper-wrap-function": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz", - "integrity": "sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz", + "integrity": "sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/helper-function-name": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helpers": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.3.tgz", - "integrity": "sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.5.tgz", + "integrity": "sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw==", "dev": true, "requires": { - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.15.0", - "@babel/types": "^7.15.0" + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.15.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -413,189 +415,198 @@ } }, "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "version": "7.16.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz", + "integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==", "dev": true }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz", + "integrity": "sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz", - "integrity": "sha512-ZoJS2XCKPBfTmL122iP6NM9dOg+d4lc9fFk3zxc8iDjvt8Pk4+TlsHSKhIPf6X+L5ORCdBzqMZDjL/WHj7WknQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz", + "integrity": "sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.9.tgz", - "integrity": "sha512-d1lnh+ZnKrFKwtTYdw320+sQWCTwgkB9fmUhNXRADA4akR6wLjaruSGnIEUjpt9HCOwTr4ynFTKu19b7rFRpmw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.5.tgz", + "integrity": "sha512-C/FX+3HNLV6sz7AqbTQqEo1L9/kfrKjxcVtgyBCmvIgOjvuBVUWooDoi7trsLxOzCEo5FccjRvKHkfDsJFZlfA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-remap-async-to-generator": "^7.16.5", "@babel/plugin-syntax-async-generators": "^7.8.4" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", - "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz", + "integrity": "sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.5.tgz", - "integrity": "sha512-KBAH5ksEnYHCegqseI5N9skTdxgJdmDoAOc0uXa+4QMYKeZD0w5IARh4FMlTNtaHhbB8v+KzMdTgxMMzsIy6Yg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.5.tgz", + "integrity": "sha512-EEFzuLZcm/rNJ8Q5krK+FRKdVkd6FjfzT9tuSZql9sQn64K0hHA2KLJ0DqVot9/iV6+SsuadC5yI39zWnm+nmQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-proposal-decorators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.14.5.tgz", - "integrity": "sha512-LYz5nvQcvYeRVjui1Ykn28i+3aUiXwQ/3MGoEy0InTaz1pJo/lAzmIDXX+BQny/oufgHzJ6vnEEiXQ8KZjEVFg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.16.5.tgz", + "integrity": "sha512-XAiZll5oCdp2Dd2RbXA3LVPlFyIRhhcQy+G34p9ePpl6mjFkbqHAYHovyw2j5mqUrlBf0/+MtOIJ3JGYtz8qaw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-decorators": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/plugin-syntax-decorators": "^7.16.5" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", - "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz", + "integrity": "sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", - "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz", + "integrity": "sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", - "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.5.tgz", + "integrity": "sha512-QQJueTFa0y9E4qHANqIvMsuxM/qcLQmKttBACtPCQzGUEizsXDACGonlPiSwynHfOa3vNw0FPMVvQzbuXwh4SQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", - "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz", + "integrity": "sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", - "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz", + "integrity": "sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", - "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz", + "integrity": "sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", - "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.5.tgz", + "integrity": "sha512-UEd6KpChoyPhCoE840KRHOlGhEZFutdPDMGj+0I56yuTTOaT51GzmnEl/0uT41fB/vD2nT+Pci2KjezyE3HmUw==", "dev": true, "requires": { - "@babel/compat-data": "^7.14.7", - "@babel/helper-compilation-targets": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.14.5" + "@babel/plugin-transform-parameters": "^7.16.5" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", - "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.5.tgz", + "integrity": "sha512-ihCMxY1Iljmx4bWy/PIMJGXN4NS4oUj1MKynwO07kiKms23pNvIn1DMB92DNB2R0EA882sw0VXIelYGdtF7xEQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", - "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz", + "integrity": "sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", - "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz", + "integrity": "sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-62EyfyA3WA0mZiF2e2IV9mc9Ghwxcg8YTu8BS4Wss4Y3PY725OmS9M0qLORbJwLqFtGh+jiE4wAmocK2CTUK2Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz", + "integrity": "sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", - "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.5.tgz", + "integrity": "sha512-s5sKtlKQyFSatt781HQwv1hoM5BQ9qRH30r+dK56OLDsHmV74mzwJNX7R1yMuE7VZKG5O6q/gmOGSAO6ikTudg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-syntax-async-generators": { @@ -626,12 +637,12 @@ } }, "@babel/plugin-syntax-decorators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.14.5.tgz", - "integrity": "sha512-c4sZMRWL4GSvP1EXy0woIP7m4jkVcEuG8R1TOZxPBPtp4FSM/kiPZub9UIs/Jrb5ZAOzvTUSGYrWsrSu1JvoPw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.16.5.tgz", + "integrity": "sha512-3CbYTXfflvyy8O819uhZcZSMedZG4J8yS/NLTc/8T24M9ke1GssTGvg8VZu3Yn2LU5IyQSv1CmPq0a9JWHXJwg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-syntax-dynamic-import": { @@ -662,12 +673,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz", + "integrity": "sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -743,55 +754,56 @@ } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", - "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz", + "integrity": "sha512-8bTHiiZyMOyfZFULjsCnYOWG059FVMes0iljEHSfARhNgFfpsqE92OrCffv3veSw9rwMkYcFe9bj0ZoXU2IGtQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", - "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz", + "integrity": "sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-remap-async-to-generator": "^7.16.5" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", - "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.5.tgz", + "integrity": "sha512-BxmIyKLjUGksJ99+hJyL/HIxLIGnLKtw772zYDER7UuycDZ+Xvzs98ZQw6NGgM2ss4/hlFAaGiZmMNKvValEjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.5.tgz", + "integrity": "sha512-JxjSPNZSiOtmxjX7PBRBeRJTUKTyJ607YUYeT0QJCNdsedOe+/rXITjP08eG8xUpsLfPirgzdCFN+h0w6RI+pQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-classes": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.9.tgz", - "integrity": "sha512-NfZpTcxU3foGWbl4wxmZ35mTsYJy8oQocbeIMoDAGGFarAmSQlL+LWMkDx/tj6pNotpbX3rltIA4dprgAPOq5A==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.5.tgz", + "integrity": "sha512-DzJ1vYf/7TaCYy57J3SJ9rV+JEuvmlnvvyvYKFbk5u46oQbBvuB9/0w+YsVsxkOv8zVWKpDmUoj4T5ILHoXevA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-replace-supers": "^7.16.5", + "@babel/helper-split-export-declaration": "^7.16.0", "globals": "^11.1.0" }, "dependencies": { @@ -804,210 +816,210 @@ } }, "@babel/plugin-transform-computed-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", - "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.5.tgz", + "integrity": "sha512-n1+O7xtU5lSLraRzX88CNcpl7vtGdPakKzww74bVwpAIRgz9JVLJJpOLb0uYqcOaXVM0TL6X0RVeIJGD2CnCkg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-destructuring": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", - "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.5.tgz", + "integrity": "sha512-GuRVAsjq+c9YPK6NeTkRLWyQskDC099XkBSVO+6QzbnOnH2d/4mBVXYStaPrZD3dFRfg00I6BFJ9Atsjfs8mlg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", - "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.5.tgz", + "integrity": "sha512-iQiEMt8Q4/5aRGHpGVK2Zc7a6mx7qEAO7qehgSug3SDImnuMzgmm/wtJALXaz25zUj1PmnNHtShjFgk4PDx4nw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", - "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.5.tgz", + "integrity": "sha512-81tijpDg2a6I1Yhj4aWY1l3O1J4Cg/Pd7LfvuaH2VVInAkXtzibz9+zSPdUM1WvuUi128ksstAP0hM5w48vQgg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", - "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.5.tgz", + "integrity": "sha512-12rba2HwemQPa7BLIKCzm1pT2/RuQHtSFHdNl41cFiC6oi4tcrp7gjB07pxQvFpcADojQywSjblQth6gJyE6CA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-for-of": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz", - "integrity": "sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.5.tgz", + "integrity": "sha512-+DpCAJFPAvViR17PIMi9x2AE34dll5wNlXO43wagAX2YcRGgEVHCNFC4azG85b4YyyFarvkc/iD5NPrz4Oneqw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", - "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.5.tgz", + "integrity": "sha512-Fuec/KPSpVLbGo6z1RPw4EE1X+z9gZk1uQmnYy7v4xr4TO9p41v1AoUuXEtyqAI7H+xNJYSICzRqZBhDEkd3kQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", - "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.5.tgz", + "integrity": "sha512-B1j9C/IfvshnPcklsc93AVLTrNVa69iSqztylZH6qnmiAsDDOmmjEYqOm3Ts2lGSgTSywnBNiqC949VdD0/gfw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", - "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.5.tgz", + "integrity": "sha512-d57i3vPHWgIde/9Y8W/xSFUndhvhZN5Wu2TjRrN1MVz5KzdUihKnfDVlfP1U7mS5DNj/WHHhaE4/tTi4hIyHwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", - "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.5.tgz", + "integrity": "sha512-oHI15S/hdJuSCfnwIz+4lm6wu/wBn7oJ8+QrkzPPwSFGXk8kgdI/AIKcbR/XnD1nQVMg/i6eNaXpszbGuwYDRQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.0.tgz", - "integrity": "sha512-3H/R9s8cXcOGE8kgMlmjYYC9nqr5ELiPkJn4q0mypBrjhYQoc+5/Maq69vV4xRPWnkzZuwJPf5rArxpB/35Cig==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz", + "integrity": "sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.15.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.14.8", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-simple-access": "^7.16.0", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.14.5.tgz", - "integrity": "sha512-mNMQdvBEE5DcMQaL5LbzXFMANrQjd2W7FPzg34Y4yEz7dBgdaC+9B84dSO+/1Wba98zoDbInctCDo4JGxz1VYA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.5.tgz", + "integrity": "sha512-53gmLdScNN28XpjEVIm7LbWnD/b/TpbwKbLk6KV4KqC9WyU6rq1jnNmVG6UgAdQZVVGZVoik3DqHNxk4/EvrjA==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-validator-identifier": "^7.15.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", - "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.5.tgz", + "integrity": "sha512-qTFnpxHMoenNHkS3VoWRdwrcJ3FhX567GvDA3hRZKF0Dj8Fmg0UzySZp3AP2mShl/bzcywb/UWAMQIjA1bhXvw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", - "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.5.tgz", + "integrity": "sha512-/wqGDgvFUeKELW6ex6QB7dLVRkd5ehjw34tpXu1nhKC0sFfmaLabIswnpf8JgDyV2NeDmZiwoOb0rAmxciNfjA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.0" } }, "@babel/plugin-transform-new-target": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", - "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.5.tgz", + "integrity": "sha512-ZaIrnXF08ZC8jnKR4/5g7YakGVL6go6V9ql6Jl3ecO8PQaQqFE74CuM384kezju7Z9nGCCA20BqZaR1tJ/WvHg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-object-super": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", - "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz", + "integrity": "sha512-tded+yZEXuxt9Jdtkc1RraW1zMF/GalVxaVVxh41IYwirdRgyAxxxCKZ9XB7LxZqmsjfjALxupNE1MIz9KH+Zg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-replace-supers": "^7.16.5" } }, "@babel/plugin-transform-parameters": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz", - "integrity": "sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.5.tgz", + "integrity": "sha512-B3O6AL5oPop1jAVg8CV+haeUte9oFuY85zu0jwnRNZZi3tVAbJriu5tag/oaO2kGaQM/7q7aGPBlTI5/sr9enA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-property-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", - "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.5.tgz", + "integrity": "sha512-+IRcVW71VdF9pEH/2R/Apab4a19LVvdVsr/gEeotH00vSDVlKD+XgfSIw+cgGWsjDB/ziqGv/pGoQZBIiQVXHg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-regenerator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", - "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.5.tgz", + "integrity": "sha512-2z+it2eVWU8TtQQRauvGUqZwLy4+7rTfo6wO4npr+fvvN1SW30ZF3O/ZRCNmTuu4F5MIP8OJhXAhRV5QMJOuYg==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", - "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.5.tgz", + "integrity": "sha512-aIB16u8lNcf7drkhXJRoggOxSTUAuihTSTfAcpynowGJOZiGf+Yvi7RuTwFzVYSYPmWyARsPqUGoZWWWxLiknw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-runtime": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.0.tgz", - "integrity": "sha512-sfHYkLGjhzWTq6xsuQ01oEsUYjkHRux9fW1iUA68dC7Qd8BS1Unq4aZ8itmQp95zUzIcyR2EbNMTzAicFj+guw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.5.tgz", + "integrity": "sha512-gxpfS8XQWDbQ8oP5NcmpXxtEgCJkbO+W9VhZlOhr0xPyVaRjAQPOv7ZDj9fg0d5s9+NiVvMCE6gbkEkcsxwGRw==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "babel-plugin-polyfill-corejs2": "^0.2.2", - "babel-plugin-polyfill-corejs3": "^0.2.2", - "babel-plugin-polyfill-regenerator": "^0.2.2", + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", "semver": "^6.3.0" }, "dependencies": { @@ -1020,96 +1032,97 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", - "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.5.tgz", + "integrity": "sha512-ZbuWVcY+MAXJuuW7qDoCwoxDUNClfZxoo7/4swVbOW1s/qYLOMHlm9YRWMsxMFuLs44eXsv4op1vAaBaBaDMVg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-spread": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", - "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.5.tgz", + "integrity": "sha512-5d6l/cnG7Lw4tGHEoga4xSkYp1euP7LAtrah1h1PgJ3JY7yNsjybsxQAnVK4JbtReZ/8z6ASVmd3QhYYKLaKZw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", - "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.5.tgz", + "integrity": "sha512-usYsuO1ID2LXxzuUxifgWtJemP7wL2uZtyrTVM4PKqsmJycdS4U4mGovL5xXkfUheds10Dd2PjoQLXw6zCsCbg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-template-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", - "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.5.tgz", + "integrity": "sha512-gnyKy9RyFhkovex4BjKWL3BVYzUDG6zC0gba7VMLbQoDuqMfJ1SDXs8k/XK41Mmt1Hyp4qNAvGFb9hKzdCqBRQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", - "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.5.tgz", + "integrity": "sha512-ldxCkW180qbrvyCVDzAUZqB0TAeF8W/vGJoRcaf75awm6By+PxfJKvuqVAnq8N9wz5Xa6mSpM19OfVKKVmGHSQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", - "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz", + "integrity": "sha512-shiCBHTIIChGLdyojsKQjoAyB8MBwat25lKM7MJjbe1hE0bgIppD+LX9afr41lLHOhqceqeWl4FkLp+Bgn9o1Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", - "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.5.tgz", + "integrity": "sha512-GTJ4IW012tiPEMMubd7sD07iU9O/LOo8Q/oU4xNhcaq0Xn8+6TcUQaHtC8YxySo1T+ErQ8RaWogIEeFhKGNPzw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/preset-env": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.15.0.tgz", - "integrity": "sha512-FhEpCNFCcWW3iZLg0L2NPE9UerdtsCR6ZcsGHUX6Om6kbCQeL5QZDqFDmeNHC6/fy6UH3jEge7K4qG5uC9In0Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.5.tgz", + "integrity": "sha512-MiJJW5pwsktG61NDxpZ4oJ1CKxM1ncam9bzRtx9g40/WkLRkxFP6mhpkYV0/DxcciqoiHicx291+eUQrXb/SfQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.0", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-async-generator-functions": "^7.14.9", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-class-static-block": "^7.14.5", - "@babel/plugin-proposal-dynamic-import": "^7.14.5", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-proposal-json-strings": "^7.14.5", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", - "@babel/plugin-proposal-numeric-separator": "^7.14.5", - "@babel/plugin-proposal-object-rest-spread": "^7.14.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-proposal-private-property-in-object": "^7.14.5", - "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.2", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-async-generator-functions": "^7.16.5", + "@babel/plugin-proposal-class-properties": "^7.16.5", + "@babel/plugin-proposal-class-static-block": "^7.16.5", + "@babel/plugin-proposal-dynamic-import": "^7.16.5", + "@babel/plugin-proposal-export-namespace-from": "^7.16.5", + "@babel/plugin-proposal-json-strings": "^7.16.5", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.5", + "@babel/plugin-proposal-numeric-separator": "^7.16.5", + "@babel/plugin-proposal-object-rest-spread": "^7.16.5", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.5", + "@babel/plugin-proposal-optional-chaining": "^7.16.5", + "@babel/plugin-proposal-private-methods": "^7.16.5", + "@babel/plugin-proposal-private-property-in-object": "^7.16.5", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.5", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", @@ -1124,44 +1137,44 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.14.5", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/plugin-transform-block-scoped-functions": "^7.14.5", - "@babel/plugin-transform-block-scoping": "^7.14.5", - "@babel/plugin-transform-classes": "^7.14.9", - "@babel/plugin-transform-computed-properties": "^7.14.5", - "@babel/plugin-transform-destructuring": "^7.14.7", - "@babel/plugin-transform-dotall-regex": "^7.14.5", - "@babel/plugin-transform-duplicate-keys": "^7.14.5", - "@babel/plugin-transform-exponentiation-operator": "^7.14.5", - "@babel/plugin-transform-for-of": "^7.14.5", - "@babel/plugin-transform-function-name": "^7.14.5", - "@babel/plugin-transform-literals": "^7.14.5", - "@babel/plugin-transform-member-expression-literals": "^7.14.5", - "@babel/plugin-transform-modules-amd": "^7.14.5", - "@babel/plugin-transform-modules-commonjs": "^7.15.0", - "@babel/plugin-transform-modules-systemjs": "^7.14.5", - "@babel/plugin-transform-modules-umd": "^7.14.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.9", - "@babel/plugin-transform-new-target": "^7.14.5", - "@babel/plugin-transform-object-super": "^7.14.5", - "@babel/plugin-transform-parameters": "^7.14.5", - "@babel/plugin-transform-property-literals": "^7.14.5", - "@babel/plugin-transform-regenerator": "^7.14.5", - "@babel/plugin-transform-reserved-words": "^7.14.5", - "@babel/plugin-transform-shorthand-properties": "^7.14.5", - "@babel/plugin-transform-spread": "^7.14.6", - "@babel/plugin-transform-sticky-regex": "^7.14.5", - "@babel/plugin-transform-template-literals": "^7.14.5", - "@babel/plugin-transform-typeof-symbol": "^7.14.5", - "@babel/plugin-transform-unicode-escapes": "^7.14.5", - "@babel/plugin-transform-unicode-regex": "^7.14.5", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.15.0", - "babel-plugin-polyfill-corejs2": "^0.2.2", - "babel-plugin-polyfill-corejs3": "^0.2.2", - "babel-plugin-polyfill-regenerator": "^0.2.2", - "core-js-compat": "^3.16.0", + "@babel/plugin-transform-arrow-functions": "^7.16.5", + "@babel/plugin-transform-async-to-generator": "^7.16.5", + "@babel/plugin-transform-block-scoped-functions": "^7.16.5", + "@babel/plugin-transform-block-scoping": "^7.16.5", + "@babel/plugin-transform-classes": "^7.16.5", + "@babel/plugin-transform-computed-properties": "^7.16.5", + "@babel/plugin-transform-destructuring": "^7.16.5", + "@babel/plugin-transform-dotall-regex": "^7.16.5", + "@babel/plugin-transform-duplicate-keys": "^7.16.5", + "@babel/plugin-transform-exponentiation-operator": "^7.16.5", + "@babel/plugin-transform-for-of": "^7.16.5", + "@babel/plugin-transform-function-name": "^7.16.5", + "@babel/plugin-transform-literals": "^7.16.5", + "@babel/plugin-transform-member-expression-literals": "^7.16.5", + "@babel/plugin-transform-modules-amd": "^7.16.5", + "@babel/plugin-transform-modules-commonjs": "^7.16.5", + "@babel/plugin-transform-modules-systemjs": "^7.16.5", + "@babel/plugin-transform-modules-umd": "^7.16.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.5", + "@babel/plugin-transform-new-target": "^7.16.5", + "@babel/plugin-transform-object-super": "^7.16.5", + "@babel/plugin-transform-parameters": "^7.16.5", + "@babel/plugin-transform-property-literals": "^7.16.5", + "@babel/plugin-transform-regenerator": "^7.16.5", + "@babel/plugin-transform-reserved-words": "^7.16.5", + "@babel/plugin-transform-shorthand-properties": "^7.16.5", + "@babel/plugin-transform-spread": "^7.16.5", + "@babel/plugin-transform-sticky-regex": "^7.16.5", + "@babel/plugin-transform-template-literals": "^7.16.5", + "@babel/plugin-transform-typeof-symbol": "^7.16.5", + "@babel/plugin-transform-unicode-escapes": "^7.16.5", + "@babel/plugin-transform-unicode-regex": "^7.16.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.0", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.19.1", "semver": "^6.3.0" }, "dependencies": { @@ -1174,9 +1187,9 @@ } }, "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1187,62 +1200,43 @@ } }, "@babel/runtime": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", - "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz", + "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", + "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - } + "@babel/code-frame": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz", + "integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.5", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/parser": "^7.16.5", + "@babel/types": "^7.16.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -1252,47 +1246,55 @@ } }, "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.15.7", "to-fast-properties": "^2.0.0" } }, "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", + "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", "dev": true, "requires": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", + "debug": "^4.3.2", + "espree": "^9.2.0", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } } }, "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" } }, "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "@istanbuljs/schema": { @@ -1347,9 +1349,9 @@ } }, "@rollup/plugin-node-resolve": { - "version": "13.0.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.4.tgz", - "integrity": "sha512-eYq4TFy40O8hjeDs+sIxEH/jc9lyuI2k9DM557WN6rO5OpnC2qXMBNj4IKH1oHrnAazL49C5p0tgP0/VpqJ+/w==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.1.tgz", + "integrity": "sha512-6QKtRevXLrmEig9UiMYt2fSvee9TyltGRfw+qSs6xjUnxwjOzTOqy+/Lpxsgjb8mJn1EQNbCDAvt89O4uzL5kw==", "dev": true, "requires": { "@rollup/pluginutils": "^3.1.0", @@ -1382,14 +1384,25 @@ "is-regexp": "^2.0.0" }, "dependencies": { - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", "dev": true } } }, + "@simonbrunel/vuepress-plugin-versions": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@simonbrunel/vuepress-plugin-versions/-/vuepress-plugin-versions-0.2.0.tgz", + "integrity": "sha512-6qgrbxCVG5mIHQDqTvWfpSxGMpqcDAHKIlxScZ0MfJjUWW40Kt4xcZ3OTx4NvlsNZUDNLZVWngIPYsMah4C/mQ==", + "dev": true, + "requires": { + "node-fetch": "^2.6.1", + "semiver": "^1.1.0", + "stringify-object": "^3.3.0" + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -1405,12 +1418,41 @@ "defer-to-connect": "^1.0.1" } }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/component-emitter": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", - "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", "dev": true }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -1429,31 +1471,99 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.27", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.27.tgz", + "integrity": "sha512-e/sVallzUTPdyOTiqi8O8pMdBBphscvI6E4JYaKlja4Lm+zh7UFSSdW5VMkRbhDtmrONqOUHOXRguPsDckzxNA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "requires": { "@types/minimatch": "*", "@types/node": "*" } }, + "@types/highlight.js": { + "version": "9.12.4", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.4.tgz", + "integrity": "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww==", + "dev": true + }, + "@types/http-proxy": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", + "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "@types/markdown-it": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-10.0.3.tgz", + "integrity": "sha512-daHJk22isOUvNssVGF2zDnnSyxHhFYhtjeX4oQaKD6QzL3ZR1QSgiD1g+Q6/WSWYVogNXYDXODtbgW/WiFCtyw==", + "dev": true, + "requires": { + "@types/highlight.js": "^9.7.0", + "@types/linkify-it": "*", + "@types/mdurl": "*", + "highlight.js": "^9.7.0" + } + }, "@types/mdast": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.9.tgz", - "integrity": "sha512-IUlIhG2KNPjOEuXIblTjovD1XW8HPGeulA12nEyc6xhO4Yrrcs+xczAl4ucR3cpwVlE+vb2x9Z7pRmVP4bUHng==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", "dev": true, "requires": { "@types/unist": "*" } }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -1461,15 +1571,9 @@ "dev": true }, "@types/node": { - "version": "16.7.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz", - "integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", + "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==", "dev": true }, "@types/q": { @@ -1478,6 +1582,18 @@ "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==", "dev": true }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -1487,94 +1603,178 @@ "@types/node": "*" } }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/tapable": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", + "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", + "integrity": "sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, + "@types/webpack": { + "version": "4.41.32", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", + "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, + "@types/webpack-dev-server": { + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz", + "integrity": "sha512-XCph0RiiqFGetukCTC3KVnY1jwLcZ84illFRMbyFzCcWl90B/76ew0tSqF46oBhnLC4obNDG7dMO0JfTN0MgMQ==", + "dev": true, + "requires": { + "@types/connect-history-api-fallback": "*", + "@types/express": "*", + "@types/serve-static": "*", + "@types/webpack": "^4", + "http-proxy-middleware": "^1.0.0" + } + }, + "@types/webpack-sources": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", + "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, "@typescript-eslint/eslint-plugin": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.2.tgz", - "integrity": "sha512-x4EMgn4BTfVd9+Z+r+6rmWxoAzBaapt4QFqE+d8L8sUtYZYLDTK6VG/y/SMMWA5t1/BVU5Kf+20rX4PtWzUYZg==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz", + "integrity": "sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.29.2", - "@typescript-eslint/scope-manager": "4.29.2", - "debug": "^4.3.1", + "@typescript-eslint/experimental-utils": "5.8.1", + "@typescript-eslint/scope-manager": "5.8.1", + "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.1.0", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", "semver": "^7.3.5", "tsutils": "^3.21.0" } }, "@typescript-eslint/experimental-utils": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.2.tgz", - "integrity": "sha512-P6mn4pqObhftBBPAv4GQtEK7Yos1fz/MlpT7+YjH9fTxZcALbiiPKuSIfYP/j13CeOjfq8/fr9Thr2glM9ub7A==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz", + "integrity": "sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww==", "dev": true, "requires": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.29.2", - "@typescript-eslint/types": "4.29.2", - "@typescript-eslint/typescript-estree": "4.29.2", + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.8.1", + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/typescript-estree": "5.8.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/parser": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.2.tgz", - "integrity": "sha512-WQ6BPf+lNuwteUuyk1jD/aHKqMQ9jrdCn7Gxt9vvBnzbpj7aWEf+aZsJ1zvTjx5zFxGCt000lsbD9tQPEL8u6g==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.8.1.tgz", + "integrity": "sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.29.2", - "@typescript-eslint/types": "4.29.2", - "@typescript-eslint/typescript-estree": "4.29.2", - "debug": "^4.3.1" + "@typescript-eslint/scope-manager": "5.8.1", + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/typescript-estree": "5.8.1", + "debug": "^4.3.2" } }, "@typescript-eslint/scope-manager": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz", - "integrity": "sha512-mfHmvlQxmfkU8D55CkZO2sQOueTxLqGvzV+mG6S/6fIunDiD2ouwsAoiYCZYDDK73QCibYjIZmGhpvKwAB5BOA==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz", + "integrity": "sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.2", - "@typescript-eslint/visitor-keys": "4.29.2" + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1" } }, "@typescript-eslint/types": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.2.tgz", - "integrity": "sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.8.1.tgz", + "integrity": "sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz", - "integrity": "sha512-TJ0/hEnYxapYn9SGn3dCnETO0r+MjaxtlWZ2xU+EvytF0g4CqTpZL48SqSNn2hXsPolnewF30pdzR9a5Lj3DNg==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz", + "integrity": "sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.2", - "@typescript-eslint/visitor-keys": "4.29.2", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", "semver": "^7.3.5", "tsutils": "^3.21.0" } }, "@typescript-eslint/visitor-keys": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz", - "integrity": "sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz", + "integrity": "sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.2", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.8.1", + "eslint-visitor-keys": "^3.0.0" } }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "@vue/babel-helper-vue-jsx-merge-props": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz", @@ -1588,9 +1788,9 @@ "dev": true }, "@vue/babel-plugin-jsx": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.6.tgz", - "integrity": "sha512-RzYsvBhzKUmY2YG6LoV+W5PnlnkInq0thh1AzCmewwctAgGN6e9UFon6ZrQQV1CO5G5PeME7MqpB+/vvGg0h4g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz", + "integrity": "sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -1627,9 +1827,9 @@ } }, "@vue/babel-preset-app": { - "version": "4.5.13", - "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.13.tgz", - "integrity": "sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw==", + "version": "4.5.15", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.15.tgz", + "integrity": "sha512-J+YttzvwRfV1BPczf8r3qCevznYk+jh531agVF+5EYlHF4Sgh/cGXTz9qkkiux3LQgvhEGXgmCteg1n38WuuKg==", "dev": true, "requires": { "@babel/core": "^7.11.0", @@ -1758,9 +1958,9 @@ } }, "@vue/component-compiler-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.2.tgz", - "integrity": "sha512-rAYMLmgMuqJFWAOb3Awjqqv5X3Q3hVr4jH/kgrFJpiU0j3a90tnNBplqbj+snzrgZhC9W128z+dtgMifOiMfJg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", + "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", "dev": true, "requires": { "consolidate": "^0.15.1", @@ -1769,7 +1969,7 @@ "merge-source-map": "^1.1.0", "postcss": "^7.0.36", "postcss-selector-parser": "^6.0.2", - "prettier": "^1.18.2", + "prettier": "^1.18.2 || ^2.0.0", "source-map": "~0.6.1", "vue-template-es2015-compiler": "^1.9.0" }, @@ -1793,20 +1993,22 @@ } }, "@vuepress/core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.8.2.tgz", - "integrity": "sha512-lh9BLC06k9s0wxTuWtCkiNj49fkbW87enp0XSrFZHEoyDGSGndQjZmMMErcHc5Hx7nrW1nzc33sPH1NNtJl0hw==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.9.5.tgz", + "integrity": "sha512-Fv9obu+eLkflrPFpdL4qv42Rso0AzDDLk/0dGANF9yqi7t3XgIvWV8oiiUa1cg/m/Sgel5RlSjJxDPWrnXjDCQ==", "dev": true, "requires": { "@babel/core": "^7.8.4", "@vue/babel-preset-app": "^4.1.2", - "@vuepress/markdown": "1.8.2", - "@vuepress/markdown-loader": "1.8.2", - "@vuepress/plugin-last-updated": "1.8.2", - "@vuepress/plugin-register-components": "1.8.2", - "@vuepress/shared-utils": "1.8.2", + "@vuepress/markdown": "1.9.5", + "@vuepress/markdown-loader": "1.9.5", + "@vuepress/plugin-last-updated": "1.9.5", + "@vuepress/plugin-register-components": "1.9.5", + "@vuepress/shared-utils": "1.9.5", + "@vuepress/types": "1.9.5", "autoprefixer": "^9.5.1", "babel-loader": "^8.0.4", + "bundle-require": "2.1.8", "cache-loader": "^3.0.0", "chokidar": "^2.0.3", "connect-history-api-fallback": "^1.5.0", @@ -1814,6 +2016,7 @@ "core-js": "^3.6.4", "cross-spawn": "^6.0.5", "css-loader": "^2.1.1", + "esbuild": "0.14.7", "file-loader": "^3.0.1", "js-yaml": "^3.13.1", "lru-cache": "^5.1.1", @@ -1859,6 +2062,15 @@ } } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -2037,6 +2249,16 @@ } } }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2145,12 +2367,12 @@ } }, "@vuepress/markdown": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.8.2.tgz", - "integrity": "sha512-zznBHVqW+iBkznF/BO/GY9RFu53khyl0Ey0PnGqvwCJpRLNan6y5EXgYumtjw2GSYn5nDTTALYxtyNBdz64PKg==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.9.5.tgz", + "integrity": "sha512-6W2hen+9qhkSuj/j403QaWxMcnqQSnZqjHr1qt581zNz1xLwgkSva7IzfGWxEVdCkwtM8+JWZrDEkzt4UyItXA==", "dev": true, "requires": { - "@vuepress/shared-utils": "1.8.2", + "@vuepress/shared-utils": "1.9.5", "markdown-it": "^8.4.1", "markdown-it-anchor": "^5.0.2", "markdown-it-chain": "^1.3.0", @@ -2160,12 +2382,12 @@ } }, "@vuepress/markdown-loader": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.8.2.tgz", - "integrity": "sha512-mWzFXikCUcAN/chpKkqZpRYKdo0312hMv8cBea2hvrJYV6y4ODB066XKvXN8JwOcxuCjxWYJkhWGr+pXq1oTtw==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.9.5.tgz", + "integrity": "sha512-Y4pxXQpOf2gvxGMPKEotHJKqVAIi+HfqKQAJPd/0zhsFn6F54qOsuhohXOvtx3sAUnbYmuM5+zS/Jpvh6UMebA==", "dev": true, "requires": { - "@vuepress/markdown": "1.8.2", + "@vuepress/markdown": "1.9.5", "loader-utils": "^1.1.0", "lru-cache": "^5.1.1" }, @@ -2188,20 +2410,22 @@ } }, "@vuepress/plugin-active-header-links": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.8.2.tgz", - "integrity": "sha512-JmXAQg8D7J8mcKe2Ue3BZ9dOCzJMJXP4Cnkkc/IrqfDg0ET0l96gYWZohCqlvRIWt4f0VPiFAO4FLYrW+hko+g==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.9.5.tgz", + "integrity": "sha512-Pi6cu5Ed4m6Ri8QatigapdXqhEcZXd/s+lyhdKWIwjJQaMSYlyAN2pX8Pqm4vqAvW6c0Dw2wEXrd989BcI7T4g==", "dev": true, "requires": { + "@vuepress/types": "1.9.5", "lodash.debounce": "^4.0.8" } }, "@vuepress/plugin-last-updated": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.8.2.tgz", - "integrity": "sha512-pYIRZi52huO9b6HY3JQNPKNERCLzMHejjBRt9ekdnJ1xhLs4MmRvt37BoXjI/qzvXkYtr7nmGgnKThNBVRTZuA==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.9.5.tgz", + "integrity": "sha512-coy5zMgAHVno+c8yGMwxBhkTNMBYoRJdogzuVQRgITJRDZfToiKSza3RDQkFfn1YluCps2v/9NQSHwPnhEv6eA==", "dev": true, "requires": { + "@vuepress/types": "1.9.5", "cross-spawn": "^6.0.5" }, "dependencies": { @@ -2257,33 +2481,38 @@ } }, "@vuepress/plugin-nprogress": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-1.8.2.tgz", - "integrity": "sha512-3TOBee2NM3WLr1tdjDTGfrAMggjN+OlEPyKyv8FqThsVkDYhw48O3HwqlThp9KX7UbL3ExxIFBwWRFLC+kYrdw==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-1.9.5.tgz", + "integrity": "sha512-XEtyCt/R/qNcQM62/F6srqOZ6V6mmxSqfQktPMYfOdQqeRW0aCXvG6N2/cy55S4xH8LeEVL4Nxg5m9Cew9AEgA==", "dev": true, "requires": { + "@vuepress/types": "1.9.5", "nprogress": "^0.2.0" } }, "@vuepress/plugin-register-components": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.8.2.tgz", - "integrity": "sha512-6SUq3nHFMEh9qKFnjA8QnrNxj0kLs7+Gspq1OBU8vtu0NQmSvLFZVaMV7pzT/9zN2nO5Pld5qhsUJv1g71MrEA==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.9.5.tgz", + "integrity": "sha512-2eFqboPCOSY8bh7lkd13Cs7vyUcETEzzYnK/7CjqaeDmYtiJuWCoPjF30K2RsdbpVCE5aqheyXmBSEp2uWEosA==", "dev": true, "requires": { - "@vuepress/shared-utils": "1.8.2" + "@vuepress/shared-utils": "1.9.5", + "@vuepress/types": "1.9.5" } }, "@vuepress/plugin-search": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-1.8.2.tgz", - "integrity": "sha512-JrSJr9o0Kar14lVtZ4wfw39pplxvvMh8vDBD9oW09a+6Zi/4bySPGdcdaqdqGW+OHSiZNvG+6uyfKSBBBqF6PA==", - "dev": true + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-1.9.5.tgz", + "integrity": "sha512-SDK/1UnJ44OAktgZQWiw6S8Yq8F6WkJsGp4k/e3x1gHg5fB96JJlKK4VBsGk/PUsPpYRwr5554dtEVFjFG7oWw==", + "dev": true, + "requires": { + "@vuepress/types": "1.9.5" + } }, "@vuepress/shared-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.8.2.tgz", - "integrity": "sha512-6kGubc7iBDWruEBUU7yR+sQ++SOhMuvKWvWeTZJKRZedthycdzYz7QVpua0FaZSAJm5/dIt8ymU4WQvxTtZgTQ==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.9.5.tgz", + "integrity": "sha512-WA6lq5YwbG3HX3yVhMsrsw6zqDUiiMsUsBJCk/gNwbWng/NTibR/f+DkUyD/tw8fYuNu5EDK8wPn+qyLhcrjJg==", "dev": true, "requires": { "chalk": "^2.3.2", @@ -2470,6 +2699,12 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", @@ -2528,6 +2763,15 @@ } } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -2614,18 +2858,25 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1" } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true } } }, "@vuepress/theme-default": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.8.2.tgz", - "integrity": "sha512-rE7M1rs3n2xp4a/GrweO8EGwqFn3EA5gnFWdVmVIHyr7C1nix+EqjpPQF1SVWNnIrDdQuCw38PqS+oND1K2vYw==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.9.5.tgz", + "integrity": "sha512-mB919hzorh3PwEY6hzkBYEa0Xweg3kEUPhZbNx29gMXn6KBS7NqN3+sId/Frv2RydSWw5h1ax8NtphaiR0UvBQ==", "dev": true, "requires": { - "@vuepress/plugin-active-header-links": "1.8.2", - "@vuepress/plugin-nprogress": "1.8.2", - "@vuepress/plugin-search": "1.8.2", + "@vuepress/plugin-active-header-links": "1.9.5", + "@vuepress/plugin-nprogress": "1.9.5", + "@vuepress/plugin-search": "1.9.5", + "@vuepress/types": "1.9.5", "docsearch.js": "^2.5.2", "lodash": "^4.17.15", "stylus": "^0.54.8", @@ -2634,6 +2885,17 @@ "vuepress-plugin-smooth-scroll": "^0.0.3" } }, + "@vuepress/types": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@vuepress/types/-/types-1.9.5.tgz", + "integrity": "sha512-fu1Wyi+cPFyn7nfxNH4MVvU5glYvRB0RMI7besq9kiI/KkZJPb2G1hITopECJOGk+6G/cOU/iSdUDUHH4GQLgA==", + "dev": true, + "requires": { + "@types/markdown-it": "^10.0.0", + "@types/webpack-dev-server": "^3", + "webpack-chain": "^6.0.0" + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -2838,9 +3100,9 @@ } }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true }, "acorn-jsx": { @@ -2932,52 +3194,12 @@ "dev": true }, "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "dev": true, "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.1.0" } }, "ansi-colors": { @@ -3003,16 +3225,16 @@ } } }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", "dev": true }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -3041,13 +3263,10 @@ "dev": true }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "arr-diff": { "version": "4.0.0", @@ -3092,9 +3311,9 @@ "dev": true }, "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, "requires": { "safer-buffer": "~2.1.0" @@ -3159,12 +3378,6 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -3208,18 +3421,26 @@ } }, "autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", "dev": true, "requires": { "browserslist": "^4.12.0", "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", "postcss": "^7.0.32", "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + } } }, "aws-sign2": { @@ -3235,9 +3456,9 @@ "dev": true }, "babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", + "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", "dev": true, "requires": { "find-cache-dir": "^3.3.1", @@ -3256,13 +3477,13 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", - "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz", + "integrity": "sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==", "dev": true, "requires": { "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.2", + "@babel/helper-define-polyfill-provider": "^0.3.0", "semver": "^6.1.1" }, "dependencies": { @@ -3275,22 +3496,22 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.4.tgz", - "integrity": "sha512-z3HnJE5TY/j4EFEa/qpQMSbcUJZ5JQi+3UFjXzn6pQCmIKc5Ug5j98SuYyH+m4xQnvKlMDIW4plLfgyVnd0IcQ==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz", + "integrity": "sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2", - "core-js-compat": "^3.14.0" + "@babel/helper-define-polyfill-provider": "^0.3.0", + "core-js-compat": "^3.18.0" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", - "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz", + "integrity": "sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2" + "@babel/helper-define-polyfill-provider": "^0.3.0" } }, "balanced-match": { @@ -3355,9 +3576,9 @@ } }, "base64-arraybuffer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz", + "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==", "dev": true }, "base64-js": { @@ -3422,21 +3643,21 @@ "dev": true }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", "dev": true, "requires": { - "bytes": "3.1.0", + "bytes": "3.1.1", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" }, "dependencies": { "debug": { @@ -3550,6 +3771,12 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -3643,16 +3870,16 @@ } }, "browserslist": { - "version": "4.16.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.8.tgz", - "integrity": "sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001251", - "colorette": "^1.3.0", - "electron-to-chromium": "^1.3.811", + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", "escalade": "^3.1.1", - "node-releases": "^1.1.75" + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" } }, "buffer": { @@ -3710,16 +3937,22 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bundle-require": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-2.1.8.tgz", + "integrity": "sha512-oOEg3A0hy/YzvNWNowtKD0pmhZKseOFweCbgyMqTIih4gRY1nJWsvrOCT27L9NbIyL5jMjTFrAUpGxxpW68Puw==", + "dev": true + }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", "dev": true }, "cac": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.3.tgz", - "integrity": "sha512-ECVqVZh74qgSuZG9YOt2OJPI3wGcf+EwwuF/XIOYqZBD0KZYLtgPWqFPxmDPQ6joxI1nOlvVgRV6VT53Ooyocg==", + "version": "6.7.12", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.12.tgz", + "integrity": "sha512-rM7E2ygtMkJqD9c7WnFU6fruFcN3xe4FM5yUmgxhZzIKJk4uHl9U/fhwdajGFQbQuv43FAUo1Fe8gX/oIKDeSA==", "dev": true }, "cacache": { @@ -3842,6 +4075,15 @@ "semver": "^5.6.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -3988,9 +4230,9 @@ } }, "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "dev": true }, "caniuse-api": { @@ -4006,9 +4248,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001251", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz", - "integrity": "sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==", + "version": "1.0.30001294", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001294.tgz", + "integrity": "sha512-LiMlrs1nSKZ8qkNhpUf5KD0Al1KCBE3zaT7OLOwEkagXMEDij98SiOovn9wxVGQpklk9vVC/pUSqgYmkmKOS8g==", "dev": true }, "caseless": { @@ -4057,21 +4299,21 @@ "dev": true }, "chart.js": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz", - "integrity": "sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.0.tgz", + "integrity": "sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg==", "dev": true }, "chartjs-plugin-autocolors": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/chartjs-plugin-autocolors/-/chartjs-plugin-autocolors-0.0.3.tgz", - "integrity": "sha512-2lo2u0BHuKAqjrrVIHBsWVy3cWmfgeaGCOZ2V037qtxKd3mcTIpmBeE8wiJfkACvDuaOwSjkT5/xn5s6/qDA6A==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/chartjs-plugin-autocolors/-/chartjs-plugin-autocolors-0.0.5.tgz", + "integrity": "sha512-yxDDqykc4wv2g+c0lEW6JN2G5hfcQF2vp6auLiKq9znJZMjGT5Bhl2SmPsqhgCjy/C240OkjrwHIK72nZXrqAA==", "dev": true }, "chartjs-test-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.3.0.tgz", - "integrity": "sha512-YN3K8hZgiCZaVDvfysQ91ERd3q1knvydN0NmKS8N5S9MUyW1lvVaelrDL1f8Zh3jq2Qo+IjpUyO4VeOR5XvmsQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.3.1.tgz", + "integrity": "sha512-QsRYLWOedYGsloDvJsByPNUK44TOiqnxQEO5FOrOm9SguEl5WmJDCOIdd/1ePLOX4gGRClXBDVxD7o1SJY+nWA==", "dev": true, "requires": { "jasmine": "^3.6.4", @@ -4094,6 +4336,17 @@ "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } } }, "chownr": { @@ -4137,9 +4390,9 @@ } }, "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", "dev": true, "requires": { "source-map": "~0.6.0" @@ -4306,21 +4559,15 @@ "dev": true }, "color-string": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", - "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", + "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", "dev": true, "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, - "colorette": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", - "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", - "dev": true - }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -4420,15 +4667,14 @@ } }, "concurrently": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.2.1.tgz", - "integrity": "sha512-emgwhH+ezkuYKSHZQ+AkgEpoUZZlbpPVYCVv7YZx0r+T7fny1H03r2nYRebpi2DudHR4n1Rgbo2YTxKOxVJ4+g==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.5.1.tgz", + "integrity": "sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag==", "dev": true, "requires": { "chalk": "^4.1.0", "date-fns": "^2.16.1", "lodash": "^4.17.21", - "read-pkg": "^5.2.0", "rxjs": "^6.6.3", "spawn-command": "^0.0.2-1", "supports-color": "^8.1.0", @@ -4513,12 +4759,20 @@ "dev": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "content-type": { @@ -4706,6 +4960,15 @@ } } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -4762,6 +5025,15 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -4771,18 +5043,18 @@ } }, "core-js": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.2.tgz", - "integrity": "sha512-P0KPukO6OjMpjBtHSceAZEWlDD1M2Cpzpg6dBbrjFqFhBHe/BwhxaP820xKOjRn/lZRQirrCusIpLS/n2sgXLQ==", + "version": "3.20.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.1.tgz", + "integrity": "sha512-btdpStYFQScnNVQ5slVcr858KP0YWYjV16eGJQw8Gg7CWtu/2qNvIM3qVRIR3n1pK2R9NNOrTevbvAYxajwEjg==", "dev": true }, "core-js-compat": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.16.2.tgz", - "integrity": "sha512-4lUshXtBXsdmp8cDWh6KKiHUg40AjiuPD3bOWkNVsr1xkAhpUqCjaZ8lB1bKx9Gb5fXcbRbFJ4f4qpRIRTuJqQ==", + "version": "3.20.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.1.tgz", + "integrity": "sha512-AVhKZNpqMV3Jz8hU0YEXXE06qoxtQGsAqU0u1neUngz5IusDJRX/ZJ6t3i7mS7QxNyEONbCo14GprkBrxPlTZA==", "dev": true, "requires": { - "browserslist": "^4.16.7", + "browserslist": "^4.19.1", "semver": "7.0.0" }, "dependencies": { @@ -4795,9 +5067,9 @@ } }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, "cors": { @@ -4822,6 +5094,15 @@ "parse-json": "^4.0.0" }, "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -4832,14 +5113,14 @@ "resolve-from": "^3.0.0" } }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "resolve-from": { @@ -5060,6 +5341,12 @@ "dev": true } } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true } } }, @@ -5217,9 +5504,9 @@ } }, "date-fns": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.23.0.tgz", - "integrity": "sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==", "dev": true }, "date-format": { @@ -5241,18 +5528,18 @@ "dev": true }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" } }, "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, "decode-uri-component": { @@ -5291,9 +5578,9 @@ "dev": true }, "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "deepmerge": { @@ -5438,6 +5725,12 @@ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -5545,6 +5838,14 @@ "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" + }, + "dependencies": { + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + } } }, "dom-walk": { @@ -5566,18 +5867,18 @@ "dev": true }, "domhandler": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", - "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", "dev": true, "requires": { "domelementtype": "^2.2.0" } }, "domutils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", - "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "dev": true, "requires": { "dom-serializer": "^1.0.1", @@ -5592,6 +5893,14 @@ "dev": true, "requires": { "is-obj": "^2.0.0" + }, + "dependencies": { + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + } } }, "duplexer3": { @@ -5629,9 +5938,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.814", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.814.tgz", - "integrity": "sha512-0mH03cyjh6OzMlmjauGg0TLd87ErIJqWiYxMcOLKf5w6p0YEOl7DJAj7BDlXEFmCguY5CQaKVOiMjAMODO2XDw==", + "version": "1.4.29", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.29.tgz", + "integrity": "sha512-N2Jbwxo5Rum8G2YXeUxycs1sv4Qme/ry71HG73bv8BvZl+I/4JtRgK/En+ST/Wh/yF1fqvVCY4jZBgMxnhjtBA==", "dev": true }, "elliptic": { @@ -5685,27 +5994,30 @@ } }, "engine.io": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", - "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.0.tgz", + "integrity": "sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==", "dev": true, "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~4.0.0", - "ws": "~7.4.2" + "engine.io-parser": "~5.0.0", + "ws": "~8.2.3" } }, "engine.io-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", - "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.2.tgz", + "integrity": "sha512-wuiO7qO/OEkPJSFueuATIXtrxF7/6GTbAO9QLv7nnbjwZ5tYhLm9zxvLwxstRs0dcT0KUlWTjtIOs1T86jt12g==", "dev": true, "requires": { - "base64-arraybuffer": "0.1.4" + "base64-arraybuffer": "~1.0.1" } }, "enhanced-resolve": { @@ -5747,9 +6059,9 @@ "dev": true }, "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", "dev": true }, "envify": { @@ -5787,22 +6099,25 @@ } }, "es-abstract": { - "version": "1.18.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", - "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", "has": "^1.0.3", "has-symbols": "^1.0.2", "internal-slot": "^1.0.3", - "is-callable": "^1.2.3", + "is-callable": "^1.2.4", "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", @@ -5828,6 +6143,150 @@ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "dev": true }, + "esbuild": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.7.tgz", + "integrity": "sha512-+u/msd6iu+HvfysUPkZ9VHm83LImmSNnecYPfFI01pQ7TTcsFR+V0BkybZX7mPtIaI7LCrse6YRj+v3eraJSgw==", + "dev": true, + "requires": { + "esbuild-android-arm64": "0.14.7", + "esbuild-darwin-64": "0.14.7", + "esbuild-darwin-arm64": "0.14.7", + "esbuild-freebsd-64": "0.14.7", + "esbuild-freebsd-arm64": "0.14.7", + "esbuild-linux-32": "0.14.7", + "esbuild-linux-64": "0.14.7", + "esbuild-linux-arm": "0.14.7", + "esbuild-linux-arm64": "0.14.7", + "esbuild-linux-mips64le": "0.14.7", + "esbuild-linux-ppc64le": "0.14.7", + "esbuild-netbsd-64": "0.14.7", + "esbuild-openbsd-64": "0.14.7", + "esbuild-sunos-64": "0.14.7", + "esbuild-windows-32": "0.14.7", + "esbuild-windows-64": "0.14.7", + "esbuild-windows-arm64": "0.14.7" + } + }, + "esbuild-android-arm64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.7.tgz", + "integrity": "sha512-9/Q1NC4JErvsXzJKti0NHt+vzKjZOgPIjX/e6kkuCzgfT/GcO3FVBcGIv4HeJG7oMznE6KyKhvLrFgt7CdU2/w==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.7.tgz", + "integrity": "sha512-Z9X+3TT/Xj+JiZTVlwHj2P+8GoiSmUnGVz0YZTSt8WTbW3UKw5Pw2ucuJ8VzbD2FPy0jbIKJkko/6CMTQchShQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.7.tgz", + "integrity": "sha512-68e7COhmwIiLXBEyxUxZSSU0akgv8t3e50e2QOtKdBUE0F6KIRISzFntLe2rYlNqSsjGWsIO6CCc9tQxijjSkw==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.7.tgz", + "integrity": "sha512-76zy5jAjPiXX/S3UvRgG85Bb0wy0zv/J2lel3KtHi4V7GUTBfhNUPt0E5bpSXJ6yMT7iThhnA5rOn+IJiUcslQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.7.tgz", + "integrity": "sha512-lSlYNLiqyzd7qCN5CEOmLxn7MhnGHPcu5KuUYOG1i+t5A6q7LgBmfYC9ZHJBoYyow3u4CNu79AWHbvVLpE/VQQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.7.tgz", + "integrity": "sha512-Vk28u409wVOXqTaT6ek0TnfQG4Ty1aWWfiysIaIRERkNLhzLhUf4i+qJBN8mMuGTYOkE40F0Wkbp6m+IidOp2A==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.7.tgz", + "integrity": "sha512-+Lvz6x+8OkRk3K2RtZwO+0a92jy9si9cUea5Zoru4yJ/6EQm9ENX5seZE0X9DTwk1dxJbjmLsJsd3IoowyzgVg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.7.tgz", + "integrity": "sha512-OzpXEBogbYdcBqE4uKynuSn5YSetCvK03Qv1HcOY1VN6HmReuatjJ21dCH+YPHSpMEF0afVCnNfffvsGEkxGJQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.7.tgz", + "integrity": "sha512-kJd5beWSqteSAW086qzCEsH6uwpi7QRIpzYWHzEYwKKu9DiG1TwIBegQJmLpPsLp4v5RAFjea0JAmAtpGtRpqg==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.7.tgz", + "integrity": "sha512-mFWpnDhZJmj/h7pxqn1GGDsKwRfqtV7fx6kTF5pr4PfXe8pIaTERpwcKkoCwZUkWAOmUEjMIUAvFM72A6hMZnA==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.7.tgz", + "integrity": "sha512-wM7f4M0bsQXfDL4JbbYD0wsr8cC8KaQ3RPWc/fV27KdErPW7YsqshZZSjDV0kbhzwpNNdhLItfbaRT8OE8OaKA==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.7.tgz", + "integrity": "sha512-J/afS7woKyzGgAL5FlgvMyqgt5wQ597lgsT+xc2yJ9/7BIyezeXutXqfh05vszy2k3kSvhLesugsxIA71WsqBw==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.7.tgz", + "integrity": "sha512-7CcxgdlCD+zAPyveKoznbgr3i0Wnh0L8BDGRCjE/5UGkm5P/NQko51tuIDaYof8zbmXjjl0OIt9lSo4W7I8mrw==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.7.tgz", + "integrity": "sha512-GKCafP2j/KUljVC3nesw1wLFSZktb2FGCmoT1+730zIF5O6hNroo0bSEofm6ZK5mNPnLiSaiLyRB9YFgtkd5Xg==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.7.tgz", + "integrity": "sha512-5I1GeL/gZoUUdTPA0ws54bpYdtyeA2t6MNISalsHpY269zK8Jia/AXB3ta/KcDHv2SvNwabpImeIPXC/k0YW6A==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.7.tgz", + "integrity": "sha512-CIGKCFpQOSlYsLMbxt8JjxxvVw9MlF1Rz2ABLVfFyHUF5OeqHD5fPhGrCVNaVrhO8Xrm+yFmtjcZudUGr5/WYQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.7.tgz", + "integrity": "sha512-eOs1eSivOqN7cFiRIukEruWhaCf75V0N8P0zP7dh44LIhLl8y6/z++vv9qQVbkBm5/D7M7LfCfCTmt1f1wHOCw==", + "dev": true, + "optional": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -5853,37 +6312,36 @@ "dev": true }, "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.5.0.tgz", + "integrity": "sha512-tVGSkgNbOfiHyVte8bCM8OmX+xG9PzVG/B4UCF60zx7j61WIVY/AqJECDgpLD4DbbESD0e174gOg3ZlrX15GDg==", "dev": true, "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", + "@eslint/eslintrc": "^1.0.5", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", + "eslint-scope": "^7.1.0", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.1.0", + "espree": "^9.2.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", + "glob-parent": "^6.0.1", "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -5891,31 +6349,35 @@ "natural-compare": "^1.4.0", "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^3.1.0", + "regexpp": "^3.2.0", "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "eslint-scope": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", + "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true } } }, @@ -5953,18 +6415,18 @@ } }, "eslint-plugin-html": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.1.2.tgz", - "integrity": "sha512-bhBIRyZFqI4EoF12lGDHAmgfff8eLXx6R52/K3ESQhsxzCzIE6hdebS7Py651f7U3RBotqroUnC3L29bR7qJWQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.2.0.tgz", + "integrity": "sha512-vi3NW0E8AJombTvt8beMwkL1R/fdRWl4QSNRNMhVQKWm36/X0KF0unGNAY4mqUF06mnwVWZcIcerrCnfn9025g==", "dev": true, "requires": { - "htmlparser2": "^6.0.1" + "htmlparser2": "^7.1.2" } }, "eslint-plugin-markdown": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-2.2.0.tgz", - "integrity": "sha512-Ctuc7aP1tU92qnFwVO1wDLEzf1jqMxwRkcSTw7gjbvnEqfh5CKUcTXM0sxg8CB2KDXrqpTuMZPgJ1XE9Olr7KA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-2.2.1.tgz", + "integrity": "sha512-FgWp4iyYvTFxPwfbxofTvXxgzPsDuSKHQy2S+a8Ve6savbujey+lgrFFbXQA0HPygISpRYWYBjooPzhYSF81iA==", "dev": true, "requires": { "mdast-util-from-markdown": "^0.8.5" @@ -5987,31 +6449,31 @@ "dev": true, "requires": { "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } } }, "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", "dev": true }, "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz", + "integrity": "sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==", "dev": true, "requires": { - "acorn": "^7.4.0", + "acorn": "^8.6.0", "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "eslint-visitor-keys": "^3.1.0" } }, "esprima": { @@ -6030,9 +6492,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -6047,9 +6509,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -6208,17 +6670,17 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", + "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", "dev": true, "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.19.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.4.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", @@ -6232,13 +6694,13 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.9.6", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", "statuses": "~1.5.0", "type-is": "~1.6.18", "utils-merge": "1.0.1", @@ -6251,12 +6713,6 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6271,6 +6727,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, @@ -6368,6 +6830,17 @@ "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } } }, "fast-json-stable-stringify": { @@ -6383,9 +6856,9 @@ "dev": true }, "fastq": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", - "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -6504,9 +6977,9 @@ } }, "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -6515,15 +6988,21 @@ } }, "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -6535,9 +7014,15 @@ } }, "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", + "dev": true + }, + "flexsearch": { + "version": "0.6.32", + "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.6.32.tgz", + "integrity": "sha512-EF1BWkhwoeLtbIlDbY/vDSLBen/E5l/f1Vg7iX5CDymQCamcx1vhlc3tIZxIDplPjgi0jhG37c67idFbjg+v+Q==", "dev": true }, "flush-write-stream": { @@ -6551,9 +7036,9 @@ } }, "follow-redirects": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", - "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==", + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz", + "integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==", "dev": true }, "for-in": { @@ -6617,14 +7102,14 @@ } }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, "fs-write-stream-atomic": { @@ -6702,6 +7187,16 @@ "pump": "^3.0.0" } }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -6718,9 +7213,9 @@ } }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -6732,12 +7227,12 @@ } }, "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" } }, "glob-to-regexp": { @@ -6766,9 +7261,9 @@ } }, "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -6786,14 +7281,6 @@ "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" - }, - "dependencies": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - } } }, "got": { @@ -6831,8 +7318,35 @@ "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } } }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -7024,6 +7538,12 @@ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", "dev": true }, + "highlight.js": { + "version": "9.18.5", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz", + "integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -7053,12 +7573,6 @@ } } }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -7125,15 +7639,15 @@ "dev": true }, "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", "dev": true, "requires": { "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" } }, "http-cache-semantics": { @@ -7149,196 +7663,46 @@ "dev": true }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", - "dev": true - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - } - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", + "integrity": "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz", + "integrity": "sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg==", + "dev": true, + "requires": { + "@types/http-proxy": "^1.17.5", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" } }, "http-signature": { @@ -7395,9 +7759,9 @@ "dev": true }, "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, "immediate": { @@ -7477,6 +7841,15 @@ "path-exists": "^3.0.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -7704,9 +8077,9 @@ } }, "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", "dev": true, "requires": { "has": "^1.0.3" @@ -7797,9 +8170,9 @@ "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -7828,9 +8201,9 @@ "dev": true }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, "is-npm": { @@ -7855,9 +8228,9 @@ } }, "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, "is-path-cwd": { @@ -7893,9 +8266,9 @@ "dev": true }, "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", "dev": true }, "is-plain-object": { @@ -7918,9 +8291,9 @@ } }, "is-regexp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", - "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", "dev": true }, "is-resolvable": { @@ -7929,6 +8302,12 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -7959,6 +8338,21 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8011,9 +8405,9 @@ "dev": true }, "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, "istanbul-lib-instrument": { @@ -8059,9 +8453,9 @@ } }, "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "requires": { "debug": "^4.1.1", @@ -8070,9 +8464,9 @@ } }, "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.2.tgz", + "integrity": "sha512-0gHxuT1NNC0aEIL1zbJ+MTgPbbHhU77eJPuU35WKA7TgXiSNlCAx4PENoMrH0Or6M2H80TaZcWKhM0IK6V8gRw==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -8080,19 +8474,19 @@ } }, "jasmine": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.9.0.tgz", - "integrity": "sha512-JgtzteG7xnqZZ51fg7N2/wiQmXon09szkALcRMTgCMX4u/m17gVJFjObnvw5FXkZOWuweHPaPRVB6DI2uN0wVA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.10.0.tgz", + "integrity": "sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==", "dev": true, "requires": { "glob": "^7.1.6", - "jasmine-core": "~3.9.0" + "jasmine-core": "~3.10.0" } }, "jasmine-core": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz", - "integrity": "sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.10.1.tgz", + "integrity": "sha512-ooZWSDVAdh79Rrj4/nnfklL3NQVra0BcuhcuWoAwwi+znLDoUeH87AFfeX8s+YeYi6xlv5nveRyaA1v7CintfA==", "dev": true }, "javascript-stringify": { @@ -8130,13 +8524,12 @@ "dev": true }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsbn": { @@ -8163,16 +8556,10 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, "json-schema-traverse": { @@ -8209,30 +8596,31 @@ } }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } }, "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" } }, "karma": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.4.tgz", - "integrity": "sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q==", + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.9.tgz", + "integrity": "sha512-E/MqdLM9uVIhfuyVnrhlGBu4miafBdXEAEqCmwdEMh3n17C7UWC/8Kvm3AYKr91gc7scutekZ0xv6rxRaUCtnw==", "dev": true, "requires": { "body-parser": "^1.19.0", @@ -8253,10 +8641,10 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^3.1.0", + "socket.io": "^4.2.0", "source-map": "^0.6.1", "tmp": "^0.2.1", - "ua-parser-js": "^0.7.28", + "ua-parser-js": "^0.7.30", "yargs": "^16.1.1" } }, @@ -8281,23 +8669,23 @@ } }, "karma-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.3.tgz", - "integrity": "sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.1.0.tgz", + "integrity": "sha512-uIejpnArNFQIovB6EPsKO/T4XofELdJWXcA2ADXztFlKhHbr0Ws6ba7wKTMVWsIhEs4iJxdhQkCQrkkhFJSZCw==", "dev": true, "requires": { - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^4.0.3", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", "minimatch": "^3.0.4" } }, "karma-firefox-launcher": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.1.tgz", - "integrity": "sha512-VzDMgPseXak9DtfyE1O5bB2BwsMy1zzO1kUxVW1rP0yhC4tDNJ0p3JoFdzvrK4QqVzdqUMa9Rx9YzkdFp8hz3Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", + "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", "dev": true, "requires": { "is-wsl": "^2.2.0", @@ -8379,12 +8767,6 @@ "type-check": "~0.4.0" } }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, "linkify-it": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", @@ -8429,12 +8811,12 @@ } }, "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" } }, "lodash": { @@ -8498,18 +8880,22 @@ "lodash._reinterpolate": "^3.0.0" } }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, "log4js": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", @@ -8532,9 +8918,9 @@ } }, "loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", "dev": true }, "lower-case": { @@ -8603,6 +8989,15 @@ "uc.micro": "^1.0.5" }, "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -8798,24 +9193,24 @@ } }, "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", "dev": true }, "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "dev": true, "requires": { - "mime-db": "1.49.0" + "mime-db": "1.51.0" } }, "mimic-response": { @@ -8933,6 +9328,77 @@ "minimist": "^1.2.5" } }, + "mocha": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", + "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.2", + "debug": "4.3.2", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.25", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.1.5", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -8987,6 +9453,12 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -9099,6 +9571,15 @@ "lower-case": "^1.1.1" } }, + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -9151,9 +9632,9 @@ } }, "node-releases": { - "version": "1.1.75", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", - "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, "nopt": { @@ -9165,26 +9646,6 @@ "abbrev": "1" } }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9281,9 +9742,9 @@ } }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true }, "object-is": { @@ -9324,14 +9785,14 @@ } }, "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", + "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" + "es-abstract": "^1.19.1" } }, "object.pick": { @@ -9344,14 +9805,14 @@ } }, "object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" + "es-abstract": "^1.19.1" } }, "obuf": { @@ -9459,21 +9920,21 @@ "dev": true }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" } }, "p-map": { @@ -9580,15 +10041,13 @@ } }, "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "json-parse-better-errors": "^1.0.1" } }, "parseurl": { @@ -9671,9 +10130,9 @@ } }, "perfect-scrollbar": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.2.tgz", - "integrity": "sha512-McHAinFkyzKbBZrFtb4MT2mxkehp15KvOX/UrjB8C5EZZXHTHgyETo5IGFYtHRTI2Pb2bsV0OE0YnkjT9Cw3aw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.3.tgz", + "integrity": "sha512-+Lo6t61lSuCY9ghpqh1NFMXOu8fNwlYGqPoUMOZ3HTFIL4g7+L7zD7hQCLW5yjkOZ6LGTw1m9+MfEew7cngtAQ==", "dev": true }, "performance-now": { @@ -9682,6 +10141,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -9725,122 +10190,99 @@ "dev": true, "requires": { "find-up": "^4.0.0" - } - }, - "pngjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", - "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", - "dev": true - }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" }, "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "ms": "^2.1.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "p-locate": "^4.1.0" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "p-try": "^2.0.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "color-name": "1.1.3" + "p-limit": "^2.2.0" } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + } + } + }, + "pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ms": "^2.1.1" } } } }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + } + } + }, "postcss-calc": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", @@ -10392,9 +10834,9 @@ } }, "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", + "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -10432,9 +10874,9 @@ } }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "prelude-ls": { @@ -10450,9 +10892,9 @@ "dev": true }, "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true, "optional": true }, @@ -10473,9 +10915,9 @@ "dev": true }, "prismjs": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz", - "integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz", + "integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==", "dev": true }, "process": { @@ -10613,9 +11055,9 @@ "dev": true }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", "dev": true }, "query-string": { @@ -10679,13 +11121,13 @@ "dev": true }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "dev": true, "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.1", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -10719,26 +11161,6 @@ "pify": "^2.3.0" } }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -10787,12 +11209,12 @@ "dev": true }, "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", + "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", "dev": true, "requires": { - "regenerate": "^1.4.0" + "regenerate": "^1.4.2" } }, "regenerator-runtime": { @@ -10858,17 +11280,17 @@ "dev": true }, "regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", + "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", "dev": true, "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^9.0.0", + "regjsgen": "^0.5.2", + "regjsparser": "^0.7.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" } }, "registry-auth-token": { @@ -10896,9 +11318,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", - "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", + "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -10944,28 +11366,46 @@ "dev": true }, "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz", + "integrity": "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==", "dev": true, "requires": { "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "css-what": "^5.1.0", + "domhandler": "^4.3.0", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" } }, "css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", + "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, "nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", "dev": true, "requires": { "boolbase": "^1.0.0" @@ -11036,12 +11476,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -11158,9 +11592,9 @@ } }, "rollup": { - "version": "2.56.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.2.tgz", - "integrity": "sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ==", + "version": "2.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.62.0.tgz", + "integrity": "sha512-cJEQq2gwB0GWMD3rYImefQTSjrPYaC6s4J9pYqnstVLJ1CHa/aZNVkD4Epuvg4iLeMA4KRiq7UM7awKK6j7jcw==", "dev": true, "requires": { "fsevents": "~2.3.2" @@ -11177,9 +11611,9 @@ }, "dependencies": { "@rollup/pluginutils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.1.tgz", - "integrity": "sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz", + "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==", "dev": true, "requires": { "estree-walker": "^2.0.1", @@ -11204,6 +11638,17 @@ "jest-worker": "^26.2.1", "serialize-javascript": "^4.0.0", "terser": "^5.0.0" + }, + "dependencies": { + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + } } }, "run-parallel": { @@ -11296,6 +11741,12 @@ "node-forge": "^0.10.0" } }, + "semiver": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", + "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", + "dev": true + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -11323,9 +11774,9 @@ } }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", "dev": true, "requires": { "debug": "2.6.9", @@ -11335,9 +11786,9 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "1.8.1", "mime": "1.6.0", - "ms": "2.1.1", + "ms": "2.1.3", "on-finished": "~2.3.0", "range-parser": "~1.2.1", "statuses": "~1.5.0" @@ -11367,17 +11818,17 @@ "dev": true }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } }, "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -11440,15 +11891,15 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.17.2" } }, "set-blocking": { @@ -11476,9 +11927,9 @@ "dev": true }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, "sha.js": { @@ -11518,9 +11969,9 @@ } }, "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", "dev": true }, "simple-swizzle": { @@ -11546,17 +11997,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, "smoothscroll-polyfill": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz", @@ -11674,26 +12114,23 @@ } }, "socket.io": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", - "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.0.tgz", + "integrity": "sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ==", "dev": true, "requires": { - "@types/cookie": "^0.4.0", - "@types/cors": "^2.8.8", - "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "~2.0.0", - "debug": "~4.3.1", - "engine.io": "~4.1.0", - "socket.io-adapter": "~2.1.0", - "socket.io-parser": "~4.0.3" + "debug": "~4.3.2", + "engine.io": "~6.1.0", + "socket.io-adapter": "~2.3.3", + "socket.io-parser": "~4.0.4" } }, "socket.io-adapter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", - "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", + "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", "dev": true }, "socket.io-parser": { @@ -11708,20 +12145,28 @@ } }, "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "requires": { "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", + "uuid": "^8.3.2", "websocket-driver": "^0.7.4" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "sockjs-client": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", - "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", + "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==", "dev": true, "requires": { "debug": "^3.2.6", @@ -11729,7 +12174,7 @@ "faye-websocket": "^0.11.3", "inherits": "^2.0.4", "json3": "^3.3.3", - "url-parse": "^1.5.1" + "url-parse": "^1.5.3" }, "dependencies": { "debug": { @@ -11750,6 +12195,14 @@ "dev": true, "requires": { "is-plain-obj": "^1.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + } } }, "source-list-map": { @@ -11778,9 +12231,9 @@ } }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -11799,38 +12252,6 @@ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", "dev": true }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, "spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -11973,18 +12394,18 @@ "dev": true }, "std-env": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.0.tgz", - "integrity": "sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.1.tgz", + "integrity": "sha512-eOsoKTWnr6C8aWrqJJ2KAReXoa7Vn5Ywyw6uCXgA/xDhxPoaIsBa5aNJmISY04dLwXPBnDHW4diGM7Sn5K4R/g==", "dev": true, "requires": { - "ci-info": "^3.0.0" + "ci-info": "^3.1.1" }, "dependencies": { "ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", "dev": true } } @@ -12044,6 +12465,32 @@ "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true } } }, @@ -12054,14 +12501,14 @@ "dev": true }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "string.prototype.trimend": { @@ -12093,13 +12540,24 @@ "safe-buffer": "~5.1.0" } }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom-string": { @@ -12251,6 +12709,15 @@ "color-convert": "^1.9.0" } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -12289,6 +12756,16 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -12300,40 +12777,6 @@ } } }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -12347,14 +12790,14 @@ "dev": true }, "terser": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.1.tgz", - "integrity": "sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", + "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", "dev": true, "requires": { "commander": "^2.20.0", "source-map": "~0.7.2", - "source-map-support": "~0.5.19" + "source-map-support": "~0.5.20" }, "dependencies": { "source-map": { @@ -12428,6 +12871,15 @@ "semver": "^5.6.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -12475,6 +12927,15 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "terser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", @@ -12666,9 +13127,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, "toml": { @@ -12693,6 +13154,12 @@ "punycode": "^2.1.1" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -12776,15 +13243,15 @@ } }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true }, "ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", "dev": true }, "uc.micro": { @@ -12824,31 +13291,31 @@ } }, "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", "dev": true }, "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" } }, "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", "dev": true }, "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", "dev": true }, "union-value": { @@ -12912,9 +13379,9 @@ } }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "unpipe": { @@ -13087,9 +13554,9 @@ } }, "url-parse": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", - "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz", + "integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==", "dev": true, "requires": { "querystringify": "^2.1.1", @@ -13170,16 +13637,6 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -13201,6 +13658,14 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + } } }, "vm-browserify": { @@ -13241,15 +13706,15 @@ } }, "vue-prism-editor": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-1.2.2.tgz", - "integrity": "sha512-Lq2VgVygTx3Whn/tC8gD4m1ajA4lzSyCTqPLZA1Dq/ErbBaZA93FWRblwCoDR7AD2nXhGWuiTzb5ih3guzB7DA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-1.3.0.tgz", + "integrity": "sha512-54RfgtMGRMNr9484zKMOZs1wyLDR6EfFylzE2QrMCD9alCvXyYYcS0vX8oUHh+6pMUu6ts59uSN9cHglpU2NRQ==", "dev": true }, "vue-router": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.2.tgz", - "integrity": "sha512-807gn82hTnjCYGrnF3eNmIw/dk7/GE4B5h69BlyCK9KHASwSloD1Sjcn06zg9fVG4fYH2DrsNBZkpLtb25WtaQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz", + "integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg==", "dev": true }, "vue-server-renderer": { @@ -13358,24 +13823,25 @@ "dev": true }, "vue2-perfect-scrollbar": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/vue2-perfect-scrollbar/-/vue2-perfect-scrollbar-1.5.0.tgz", - "integrity": "sha512-hQAjhOX9MP3/ePa/AozKKtpiHSaV0dBlniyt2L7eqC/PsDx+pu7YBmTpM0KR5WlNq5GZwreK6215L8ZG1EHC7w==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/vue2-perfect-scrollbar/-/vue2-perfect-scrollbar-1.5.2.tgz", + "integrity": "sha512-3TSBuRUgeLmiwM7bcqQ7OIoOIcltjNocGWwoHfwgHUhkVjVUVNRun2XFdwvN9hZFfjDDW9cHYsEoIQwvgsSCbw==", "dev": true, "requires": { "cssnano": "^4.1.3", - "perfect-scrollbar": "^1.5.0", + "perfect-scrollbar": "^1.5.2", "postcss-import": "^12.0.0" } }, "vuepress": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.8.2.tgz", - "integrity": "sha512-BU1lUDwsA3ghf7a9ga4dsf0iTc++Z/l7BR1kUagHWVBHw7HNRgRDfAZBDDQXhllMILVToIxaTifpne9mSi94OA==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.9.5.tgz", + "integrity": "sha512-cLLCKkJSxL0PCgHZILrZ13z57Wi66zABhwe2n0JaoXq/bWXdGPfr9WoObVdQ0p186I/IKRIw4Ip22Y6qy+JA8g==", "dev": true, "requires": { - "@vuepress/core": "1.8.2", - "@vuepress/theme-default": "1.8.2", + "@vuepress/core": "1.9.5", + "@vuepress/theme-default": "1.9.5", + "@vuepress/types": "1.9.5", "cac": "^6.5.6", "envinfo": "^7.2.0", "opencollective-postinstall": "^2.0.2", @@ -13458,6 +13924,16 @@ "@vuepress/shared-utils": "^1.2.0" } }, + "vuepress-plugin-flexsearch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/vuepress-plugin-flexsearch/-/vuepress-plugin-flexsearch-0.3.0.tgz", + "integrity": "sha512-dffrD35hDE6FcpN3JRTy5E9tccq1uB7l+ocdPBObuiuFjHJP/xlU+pOR3Yc6yQlsvP5ResweGOP2kaeGViorBg==", + "dev": true, + "requires": { + "@vuepress/plugin-search": "^1.8.2", + "flexsearch": "^0.6.32" + } + }, "vuepress-plugin-redirect": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/vuepress-plugin-redirect/-/vuepress-plugin-redirect-1.2.5.tgz", @@ -13486,14 +13962,6 @@ "acorn": "^8.1.0", "vue-prism-editor": "^1.2.2", "vue2-perfect-scrollbar": "^1.5.0" - }, - "dependencies": { - "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", - "dev": true - } } }, "watchpack": { @@ -13789,6 +14257,12 @@ "minimalistic-assert": "^1.0.0" } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, "webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", @@ -14022,12 +14496,12 @@ } }, "webpack-dev-server": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz", - "integrity": "sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==", + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz", + "integrity": "sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA==", "dev": true, "requires": { - "ansi-html": "0.0.7", + "ansi-html-community": "0.0.8", "bonjour": "^3.5.0", "chokidar": "^2.1.8", "compression": "^1.7.4", @@ -14167,6 +14641,12 @@ } } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -14242,6 +14722,18 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, "is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", @@ -14364,6 +14856,15 @@ } } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -14630,6 +15131,16 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "when": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", @@ -14688,6 +15199,12 @@ "errno": "~0.1.7" } }, + "workerpool": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "dev": true + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -14782,9 +15299,9 @@ } }, "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "dev": true }, "xdg-basedir": { @@ -14835,9 +15352,35 @@ } }, "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + } + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, "zepto": { diff --git a/package.json b/package.json index e4f1708ef..5614d020d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "chartjs-plugin-annotation", "homepage": "https://www.chartjs.org/chartjs-plugin-annotation/index", "description": "Annotations for Chart.js", - "version": "1.0.2", + "version": "1.2.2", "author": "Evert Timberg ", "license": "MIT", "main": "dist/chartjs-plugin-annotation.js", @@ -19,32 +19,36 @@ "scripts": { "build": "rollup -c", "dev": "karma start --auto-watch --no-single-run --browsers chrome", + "dev:ff": "karma start --auto-watch --no-single-run --browsers firefox", "docs": "npm run build && vuepress build docs --no-cache", "docs:dev": "npm run build && vuepress dev docs --no-cache", "lint": "concurrently \"npm:lint-*\"", - "lint-js": "eslint \"old_samples/**/*.html\" \"test/**/*.js\" \"src/**/*.js\"", + "lint-js": "eslint \"test/**/*.js\" \"src/**/*.js\"", "lint-md": "eslint \"**/*.md\"", "lint-types": "eslint \"types/**/*.ts\" && tsc -p types/tests/", "test": "cross-env NODE_ENV=test concurrently \"npm:test-*\"", - "test-types": "tsc -p types/tests/", + "test-integration": "mocha --full-trace test/integration/*-test.js", + "test-karma": "karma start --auto-watch --single-run", "test-lint": "npm run lint", - "test-karma": "karma start --auto-watch --single-run" + "test-types": "tsc -p types/tests/" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.0.0", - "@typescript-eslint/eslint-plugin": "^4.22.0", - "@typescript-eslint/parser": "^4.22.0", + "@simonbrunel/vuepress-plugin-versions": "^0.2.0", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "@typescript-eslint/parser": "^5.4.0", "chart.js": "^3.1.0", - "chartjs-plugin-autocolors": "0.0.3", + "chartjs-plugin-autocolors": "^0.0.5", "chartjs-test-utils": "^0.3.0", "concurrently": "^6.0.2", "cross-env": "^7.0.3", - "eslint": "^7.24.0", + "eslint": "^8.2.0", "eslint-config-chartjs": "^0.3.0", "eslint-plugin-es": "^4.1.0", "eslint-plugin-html": "^6.1.2", "eslint-plugin-markdown": "^2.0.1", + "fs-extra": "^10.0.0", "karma": "^6.3.2", "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.0.3", @@ -52,12 +56,14 @@ "karma-jasmine": "^4.0.1", "karma-jasmine-html-reporter": "^1.5.4", "karma-rollup-preprocessor": "^7.0.7", + "mocha": "^9.1.3", "pixelmatch": "^5.2.1", "rollup": "^2.45.2", "rollup-plugin-istanbul": "^3.0.0", "rollup-plugin-terser": "^7.0.2", "typescript": "^4.2.4", "vuepress": "^1.8.2", + "vuepress-plugin-flexsearch": "^0.3.0", "vuepress-plugin-redirect": "^1.2.5", "vuepress-theme-chartjs": "^0.2.0" }, diff --git a/scripts/docs-config.sh b/scripts/docs-config.sh new file mode 100755 index 000000000..25b3589d9 --- /dev/null +++ b/scripts/docs-config.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +# tag is next|latest|master|x.x.x +# https://www.chartjs.org/chartjs-pligin-annotation/$tag/ +function tag_from_version { + local version=$1 + local mode=$2 + local tag='' + if [ "$version" == "master" ]; then + tag=master + elif [[ "$version" =~ ^[^-]+$ ]]; then + if [[ "$mode" == "release" ]]; then + tag=$version + else + tag=latest + fi + else + tag=next + fi + echo $tag +} + +VERSION=$1 +MODE=$2 + +TAG=$(tag_from_version "$VERSION" "$MODE") + +sed -i -e "s/VERSION/$TAG/g" "docs/.vuepress/config.js" diff --git a/src/annotation.js b/src/annotation.js index 8d71f4c38..3fa327399 100644 --- a/src/annotation.js +++ b/src/annotation.js @@ -1,34 +1,38 @@ -import ChartJsV3, {Animations, Chart, defaults} from 'chart.js-v3'; -const {clipArea, unclipArea, isFinite, valueOrDefault, isObject, isArray} = ChartJsV3.helpers; +import ChartJsV3, {Animations, Chart} from 'chart.js-v3'; +const {clipArea, unclipArea, isObject, isArray} = ChartJsV3.helpers; import {handleEvent, hooks, updateListeners} from './events'; -import BoxAnnotation from './types/box'; -import LineAnnotation from './types/line'; -import EllipseAnnotation from './types/ellipse'; -import PointAnnotation from './types/point'; -import {version} from '../package.json'; +import {adjustScaleRange, verifyScaleOptions} from './scale'; +import {annotationTypes} from './types'; +import {requireVersion} from './helpers'; +import {name, version} from '../package.json'; const chartStates = new Map(); -const annotationTypes = { - box: BoxAnnotation, - line: LineAnnotation, - ellipse: EllipseAnnotation, - point: PointAnnotation -}; - -Object.keys(annotationTypes).forEach(key => { - defaults.describe(`elements.${annotationTypes[key].id}`, { - _fallback: 'plugins.annotation' - }); -}); - export default { id: 'annotation', version, + /* TODO: enable in v2 + beforeRegister() { + requireVersion('chart.js', '3.7', Chart.version); + }, + */ + afterRegister() { Chart.register(annotationTypes); + + // TODO: Remove this check, warning and workaround in v2 + if (!requireVersion('chart.js', '3.7', Chart.version, false)) { + console.warn(`${name} has known issues with chart.js versions prior to 3.7, please consider upgrading.`); + + // Workaround for https://github.com/chartjs/chartjs-plugin-annotation/issues/572 + Chart.defaults.set('elements.lineAnnotation', { + callout: {}, + font: {}, + padding: 6 + }); + } }, afterUnregister() { @@ -39,6 +43,7 @@ export default { chartStates.set(chart, { annotations: [], elements: [], + visibleElements: [], listeners: {}, listened: false, moveListened: false @@ -61,6 +66,7 @@ export default { } else if (isArray(annotationOptions)) { annotations.push(...annotationOptions); } + verifyScaleOptions(annotations, chart.scales); }, afterDataLimits(chart, args) { @@ -72,27 +78,28 @@ export default { const state = chartStates.get(chart); updateListeners(chart, state, options); updateElements(chart, state, options, args.mode); + state.visibleElements = state.elements.filter(el => !el.skip && el.options.display); }, - beforeDatasetsDraw(chart) { - draw(chart, 'beforeDatasetsDraw'); + beforeDatasetsDraw(chart, _args, options) { + draw(chart, 'beforeDatasetsDraw', options.clip); }, - afterDatasetsDraw(chart) { - draw(chart, 'afterDatasetsDraw'); + afterDatasetsDraw(chart, _args, options) { + draw(chart, 'afterDatasetsDraw', options.clip); }, - beforeDraw(chart) { - draw(chart, 'beforeDraw'); + beforeDraw(chart, _args, options) { + draw(chart, 'beforeDraw', options.clip); }, - afterDraw(chart) { - draw(chart, 'afterDraw'); + afterDraw(chart, _args, options) { + draw(chart, 'afterDraw', options.clip); }, beforeEvent(chart, args, options) { const state = chartStates.get(chart); - handleEvent(chart, state, args.event, options); + handleEvent(state, args.event, options); }, destroy(chart) { @@ -104,14 +111,15 @@ export default { }, defaults: { - drawTime: 'afterDatasetsDraw', - dblClickSpeed: 350, // ms animations: { numbers: { - properties: ['x', 'y', 'x2', 'y2', 'width', 'height'], + properties: ['x', 'y', 'x2', 'y2', 'width', 'height', 'pointX', 'pointY', 'labelX', 'labelY', 'labelWidth', 'labelHeight', 'radius'], type: 'number' }, }, + clip: true, + dblClickSpeed: 350, // ms + drawTime: 'afterDatasetsDraw', label: { drawTime: null } @@ -122,7 +130,7 @@ export default { _scriptable: (prop) => !hooks.includes(prop), annotations: { _allKeys: false, - _fallback: (prop, opts) => `elements.${annotationTypes[opts.type || 'line'].id}`, + _fallback: (prop, opts) => `elements.${annotationTypes[resolveType(opts.type)].id}`, }, }, @@ -140,6 +148,14 @@ function resolveAnimations(chart, animOpts, mode) { return new Animations(chart, animOpts); } +function resolveType(type = 'line') { + if (annotationTypes[type]) { + return type; + } + console.warn(`Unknown annotation type: '${type}', defaulting to 'line'`); + return 'line'; +} + function updateElements(chart, state, options, mode) { const animations = resolveAnimations(chart, options.animations, mode); @@ -147,27 +163,60 @@ function updateElements(chart, state, options, mode) { const elements = resyncElements(state.elements, annotations); for (let i = 0; i < annotations.length; i++) { - const annotation = annotations[i]; - let el = elements[i]; - const elType = annotationTypes[annotation.type] || annotationTypes.line; - if (!el || !(el instanceof elType)) { - el = elements[i] = new elType(); - } - const opts = resolveAnnotationOptions(annotation.setContext(getContext(chart, el, annotation))); - const properties = el.resolveElementProperties(chart, opts); + const annotationOptions = annotations[i]; + const element = getOrCreateElement(elements, i, annotationOptions.type); + const resolver = annotationOptions.setContext(getContext(chart, element, annotationOptions)); + const resolvedOptions = resolveAnnotationOptions(resolver); + const properties = element.resolveElementProperties(chart, resolvedOptions); + properties.skip = isNaN(properties.x) || isNaN(properties.y); - properties.options = opts; - animations.update(el, properties); + properties.options = resolvedOptions; + + if ('elements' in properties) { + updateSubElements(element, properties, resolver, animations); + // Remove the sub-element definitions from properties, so the actual elements + // are not overwritten by their definitions + delete properties.elements; + } + + animations.update(element, properties); } } +function updateSubElements(mainElement, {elements, initProperties}, resolver, animations) { + const subElements = mainElement.elements || (mainElement.elements = []); + subElements.length = elements.length; + for (let i = 0; i < elements.length; i++) { + const definition = elements[i]; + const properties = definition.properties; + const subElement = getOrCreateElement(subElements, i, definition.type, initProperties); + const subResolver = resolver[definition.optionScope].override(definition); + properties.options = resolveAnnotationOptions(subResolver); + animations.update(subElement, properties); + } +} + +function getOrCreateElement(elements, index, type, initProperties) { + const elementClass = annotationTypes[resolveType(type)]; + let element = elements[index]; + if (!element || !(element instanceof elementClass)) { + element = elements[index] = new elementClass(); + if (isObject(initProperties)) { + Object.assign(element, initProperties); + } + } + return element; +} + function resolveAnnotationOptions(resolver) { - const elType = annotationTypes[resolver.type] || annotationTypes.line; + const elementClass = annotationTypes[resolveType(resolver.type)]; const result = {}; result.id = resolver.id; result.type = resolver.type; result.drawTime = resolver.drawTime; - Object.assign(result, resolveObj(resolver, elType.defaults), resolveObj(resolver, elType.defaultRoutes)); + Object.assign(result, + resolveObj(resolver, elementClass.defaults), + resolveObj(resolver, elementClass.defaultRoutes)); for (const hook of hooks) { result[hook] = resolver[hook]; } @@ -176,10 +225,10 @@ function resolveAnnotationOptions(resolver) { function resolveObj(resolver, defs) { const result = {}; - for (const name of Object.keys(defs)) { - const optDefs = defs[name]; - const value = resolver[name]; - result[name] = isObject(optDefs) ? resolveObj(value, optDefs) : value; + for (const prop of Object.keys(defs)) { + const optDefs = defs[prop]; + const value = resolver[prop]; + result[prop] = isObject(optDefs) ? resolveObj(value, optDefs) : value; } return result; } @@ -205,72 +254,44 @@ function resyncElements(elements, annotations) { return elements; } -function draw(chart, caller) { +function draw(chart, caller, clip) { const {ctx, chartArea} = chart; - const state = chartStates.get(chart); - const elements = state.elements.filter(el => !el.skip && el.options.display); + const {visibleElements} = chartStates.get(chart); - clipArea(ctx, chartArea); - elements.forEach(el => { - if (el.options.drawTime === caller) { - el.draw(ctx); - } - }); - unclipArea(ctx); + if (clip) { + clipArea(ctx, chartArea); + } - elements.forEach(el => { - if ('drawLabel' in el && el.options.label && (el.options.label.drawTime || el.options.drawTime) === caller) { + drawElements(ctx, visibleElements, caller); + drawSubElements(ctx, visibleElements, caller); + + if (clip) { + unclipArea(ctx); + } + + visibleElements.forEach(el => { + if (!('drawLabel' in el)) { + return; + } + const label = el.options.label; + if (label && label.enabled && label.content && (label.drawTime || el.options.drawTime) === caller) { el.drawLabel(ctx, chartArea); } }); } -function adjustScaleRange(chart, scale, annotations) { - const range = getScaleLimits(scale, annotations); - let changed = false; - if (isFinite(range.min) && - typeof scale.options.min === 'undefined' && - typeof scale.options.suggestedMin === 'undefined') { - changed = scale.min !== range.min; - scale.min = range.min; - } - if (isFinite(range.max) && - typeof scale.options.max === 'undefined' && - typeof scale.options.suggestedMax === 'undefined') { - changed = scale.max !== range.max; - scale.max = range.max; - } - if (changed && typeof scale.handleTickRangeOptions === 'function') { - scale.handleTickRangeOptions(); +function drawElements(ctx, elements, caller) { + for (const el of elements) { + if (el.options.drawTime === caller) { + el.draw(ctx); + } } } -function getScaleLimits(scale, annotations) { - const axis = scale.axis; - const scaleID = scale.id; - const scaleIDOption = axis + 'ScaleID'; - let min = valueOrDefault(scale.min, Number.NEGATIVE_INFINITY); - let max = valueOrDefault(scale.max, Number.POSITIVE_INFINITY); - for (const annotation of annotations) { - if (annotation.scaleID === scaleID) { - for (const prop of ['value', 'endValue']) { - const raw = annotation[prop]; - if (raw) { - const value = scale.parse(raw); - min = Math.min(min, value); - max = Math.max(max, value); - } - } - } else if (annotation[scaleIDOption] === scaleID) { - for (const prop of [axis + 'Min', axis + 'Max', axis + 'Value']) { - const raw = annotation[prop]; - if (raw) { - const value = scale.parse(raw); - min = Math.min(min, value); - max = Math.max(max, value); - } - } +function drawSubElements(ctx, elements, caller) { + for (const el of elements) { + if (isArray(el.elements)) { + drawElements(ctx, el.elements, caller); } } - return {min, max}; } diff --git a/src/events.js b/src/events.js index 179b70fff..dce9b48fc 100644 --- a/src/events.js +++ b/src/events.js @@ -1,13 +1,11 @@ import ChartJsV3 from 'chart.js-v3'; -const {distanceBetweenPoints} = ChartJsV3.helpers; -const callHandler = ChartJsV3.helpers.callback; +const {distanceBetweenPoints, defined, callback} = ChartJsV3.helpers; const clickHooks = ['click', 'dblclick']; const moveHooks = ['enter', 'leave']; export const hooks = clickHooks.concat(moveHooks); export function updateListeners(chart, state, options) { - const annotations = state.annotations || []; state.listened = false; state.moveListened = false; @@ -15,6 +13,8 @@ export function updateListeners(chart, state, options) { if (typeof options[hook] === 'function') { state.listened = true; state.listeners[hook] = options[hook]; + } else if (defined(state.listeners[hook])) { + delete state.listeners[hook]; } }); moveHooks.forEach(hook => { @@ -24,7 +24,7 @@ export function updateListeners(chart, state, options) { }); if (!state.listened || !state.moveListened) { - annotations.forEach(scope => { + state.annotations.forEach(scope => { if (!state.listened) { clickHooks.forEach(hook => { if (typeof scope[hook] === 'function') { @@ -44,22 +44,22 @@ export function updateListeners(chart, state, options) { } } -export function handleEvent(chart, state, event, options) { +export function handleEvent(state, event, options) { if (state.listened) { switch (event.type) { case 'mousemove': case 'mouseout': - handleMoveEvents(chart, state, event); + handleMoveEvents(state, event); break; case 'click': - handleClickEvents(chart, state, event, options); + handleClickEvents(state, event, options); break; default: } } } -function handleMoveEvents(chart, state, event) { +function handleMoveEvents(state, event) { if (!state.moveListened) { return; } @@ -73,20 +73,20 @@ function handleMoveEvents(chart, state, event) { const previous = state.hovered; state.hovered = element; - dispatchMoveEvents(chart, state, {previous, element}, event); + dispatchMoveEvents(state, {previous, element}, event); } -function dispatchMoveEvents(chart, state, elements, event) { +function dispatchMoveEvents(state, elements, event) { const {previous, element} = elements; if (previous && previous !== element) { - dispatchEvent(chart, previous.options.leave || state.listeners.leave, previous, event); + dispatchEvent(previous.options.leave || state.listeners.leave, previous, event); } if (element && element !== previous) { - dispatchEvent(chart, element.options.enter || state.listeners.enter, element, event); + dispatchEvent(element.options.enter || state.listeners.enter, element, event); } } -function handleClickEvents(chart, state, event, options) { +function handleClickEvents(state, event, options) { const listeners = state.listeners; const element = getNearestItem(state.elements, event); if (element) { @@ -97,22 +97,22 @@ function handleClickEvents(chart, state, event, options) { // 2nd click before timeout, so its a double click clearTimeout(element.clickTimeout); delete element.clickTimeout; - dispatchEvent(chart, dblclick, element, event); + dispatchEvent(dblclick, element, event); } else if (dblclick) { // if there is a dblclick handler, wait for dblClickSpeed ms before deciding its a click element.clickTimeout = setTimeout(() => { delete element.clickTimeout; - dispatchEvent(chart, click, element, event); + dispatchEvent(click, element, event); }, options.dblClickSpeed); } else { // no double click handler, just call the click handler directly - dispatchEvent(chart, click, element, event); + dispatchEvent(click, element, event); } } } -function dispatchEvent(chart, handler, element, event) { - callHandler(handler, [{chart, element}, event]); +function dispatchEvent(handler, element, event) { + callback(handler, [element.$context, event]); } function getNearestItem(elements, position) { diff --git a/src/helpers.js b/src/helpers.js deleted file mode 100644 index 256a9ff87..000000000 --- a/src/helpers.js +++ /dev/null @@ -1,35 +0,0 @@ -import ChartJsV3 from 'chart.js-v3'; -const {isFinite} = ChartJsV3.helpers; - -export const clamp = (x, from, to) => Math.min(to, Math.max(from, x)); - -export function clampAll(obj, from, to) { - for (const key of Object.keys(obj)) { - obj[key] = clamp(obj[key], from, to); - } - return obj; -} - -export function scaleValue(scale, value, fallback) { - value = typeof value === 'number' ? value : scale.parse(value); - return isFinite(value) ? scale.getPixelForValue(value) : fallback; -} - -/** - * Rotate a `point` relative to `center` point by `angle` - * @param {{x: number, y: number}} point - the point to rotate - * @param {{x: number, y: number}} center - center point for rotation - * @param {number} angle - angle for rotation, in radians - * @returns {{x: number, y: number}} rotated point - */ -export function rotated(point, center, angle) { - var cos = Math.cos(angle); - var sin = Math.sin(angle); - var cx = center.x; - var cy = center.y; - - return { - x: cx + cos * (point.x - cx) - sin * (point.y - cy), - y: cy + sin * (point.x - cx) + cos * (point.y - cy) - }; -} diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js new file mode 100644 index 000000000..02779b36c --- /dev/null +++ b/src/helpers/helpers.canvas.js @@ -0,0 +1,122 @@ +import ChartJsV3 from 'chart.js-v3'; +const {addRoundedRectPath, isArray, toFont, toTRBLCorners, valueOrDefault} = ChartJsV3.helpers; + +import {Image as CanvasImage} from 'canvas'; + +import {clampAll} from './helpers.core'; +import {calculateTextAlignment, getSize} from './helpers.options'; + +const widthCache = new Map(); + +export function isImageOrCanvas(content) { + return content instanceof CanvasImage || content instanceof Image || content instanceof HTMLCanvasElement; +} + +/** + * Apply border options to the canvas context before drawing a shape + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options with border configuration + * @returns {boolean} true is the border options have been applied + */ +export function setBorderStyle(ctx, options) { + if (options && options.borderWidth) { + ctx.lineCap = options.borderCapStyle; + ctx.setLineDash(options.borderDash); + ctx.lineDashOffset = options.borderDashOffset; + ctx.lineJoin = options.borderJoinStyle; + ctx.lineWidth = options.borderWidth; + ctx.strokeStyle = options.borderColor; + return true; + } +} + +/** + * Apply shadow options to the canvas context before drawing a shape + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options with shadow configuration + */ +export function setShadowStyle(ctx, options) { + ctx.shadowColor = options.backgroundShadowColor; + ctx.shadowBlur = options.shadowBlur; + ctx.shadowOffsetX = options.shadowOffsetX; + ctx.shadowOffsetY = options.shadowOffsetY; +} + +/** + * Measure the label size using the label options. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Object} options - options to configure the label + * @returns {{width: number, height: number}} the measured size of the label + */ +export function measureLabelSize(ctx, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + return { + width: getSize(content.width, options.width), + height: getSize(content.height, options.height) + }; + } + const font = toFont(options.font); + const lines = isArray(content) ? content : [content]; + 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); +} + +/** + * Draw a box with the size and the styling options. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {{x: number, y: number, width: number, height: number}} rect - rect to draw + * @param {Object} options - options to style the box + * @returns {undefined} + */ +export function drawBox(ctx, rect, options) { + const {x, y, width, height} = rect; + ctx.save(); + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + ctx.fillStyle = options.backgroundColor; + ctx.beginPath(); + addRoundedRectPath(ctx, { + x, y, w: width, h: height, + // TODO: v2 remove support for cornerRadius + radius: clampAll(toTRBLCorners(valueOrDefault(options.cornerRadius, options.borderRadius)), 0, Math.min(width, height) / 2) + }); + ctx.closePath(); + ctx.fill(); + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); +} + +export function drawLabel(ctx, rect, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + ctx.drawImage(content, rect.x, rect.y, rect.width, rect.height); + return; + } + const labels = isArray(content) ? content : [content]; + const font = toFont(options.font); + const lh = font.lineHeight; + const x = calculateTextAlignment(rect, options); + const y = rect.y + (lh / 2); + ctx.font = font.string; + ctx.textBaseline = 'middle'; + ctx.textAlign = options.textAlign; + ctx.fillStyle = options.color; + labels.forEach((l, i) => ctx.fillText(l, x, y + (i * lh))); +} diff --git a/src/helpers/helpers.chart.js b/src/helpers/helpers.chart.js new file mode 100644 index 000000000..bc02e4227 --- /dev/null +++ b/src/helpers/helpers.chart.js @@ -0,0 +1,134 @@ +import ChartJsV3 from 'chart.js-v3'; +const {isFinite} = ChartJsV3.helpers; +import {getRectCenterPoint} from './helpers.geometric'; +import {isBoundToPoint} from './helpers.options'; + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").Scale } Scale + * @typedef { import("chart.js").Point } Point + * @typedef { import('../../types/options').CoreAnnotationOptions } CoreAnnotationOptions + * @typedef { import('../../types/options').PointAnnotationOptions } PointAnnotationOptions + */ + +/** + * @param {Scale} scale + * @param {number|string} value + * @param {number} fallback + * @returns {number} + */ +export function scaleValue(scale, value, fallback) { + value = typeof value === 'number' ? value : scale.parse(value); + return isFinite(value) ? scale.getPixelForValue(value) : fallback; +} + +/** + * @param {Scale} scale + * @param {{start: number, end: number}} options + * @returns {{start: number, end: number}} + */ +function getChartDimensionByScale(scale, options) { + if (scale) { + const min = scaleValue(scale, options.min, options.start); + const max = scaleValue(scale, options.max, options.end); + return { + start: Math.min(min, max), + end: Math.max(min, max) + }; + } + return { + start: options.start, + end: options.end + }; +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {Point} + */ +export function getChartPoint(chart, options) { + const {chartArea, scales} = chart; + const xScale = scales[options.xScaleID]; + const yScale = scales[options.yScaleID]; + let x = chartArea.width / 2; + let y = chartArea.height / 2; + + if (xScale) { + x = scaleValue(xScale, options.xValue, x); + } + + if (yScale) { + y = scaleValue(yScale, options.yValue, y); + } + return {x, y}; +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {{x?:number, y?: number, x2?: number, y2?: number, width?: number, height?: number}} + */ +export function getChartRect(chart, options) { + const xScale = chart.scales[options.xScaleID]; + const yScale = chart.scales[options.yScaleID]; + let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea; + + if (!xScale && !yScale) { + return {}; + } + + const xDim = getChartDimensionByScale(xScale, {min: options.xMin, max: options.xMax, start: x, end: x2}); + x = xDim.start; + x2 = xDim.end; + const yDim = getChartDimensionByScale(yScale, {min: options.yMin, max: options.yMax, start: y, end: y2}); + y = yDim.start; + y2 = yDim.end; + + return { + x, + y, + x2, + y2, + width: x2 - x, + height: y2 - y + }; +} + +/** + * @param {Chart} chart + * @param {PointAnnotationOptions} options + */ +export function getChartCircle(chart, options) { + const point = getChartPoint(chart, options); + return { + x: point.x + options.xAdjust, + y: point.y + options.yAdjust, + width: options.radius * 2, + height: options.radius * 2 + }; +} + +/** + * @param {Chart} chart + * @param {PointAnnotationOptions} options + * @returns + */ +export function resolvePointPosition(chart, options) { + if (!isBoundToPoint(options)) { + const box = getChartRect(chart, options); + const point = getRectCenterPoint(box); + let radius = options.radius; + if (!radius || isNaN(radius)) { + radius = Math.min(box.width, box.height) / 2; + options.radius = radius; + } + return { + x: point.x + options.xAdjust, + y: point.y + options.yAdjust, + width: radius * 2, + height: radius * 2 + }; + } + return getChartCircle(chart, options); +} diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js new file mode 100644 index 000000000..5af154f0a --- /dev/null +++ b/src/helpers/helpers.core.js @@ -0,0 +1,50 @@ +export const clamp = (x, from, to) => Math.min(to, Math.max(from, x)); + +export function clampAll(obj, from, to) { + for (const key of Object.keys(obj)) { + obj[key] = clamp(obj[key], from, to); + } + return obj; +} + +export function inPointRange(point, center, radius, borderWidth) { + if (!point || !center || radius <= 0) { + return false; + } + const hBorderWidth = borderWidth / 2 || 0; + return (Math.pow(point.x - center.x, 2) + Math.pow(point.y - center.y, 2)) <= Math.pow(radius + hBorderWidth, 2); +} + +export function inBoxRange(mouseX, mouseY, {x, y, width, height}, borderWidth) { + const hBorderWidth = borderWidth / 2 || 0; + return mouseX >= x - hBorderWidth && + mouseX <= x + width + hBorderWidth && + mouseY >= y - hBorderWidth && + mouseY <= y + height + hBorderWidth; +} + +export function getElementCenterPoint(element, useFinalPosition) { + const {x, y} = element.getProps(['x', 'y'], useFinalPosition); + return {x, y}; +} + +const isOlderPart = (act, req) => req > act || (act.length > req.length && act.substr(0, req.length) === req); + +export function requireVersion(pkg, min, ver, strict = true) { + const parts = ver.split('.'); + let i = 0; + for (const req of min.split('.')) { + const act = parts[i++]; + if (parseInt(req, 10) < parseInt(act, 10)) { + break; + } + if (isOlderPart(act, req)) { + if (strict) { + throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`); + } else { + return false; + } + } + } + return true; +} diff --git a/src/helpers/helpers.geometric.js b/src/helpers/helpers.geometric.js new file mode 100644 index 000000000..72443a0ba --- /dev/null +++ b/src/helpers/helpers.geometric.js @@ -0,0 +1,34 @@ +/** + * @typedef {import('chart.js').Point} Point + */ + +/** + * @param {{x: number, y: number, width: number, height: number}} rect + * @returns {Point} + */ +export function getRectCenterPoint(rect) { + const {x, y, width, height} = rect; + return { + x: x + width / 2, + y: y + height / 2 + }; +} + +/** + * Rotate a `point` relative to `center` point by `angle` + * @param {Point} point - the point to rotate + * @param {Point} center - center point for rotation + * @param {number} angle - angle for rotation, in radians + * @returns {Point} rotated point + */ +export function rotated(point, center, angle) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const cx = center.x; + const cy = center.y; + + return { + x: cx + cos * (point.x - cx) - sin * (point.y - cy), + y: cy + sin * (point.x - cx) + cos * (point.y - cy) + }; +} diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js new file mode 100644 index 000000000..1c65545e5 --- /dev/null +++ b/src/helpers/helpers.options.js @@ -0,0 +1,57 @@ +import ChartJsV3 from 'chart.js-v3'; +const {isObject, valueOrDefault, defined} = ChartJsV3.helpers; +import {clamp} from './helpers.core'; + +const isPercentString = (s) => typeof s === 'string' && s.endsWith('%'); +const toPercent = (s) => clamp(parseFloat(s) / 100, 0, 1); + +export function getRelativePosition(size, positionOption) { + if (positionOption === 'start') { + return 0; + } + if (positionOption === 'end') { + return size; + } + if (isPercentString(positionOption)) { + return toPercent(positionOption) * size; + } + return size / 2; +} + +export function getSize(size, value) { + if (typeof value === 'number') { + return value; + } else if (isPercentString(value)) { + return toPercent(value) * size; + } + return size; +} + +export function calculateTextAlignment(size, options) { + const {x, width} = size; + const textAlign = options.textAlign; + if (textAlign === 'center') { + return x + width / 2; + } else if (textAlign === 'end' || textAlign === 'right') { + return x + width; + } + return x; +} + +export function toPosition(value) { + if (isObject(value)) { + return { + x: valueOrDefault(value.x, 'center'), + y: valueOrDefault(value.y, 'center'), + }; + } + value = valueOrDefault(value, 'center'); + return { + x: value, + y: value + }; +} + +export function isBoundToPoint(options) { + return options && (defined(options.xValue) || defined(options.yValue)); +} diff --git a/src/helpers/index.js b/src/helpers/index.js new file mode 100644 index 000000000..62a3fe021 --- /dev/null +++ b/src/helpers/index.js @@ -0,0 +1,5 @@ +export * from './helpers.canvas'; +export * from './helpers.chart'; +export * from './helpers.core'; +export * from './helpers.geometric'; +export * from './helpers.options'; diff --git a/src/scale.js b/src/scale.js new file mode 100644 index 000000000..437fcde91 --- /dev/null +++ b/src/scale.js @@ -0,0 +1,66 @@ +import ChartJsV3 from 'chart.js-v3'; +const {isFinite, valueOrDefault, defined} = ChartJsV3.helpers; + +export function adjustScaleRange(chart, scale, annotations) { + const range = getScaleLimits(scale, annotations); + let changed = changeScaleLimit(scale, range, 'min', 'suggestedMin'); + changed = changeScaleLimit(scale, range, 'max', 'suggestedMax') || changed; + if (changed && typeof scale.handleTickRangeOptions === 'function') { + scale.handleTickRangeOptions(); + } +} + +export function verifyScaleOptions(annotations, scales) { + for (const annotation of annotations) { + verifyScaleIDs(annotation, scales); + } +} + +function changeScaleLimit(scale, range, limit, suggestedLimit) { + if (isFinite(range[limit]) && !scaleLimitDefined(scale.options, limit, suggestedLimit)) { + const changed = scale[limit] !== range[limit]; + scale[limit] = range[limit]; + return changed; + } +} + +function scaleLimitDefined(scaleOptions, limit, suggestedLimit) { + return defined(scaleOptions[limit]) || defined(scaleOptions[suggestedLimit]); +} + +function verifyScaleIDs(annotation, scales) { + for (const key of ['scaleID', 'xScaleID', 'yScaleID']) { + if (annotation[key] && !scales[annotation[key]]) { + console.warn(`No scale found with id '${annotation[key]}' for annotation '${annotation.id}'`); + } + } +} + +function getScaleLimits(scale, annotations) { + const axis = scale.axis; + const scaleID = scale.id; + const scaleIDOption = axis + 'ScaleID'; + const limits = { + min: valueOrDefault(scale.min, Number.NEGATIVE_INFINITY), + max: valueOrDefault(scale.max, Number.POSITIVE_INFINITY) + }; + for (const annotation of annotations) { + if (annotation.scaleID === scaleID) { + updateLimits(annotation, scale, ['value', 'endValue'], limits); + } else if (annotation[scaleIDOption] === scaleID) { + updateLimits(annotation, scale, [axis + 'Min', axis + 'Max', axis + 'Value'], limits); + } + } + return limits; +} + +function updateLimits(annotation, scale, props, limits) { + for (const prop of props) { + const raw = annotation[prop]; + if (defined(raw)) { + const value = scale.parse(raw); + limits.min = Math.min(limits.min, value); + limits.max = Math.max(limits.max, value); + } + } +} diff --git a/src/types/box.js b/src/types/box.js index 8ccbb0f52..454d81702 100644 --- a/src/types/box.js +++ b/src/types/box.js @@ -1,107 +1,132 @@ import ChartJsV3, {Element} from 'chart.js-v3'; -const {addRoundedRectPath, toTRBLCorners, valueOrDefault} = ChartJsV3.helpers; -import {clampAll, scaleValue} from '../helpers'; +const {toPadding} = ChartJsV3.helpers; +import {drawBox, drawLabel, getRelativePosition, measureLabelSize, getRectCenterPoint, getChartRect, toPosition, inBoxRange} from '../helpers'; export default class BoxAnnotation extends Element { inRange(mouseX, mouseY, useFinalPosition) { - const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition); - - return mouseX >= x && - mouseX <= x + width && - mouseY >= y && - mouseY <= y + height; + return inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition), this.options.borderWidth); } getCenterPoint(useFinalPosition) { - const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition); - return { - x: x + width / 2, - y: y + height / 2 - }; + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); } draw(ctx) { - const {x, y, width, height, options} = this; - ctx.save(); + drawBox(ctx, this, this.options); + ctx.restore(); + } - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.fillStyle = options.backgroundColor; - - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; + drawLabel(ctx) { + const {x, y, width, height, options} = this; + const {label, borderWidth} = options; + const halfBorder = borderWidth / 2; + const position = toPosition(label.position); + const padding = toPadding(label.padding); + const labelSize = measureLabelSize(ctx, label); + const labelRect = { + x: calculateX(this, labelSize, position, padding), + y: calculateY(this, labelSize, position, padding), + width: labelSize.width, + height: labelSize.height + }; + ctx.save(); ctx.beginPath(); - addRoundedRectPath(ctx, { - x, y, w: width, h: height, - // TODO: v2 remove support for cornerRadius - radius: clampAll(toTRBLCorners(valueOrDefault(options.cornerRadius, options.borderRadius)), 0, Math.min(width, height) / 2) - }); - ctx.closePath(); - ctx.fill(); - - // If no border, don't draw it - if (options.borderWidth) { - ctx.stroke(); - } - + ctx.rect(x + halfBorder + padding.left, y + halfBorder + padding.top, + width - borderWidth - padding.width, height - borderWidth - padding.height); + ctx.clip(); + drawLabel(ctx, labelRect, label); ctx.restore(); } resolveElementProperties(chart, options) { - const xScale = chart.scales[options.xScaleID]; - const yScale = chart.scales[options.yScaleID]; - let {top: y, left: x, bottom: y2, right: x2} = chart.chartArea; - let min, max; - - if (!xScale && !yScale) { - return {options: {}}; - } - - if (xScale) { - min = scaleValue(xScale, options.xMin, x); - max = scaleValue(xScale, options.xMax, x2); - x = Math.min(min, max); - x2 = Math.max(min, max); - } - - if (yScale) { - min = scaleValue(yScale, options.yMin, y2); - max = scaleValue(yScale, options.yMax, y); - y = Math.min(min, max); - y2 = Math.max(min, max); - } - - return { - x, - y, - x2, - y2, - width: x2 - x, - height: y2 - y - }; + return getChartRect(chart, options); } } BoxAnnotation.id = 'boxAnnotation'; BoxAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0, - borderWidth: 1, + borderJoinStyle: 'miter', borderRadius: 0, - xScaleID: 'x', - xMin: undefined, + borderShadowColor: 'transparent', + borderWidth: 1, + cornerRadius: undefined, // TODO: v2 remove support for cornerRadius + display: true, + label: { + borderWidth: undefined, + color: 'black', + content: null, + drawTime: undefined, + enabled: false, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: 'bold' + }, + height: undefined, + padding: 6, + position: 'center', + textAlign: 'start', + xAdjust: 0, + yAdjust: 0, + width: undefined + }, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' }; BoxAnnotation.defaultRoutes = { borderColor: 'color', backgroundColor: 'color' }; + +BoxAnnotation.descriptors = { + label: { + _fallback: true + } +}; + +function calculateX(box, labelSize, position, padding) { + const {x: start, x2: end, width: size, options} = box; + const {xAdjust: adjust, borderWidth} = options.label; + return calculatePosition({start, end, size}, { + position: position.x, + padding: {start: padding.left, end: padding.right}, + adjust, borderWidth, + size: labelSize.width + }); +} + +function calculateY(box, labelSize, position, padding) { + const {y: start, y2: end, height: size, options} = box; + const {yAdjust: adjust, borderWidth} = options.label; + return calculatePosition({start, end, size}, { + position: position.y, + padding: {start: padding.top, end: padding.bottom}, + adjust, borderWidth, + size: labelSize.height + }); +} + +function calculatePosition(boxOpts, labelOpts) { + const {start, end} = boxOpts; + const {position, padding: {start: padStart, end: padEnd}, adjust, borderWidth} = labelOpts; + const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; + return start + borderWidth / 2 + adjust + padStart + getRelativePosition(availableSize, position); +} diff --git a/src/types/ellipse.js b/src/types/ellipse.js index 2a2255eee..621ee6b54 100644 --- a/src/types/ellipse.js +++ b/src/types/ellipse.js @@ -1,11 +1,15 @@ -import ChartJsV3 from 'chart.js-v3'; -const {toRadians} = ChartJsV3.helpers; -import BoxAnnotation from './box'; +import ChartJsV3, {Element} from 'chart.js-v3'; +const {PI, toRadians} = ChartJsV3.helpers; +import {getRectCenterPoint, getChartRect, setBorderStyle, setShadowStyle} from '../helpers'; -export default class EllipseAnnotation extends BoxAnnotation { +export default class EllipseAnnotation extends Element { - inRange(x, y) { - return pointInEllipse({x, y}, this); + inRange(mouseX, mouseY, useFinalPosition) { + return pointInEllipse({x: mouseX, y: mouseY}, this.getProps(['width', 'height'], useFinalPosition), this.options.rotation, this.options.borderWidth); + } + + getCenterPoint(useFinalPosition) { + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); } draw(ctx) { @@ -18,40 +22,45 @@ export default class EllipseAnnotation extends BoxAnnotation { if (options.rotation) { ctx.rotate(toRadians(options.rotation)); } - + setShadowStyle(ctx, this.options); ctx.beginPath(); - - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; ctx.fillStyle = options.backgroundColor; - - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; - - ctx.ellipse(0, 0, height / 2, width / 2, Math.PI / 2, 0, 2 * Math.PI); - + const stroke = setBorderStyle(ctx, options); + ctx.ellipse(0, 0, height / 2, width / 2, PI / 2, 0, 2 * PI); ctx.fill(); - ctx.stroke(); - + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } ctx.restore(); } + + resolveElementProperties(chart, options) { + return getChartRect(chart, options); + } + } EllipseAnnotation.id = 'ellipseAnnotation'; EllipseAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', borderWidth: 1, + display: true, rotation: 0, - xScaleID: 'x', - xMin: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' }; EllipseAnnotation.defaultRoutes = { @@ -59,7 +68,7 @@ EllipseAnnotation.defaultRoutes = { backgroundColor: 'color' }; -function pointInEllipse(p, ellipse) { +function pointInEllipse(p, ellipse, rotation, borderWidth) { const {width, height} = ellipse; const center = ellipse.getCenterPoint(true); const xRadius = width / 2; @@ -68,6 +77,12 @@ function pointInEllipse(p, ellipse) { if (xRadius <= 0 || yRadius <= 0) { return false; } - - return (Math.pow(p.x - center.x, 2) / Math.pow(xRadius, 2)) + (Math.pow(p.y - center.y, 2) / Math.pow(yRadius, 2)) <= 1.0; + // https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm + const angle = toRadians(rotation || 0); + const hBorderWidth = borderWidth / 2 || 0; + const cosAngle = Math.cos(angle); + const sinAngle = Math.sin(angle); + const a = Math.pow(cosAngle * (p.x - center.x) + sinAngle * (p.y - center.y), 2); + const b = Math.pow(sinAngle * (p.x - center.x) - cosAngle * (p.y - center.y), 2); + return (a / Math.pow(xRadius + hBorderWidth, 2)) + (b / Math.pow(yRadius + hBorderWidth, 2)) <= 1.0001; } diff --git a/src/types/index.js b/src/types/index.js new file mode 100644 index 000000000..f5896a685 --- /dev/null +++ b/src/types/index.js @@ -0,0 +1,39 @@ +import {defaults} from 'chart.js-v3'; +import BoxAnnotation from './box'; +import LineAnnotation from './line'; +import EllipseAnnotation from './ellipse'; +import LabelAnnotation from './label'; +import PointAnnotation from './point'; +import PolygonAnnotation from './polygon'; + +export const annotationTypes = { + box: BoxAnnotation, + ellipse: EllipseAnnotation, + label: LabelAnnotation, + line: LineAnnotation, + point: PointAnnotation, + polygon: PolygonAnnotation +}; + +export { + BoxAnnotation, + LineAnnotation, + EllipseAnnotation, + LabelAnnotation, + PointAnnotation, + PolygonAnnotation +}; + +/** + * Register fallback for annotation elements + * For example lineAnnotation options would be looked through: + * - the annotation object (options.plugins.annotation.annotations[id]) + * - element options (options.elements.lineAnnotation) + * - element defaults (defaults.elements.lineAnnotation) + * - annotation plugin defaults (defaults.plugins.annotation, this is what we are registering here) + */ +Object.keys(annotationTypes).forEach(key => { + defaults.describe(`elements.${annotationTypes[key].id}`, { + _fallback: 'plugins.annotation' + }); +}); diff --git a/src/types/label.js b/src/types/label.js new file mode 100644 index 000000000..ea1280350 --- /dev/null +++ b/src/types/label.js @@ -0,0 +1,225 @@ +import {drawBox, drawLabel, measureLabelSize, getChartPoint, getRectCenterPoint, toPosition, setBorderStyle, getSize, inBoxRange, isBoundToPoint, getChartRect, getRelativePosition} from '../helpers'; +import ChartJsV3, {Element} from 'chart.js-v3'; +const {color, toPadding} = ChartJsV3.helpers; + +export default class LabelAnnotation extends Element { + + inRange(mouseX, mouseY, useFinalPosition) { + return inBoxRange(mouseX, mouseY, this.getProps(['x', 'y', 'width', 'height'], useFinalPosition), this.options.borderWidth); + } + + getCenterPoint(useFinalPosition) { + return getRectCenterPoint(this.getProps(['x', 'y', 'width', 'height'], useFinalPosition)); + } + + draw(ctx) { + if (!this.options.content) { + return; + } + const {labelX, labelY, labelWidth, labelHeight, options} = this; + drawCallout(ctx, this); + if (this.boxVisible) { + drawBox(ctx, this, options); + } + drawLabel(ctx, {x: labelX, y: labelY, width: labelWidth, height: labelHeight}, options); + } + + // TODO: make private in v2 + resolveElementProperties(chart, options) { + const point = !isBoundToPoint(options) ? getRectCenterPoint(getChartRect(chart, options)) : getChartPoint(chart, options); + const padding = toPadding(options.padding); + const labelSize = measureLabelSize(chart.ctx, options); + const boxSize = measureRect(point, labelSize, options, padding); + const bgColor = color(options.backgroundColor); + const boxVisible = options.borderWidth > 0 || (bgColor && bgColor.valid && bgColor.rgb.a > 0); + + const properties = { + boxVisible, + pointX: point.x, + pointY: point.y, + ...boxSize, + labelX: boxSize.x + padding.left + (options.borderWidth / 2), + labelY: boxSize.y + padding.top + (options.borderWidth / 2), + labelWidth: labelSize.width, + labelHeight: labelSize.height + }; + properties.calloutPosition = options.callout.enabled && resolveCalloutPosition(properties, options.callout); + return properties; + } +} + +LabelAnnotation.id = 'labelAnnotation'; + +LabelAnnotation.defaults = { + adjustScaleRange: true, + backgroundColor: 'transparent', + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 0, + borderShadowColor: 'transparent', + borderWidth: 0, + callout: { + borderCapStyle: 'butt', + borderColor: undefined, + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 1, + enabled: false, + margin: 5, + position: 'auto', + side: 5, + start: '50%', + }, + color: 'black', + content: null, + display: true, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: undefined + }, + height: undefined, + padding: 6, + position: 'center', + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + width: undefined, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: 'x', + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: 'y', + yValue: undefined +}; + +LabelAnnotation.defaultRoutes = { + borderColor: 'color' +}; + +function measureRect(point, size, options, padding) { + const width = size.width + padding.width + options.borderWidth; + const height = size.height + padding.height + options.borderWidth; + const position = toPosition(options.position); + + return { + x: calculatePosition(point.x, width, options.xAdjust, position.x), + y: calculatePosition(point.y, height, options.yAdjust, position.y), + width, + height + }; +} + +function calculatePosition(start, size, adjust = 0, position) { + return start - getRelativePosition(size, position) + adjust; +} + +function drawCallout(ctx, element) { + const {pointX, pointY, calloutPosition, options} = element; + if (!calloutPosition) { + return; + } + const callout = options.callout; + + ctx.save(); + ctx.beginPath(); + const stroke = setBorderStyle(ctx, callout); + if (!stroke) { + return ctx.restore(); + } + const {separatorStart, separatorEnd} = getCalloutSeparatorCoord(element, calloutPosition); + const {sideStart, sideEnd} = getCalloutSideCoord(element, calloutPosition, separatorStart); + if (callout.margin > 0 || options.borderWidth === 0) { + ctx.moveTo(separatorStart.x, separatorStart.y); + ctx.lineTo(separatorEnd.x, separatorEnd.y); + } + ctx.moveTo(sideStart.x, sideStart.y); + ctx.lineTo(sideEnd.x, sideEnd.y); + ctx.lineTo(pointX, pointY); + ctx.stroke(); + ctx.restore(); +} + +function getCalloutSeparatorCoord(element, position) { + const {x, y, width, height} = element; + const adjust = getCalloutSeparatorAdjust(element, position); + let separatorStart, separatorEnd; + if (position === 'left' || position === 'right') { + separatorStart = {x: x + adjust, y}; + separatorEnd = {x: separatorStart.x, y: separatorStart.y + height}; + } else { + // position 'top' or 'bottom' + separatorStart = {x, y: y + adjust}; + separatorEnd = {x: separatorStart.x + width, y: separatorStart.y}; + } + return {separatorStart, separatorEnd}; +} + +function getCalloutSeparatorAdjust(element, position) { + const {width, height, options} = element; + const adjust = options.callout.margin + options.borderWidth / 2; + if (position === 'right') { + return width + adjust; + } else if (position === 'bottom') { + return height + adjust; + } + return -adjust; +} + +function getCalloutSideCoord(element, position, separatorStart) { + const {y, width, height, options} = element; + const start = options.callout.start; + const side = getCalloutSideAdjust(position, options.callout); + let sideStart, sideEnd; + if (position === 'left' || position === 'right') { + sideStart = {x: separatorStart.x, y: y + getSize(height, start)}; + sideEnd = {x: sideStart.x + side, y: sideStart.y}; + } else { + // position 'top' or 'bottom' + sideStart = {x: separatorStart.x + getSize(width, start), y: separatorStart.y}; + sideEnd = {x: sideStart.x, y: sideStart.y + side}; + } + return {sideStart, sideEnd}; +} + +function getCalloutSideAdjust(position, options) { + const side = options.side; + if (position === 'left' || position === 'top') { + return -side; + } + return side; +} + +function resolveCalloutPosition(element, options) { + const position = options.position; + if (position === 'left' || position === 'right' || position === 'top' || position === 'bottom') { + return position; + } + return resolveCalloutAutoPosition(element, options); +} + +function resolveCalloutAutoPosition(element, options) { + const {x, y, width, height, pointX, pointY} = element; + const {margin, side} = options; + const adjust = margin + side; + if (pointX < (x - adjust)) { + return 'left'; + } else if (pointX > (x + width + adjust)) { + return 'right'; + } else if (pointY < (y - adjust)) { + return 'top'; + } else if (pointY > (y + height + adjust)) { + return 'bottom'; + } +} diff --git a/src/types/line.js b/src/types/line.js index 7ef18f2e6..692397abc 100644 --- a/src/types/line.js +++ b/src/types/line.js @@ -1,14 +1,11 @@ import ChartJsV3, {Element} from 'chart.js-v3'; -const {addRoundedRectPath, isArray, toFontString, toRadians, toTRBLCorners, valueOrDefault} = ChartJsV3.helpers; -import {clamp, clampAll, scaleValue, rotated} from '../helpers'; +const {PI, toRadians, toPadding} = ChartJsV3.helpers; +import {clamp, scaleValue, rotated, drawBox, drawLabel, measureLabelSize, getRelativePosition, setBorderStyle, setShadowStyle} from '../helpers'; -import {Image as CanvasImage} from 'canvas'; - -const PI = Math.PI; 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; const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p2.x - p1.x))).y; -const toPercent = (s) => typeof s === 'string' && s.endsWith('%') && parseFloat(s) / 100; +const sqr = v => v * v; function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) { return !( @@ -46,10 +43,11 @@ function limitLineToArea(p1, p2, area) { } export default class LineAnnotation extends Element { - intersects(x, y, epsilon = 0.001) { + + // TODO: make private in v2 + intersects(x, y, epsilon = 0.001, useFinalPosition) { // Adapted from https://stackoverflow.com/a/6853926/25507 - const sqr = v => v * v; - const {x: x1, y: y1, x2, y2} = this; + const {x: x1, y: y1, x2, y2} = this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); const dx = x2 - x1; const dy = y2 - y1; const lenSq = sqr(dx) + sqr(dy); @@ -68,29 +66,37 @@ export default class LineAnnotation extends Element { return (sqr(x - xx) + sqr(y - yy)) < epsilon; } - labelIsVisible(chartArea) { - const label = this.options.label; - - const inside = !chartArea || isLineInArea(this, chartArea); - return inside && label && label.enabled && label.content; + /** + * @todo make private in v2 + * @param {boolean} useFinalPosition - use the element's animation target instead of current position + * @param {top, right, bottom, left} [chartArea] - optional, area of the chart + * @returns {boolean} true if the label is visible + */ + labelIsVisible(useFinalPosition, chartArea) { + const labelOpts = this.options.label; + if (!labelOpts || !labelOpts.enabled) { + return false; + } + return !chartArea || isLineInArea(this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), chartArea); } - isOnLabel(mouseX, mouseY) { - const {labelRect} = this; - if (!labelRect || !this.labelIsVisible()) { + // TODO: make private in v2 + isOnLabel(mouseX, mouseY, useFinalPosition) { + if (!this.labelIsVisible(useFinalPosition)) { return false; } - - const {x, y} = rotated({x: mouseX, y: mouseY}, labelRect, -labelRect.rotation); - const w2 = labelRect.width / 2; - const h2 = labelRect.height / 2; - return x >= labelRect.x - w2 && x <= labelRect.x + w2 && - y >= labelRect.y - h2 && y <= labelRect.y + h2; + const {labelX, labelY, labelWidth, labelHeight, labelRotation} = this.getProps(['labelX', 'labelY', 'labelWidth', 'labelHeight', 'labelRotation'], useFinalPosition); + const {x, y} = rotated({x: mouseX, y: mouseY}, {x: labelX, y: labelY}, -labelRotation); + const hBorderWidth = this.options.label.borderWidth / 2 || 0; + const w2 = labelWidth / 2 + hBorderWidth; + const h2 = labelHeight / 2 + hBorderWidth; + return x >= labelX - w2 && x <= labelX + w2 && + y >= labelY - h2 && y <= labelY + h2; } - inRange(x, y) { - const epsilon = this.options.borderWidth || 1; - return this.intersects(x, y, epsilon) || this.isOnLabel(x, y); + inRange(mouseX, mouseY, useFinalPosition) { + const epsilon = sqr(this.options.borderWidth / 2); + return this.intersects(mouseX, mouseY, epsilon, useFinalPosition) || this.isOnLabel(mouseX, mouseY, useFinalPosition); } getCenterPoint() { @@ -102,28 +108,55 @@ export default class LineAnnotation extends Element { draw(ctx) { const {x, y, x2, y2, options} = this; - ctx.save(); - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; + ctx.save(); + if (!setBorderStyle(ctx, options)) { + // no border width, then line is not drawn + return ctx.restore(); + } + setShadowStyle(ctx, options); + const angle = Math.atan2(y2 - y, x2 - x); + const length = Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)); + const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(this); - // Draw + ctx.translate(x, y); + ctx.rotate(angle); ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x2, y2); + ctx.moveTo(0 + startAdjust, 0); + ctx.lineTo(length - endAdjust, 0); + ctx.shadowColor = options.borderShadowColor; ctx.stroke(); - + drawArrowHead(ctx, 0, startAdjust, startOpts); + drawArrowHead(ctx, length, -endAdjust, endOpts); ctx.restore(); } drawLabel(ctx, chartArea) { - if (this.labelIsVisible(chartArea)) { - ctx.save(); - drawLabel(ctx, this, chartArea); - ctx.restore(); + if (!this.labelIsVisible(false, chartArea)) { + return; } + const {labelX, labelY, labelWidth, labelHeight, labelRotation, labelPadding, labelTextSize, options: {label}} = this; + + ctx.save(); + ctx.translate(labelX, labelY); + ctx.rotate(labelRotation); + + const boxRect = { + x: -(labelWidth / 2), + y: -(labelHeight / 2), + width: labelWidth, + height: labelHeight + }; + drawBox(ctx, boxRect, label); + + const labelTextRect = { + x: -(labelWidth / 2) + labelPadding.left + label.borderWidth / 2, + y: -(labelHeight / 2) + labelPadding.top + label.borderWidth / 2, + width: labelTextSize.width, + height: labelTextSize.height + }; + drawLabel(ctx, labelTextRect, label); + ctx.restore(); } resolveElementProperties(chart, options) { @@ -156,62 +189,137 @@ export default class LineAnnotation extends Element { } } const inside = isLineInArea({x, y, x2, y2}, chart.chartArea); - return inside + const properties = inside ? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea) : {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; + + const label = options.label; + if (label && label.content) { + return loadLabelRect(properties, chart, label); + } + return properties; } } LineAnnotation.id = 'lineAnnotation'; + +const arrowHeadsDefaults = { + backgroundColor: undefined, + backgroundShadowColor: undefined, + borderColor: undefined, + borderDash: undefined, + borderDashOffset: undefined, + borderShadowColor: undefined, + borderWidth: undefined, + enabled: undefined, + fill: undefined, + length: undefined, + shadowBlur: undefined, + shadowOffsetX: undefined, + shadowOffsetY: undefined, + width: undefined +}; + LineAnnotation.defaults = { - display: true, adjustScaleRange: true, - borderWidth: 2, + arrowHeads: { + enabled: false, + end: Object.assign({}, arrowHeadsDefaults), + fill: false, + length: 12, + start: Object.assign({}, arrowHeadsDefaults), + width: 6 + }, borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 2, + display: true, + endValue: undefined, label: { backgroundColor: 'rgba(0,0,0,0.8)', + backgroundShadowColor: 'transparent', borderCapStyle: 'butt', borderColor: 'black', borderDash: [], borderDashOffset: 0, borderJoinStyle: 'miter', borderRadius: 6, + borderShadowColor: 'transparent', borderWidth: 0, + color: '#fff', + content: null, + cornerRadius: undefined, // TODO: v2 remove support for cornerRadius drawTime: undefined, + enabled: false, font: { family: undefined, lineHeight: undefined, size: undefined, - style: 'bold', - weight: undefined + style: undefined, + weight: 'bold' }, - color: '#fff', - xPadding: 6, - yPadding: 6, - rotation: 0, + height: undefined, + padding: 6, position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + width: undefined, xAdjust: 0, + xPadding: undefined, // TODO: v2 remove support for xPadding yAdjust: 0, - textAlign: 'center', - enabled: false, - content: null + yPadding: undefined, // TODO: v2 remove support for yPadding }, - value: undefined, - endValue: undefined, scaleID: undefined, - xScaleID: 'x', - xMin: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + value: undefined, xMax: undefined, - yScaleID: 'y', + xMin: undefined, + xScaleID: 'x', + yMax: undefined, yMin: undefined, - yMax: undefined + yScaleID: 'y' +}; + +LineAnnotation.descriptors = { + arrowHeads: { + start: { + _fallback: true + }, + end: { + _fallback: true + }, + _fallback: true + } }; LineAnnotation.defaultRoutes = { borderColor: 'color' }; +function loadLabelRect(line, chart, options) { + // TODO: v2 remove support for xPadding and yPadding + const {padding: lblPadding, xPadding, yPadding, borderWidth} = options; + const padding = getPadding(lblPadding, xPadding, yPadding); + const textSize = measureLabelSize(chart.ctx, options); + const width = textSize.width + padding.width + borderWidth; + const height = textSize.height + padding.height + borderWidth; + const labelRect = calculateLabelPosition(line, options, {width, height, padding}, chart.chartArea); + line.labelX = labelRect.x; + line.labelY = labelRect.y; + line.labelWidth = labelRect.width; + line.labelHeight = labelRect.height; + line.labelRotation = labelRect.rotation; + line.labelPadding = padding; + line.labelTextSize = textSize; + return line; +} + function calculateAutoRotation(line) { const {x, y, x2, y2} = line; const rotation = Math.atan2(y2 - y, x2 - x); @@ -219,126 +327,26 @@ function calculateAutoRotation(line) { return rotation > PI / 2 ? rotation - PI : rotation < PI / -2 ? rotation + PI : rotation; } -function drawLabel(ctx, line, chartArea) { - const label = line.options.label; - - ctx.font = toFontString(label.font); - - const {width, height} = measureLabel(ctx, label); - const rect = line.labelRect = calculateLabelPosition(line, width, height, chartArea); - - ctx.translate(rect.x, rect.y); - ctx.rotate(rect.rotation); - - ctx.fillStyle = label.backgroundColor; - const stroke = setBorderStyle(ctx, label); - - ctx.beginPath(); - addRoundedRectPath(ctx, { - x: -(width / 2), y: -(height / 2), w: width, h: height, - // TODO: v2 remove support for cornerRadius - radius: clampAll(toTRBLCorners(valueOrDefault(label.cornerRadius, label.borderRadius)), 0, Math.min(width, height) / 2) - }); - ctx.closePath(); - ctx.fill(); - if (stroke) { - ctx.stroke(); - } - - ctx.fillStyle = label.color; - if (isArray(label.content)) { - ctx.textAlign = label.textAlign; - const x = calculateLabelXAlignment(label, width); - let textYPosition = -(height / 2) + label.yPadding; - for (let i = 0; i < label.content.length; i++) { - ctx.textBaseline = 'top'; - ctx.fillText( - label.content[i], - x, - textYPosition - ); - textYPosition += label.font.size + label.yPadding; - } - } else if (label.content instanceof CanvasImage) { - const x = -(width / 2) + label.xPadding; - const y = -(height / 2) + label.yPadding; - ctx.drawImage(label.content, x, y, width - (2 * label.xPadding), height - (2 * label.yPadding)); - } else { - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(label.content, 0, 0); +// TODO: v2 remove support for xPadding and yPadding +function getPadding(padding, xPadding, yPadding) { + let tempPadding = padding; + if (xPadding || yPadding) { + tempPadding = {x: xPadding || 6, y: yPadding || 6}; } + return toPadding(tempPadding); } -function setBorderStyle(ctx, options) { - if (options.borderWidth) { - ctx.lineCap = options.borderCapStyle; - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; - ctx.lineJoin = options.borderJoinStyle; - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; - return true; - } -} - -function calculateLabelXAlignment(label, width) { - const {textAlign, xPadding} = label; - if (textAlign === 'start') { - return -(width / 2) + xPadding; - } else if (textAlign === 'end') { - return +(width / 2) - xPadding; - } - return 0; -} - -function getImageSize(size, value) { - if (typeof value === 'number') { - return value; - } else if (typeof value === 'string') { - return toPercent(value) * size; - } - return size; -} - -const widthCache = new Map(); -function measureLabel(ctx, label) { - const content = label.content; - if (content instanceof CanvasImage) { - return { - width: getImageSize(content.width, label.width) + 2 * label.xPadding, - height: getImageSize(content.height, label.height) + 2 * label.yPadding - }; - } - const lines = isArray(content) ? content : [content]; - const count = lines.length; - let width = 0; - for (let i = 0; i < count; i++) { - const text = lines[i]; - if (!widthCache.has(text)) { - widthCache.set(text, ctx.measureText(text).width); - } - width = Math.max(width, widthCache.get(text)); - } - width += 2 * label.xPadding; - - return { - width, - height: count * label.font.size + ((count + 1) * label.yPadding) - }; -} - -function calculateLabelPosition(line, width, height, chartArea) { - const label = line.options.label; - const {xAdjust, yAdjust, xPadding, yPadding, position} = label; +function calculateLabelPosition(line, label, sizes, chartArea) { + const {width, height, padding} = sizes; + const {xAdjust, yAdjust} = label; const p1 = {x: line.x, y: line.y}; const p2 = {x: line.x2, y: line.y2}; const rotation = label.rotation === 'auto' ? calculateAutoRotation(line) : toRadians(label.rotation); const size = rotatedSize(width, height, rotation); - const t = calculateT(line, position, size, chartArea); + const t = calculateT(line, label, {labelSize: size, padding}, chartArea); const pt = pointInLine(p1, p2, t); - const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: xPadding}; - const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: yPadding}; + const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: padding.left}; + const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: padding.top}; return { x: adjustLabelCoordinate(pt.x, xCoordinateSizes) + xAdjust, @@ -358,24 +366,25 @@ function rotatedSize(width, height, rotation) { }; } -function calculateT(line, position, rotSize, chartArea) { - let t = 0.5; +function calculateT(line, label, sizes, chartArea) { + let t; const space = spaceAround(line, chartArea); - const label = line.options.label; - if (position === 'start') { - t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, rotSize, label, space); - } else if (position === 'end') { - t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, rotSize, label, space); + if (label.position === 'start') { + t = calculateTAdjust({w: line.x2 - line.x, h: line.y2 - line.y}, sizes, label, space); + } else if (label.position === 'end') { + t = 1 - calculateTAdjust({w: line.x - line.x2, h: line.y - line.y2}, sizes, label, space); + } else { + t = getRelativePosition(1, label.position); } return t; } -function calculateTAdjust(lineSize, labelSize, label, space) { - const {xPadding, yPadding} = label; +function calculateTAdjust(lineSize, sizes, label, space) { + const {labelSize, padding} = sizes; const lineW = lineSize.w * space.dx; const lineH = lineSize.h * space.dy; - const x = (lineW > 0) && ((labelSize.w / 2 + xPadding - space.x) / lineW); - const y = (lineH > 0) && ((labelSize.h / 2 + yPadding - space.y) / lineH); + const x = (lineW > 0) && ((labelSize.w / 2 + padding.left - space.x) / lineW); + const y = (lineH > 0) && ((labelSize.h / 2 + padding.top - space.y) / lineH); return clamp(Math.max(x, y), 0, 0.25); } @@ -388,27 +397,69 @@ function spaceAround(line, chartArea) { return { x: Math.min(l, r), y: Math.min(t, b), - dx: l < r ? 1 : -1, - dy: t < b ? 1 : -1 + dx: l <= r ? 1 : -1, + dy: t <= b ? 1 : -1 }; } function adjustLabelCoordinate(coordinate, labelSizes) { const {size, min, max, padding} = labelSizes; const halfSize = size / 2; - if (size > max - min) { // if it does not fit, display as much as possible return (max + min) / 2; } - if (min >= (coordinate - padding - halfSize)) { coordinate = min + padding + halfSize; } - if (max <= (coordinate + padding + halfSize)) { coordinate = max - padding - halfSize; } - return coordinate; } + +function getArrowHeads(line) { + const options = line.options; + const arrowStartOpts = options.arrowHeads && options.arrowHeads.start; + const arrowEndOpts = options.arrowHeads && options.arrowHeads.end; + return { + startOpts: arrowStartOpts, + endOpts: arrowEndOpts, + startAdjust: getLineAdjust(line, arrowStartOpts), + endAdjust: getLineAdjust(line, arrowEndOpts) + }; +} + +function getLineAdjust(line, arrowOpts) { + if (!arrowOpts || !arrowOpts.enabled) { + return 0; + } + const {length, width} = arrowOpts; + const adjust = line.options.borderWidth / 2; + const p1 = {x: length, y: width + adjust}; + const p2 = {x: 0, y: adjust}; + return Math.abs(interpolateX(0, p1, p2)); +} + +function drawArrowHead(ctx, offset, adjust, arrowOpts) { + if (!arrowOpts || !arrowOpts.enabled) { + return; + } + const {length, width, fill, backgroundColor, borderColor} = arrowOpts; + const arrowOffsetX = Math.abs(offset - length) + adjust; + ctx.beginPath(); + setShadowStyle(ctx, arrowOpts); + setBorderStyle(ctx, arrowOpts); + ctx.moveTo(arrowOffsetX, -width); + ctx.lineTo(offset + adjust, 0); + ctx.lineTo(arrowOffsetX, width); + if (fill === true) { + ctx.fillStyle = backgroundColor || borderColor; + ctx.closePath(); + ctx.fill(); + ctx.shadowColor = 'transparent'; + } else { + ctx.shadowColor = arrowOpts.borderShadowColor; + } + ctx.stroke(); +} diff --git a/src/types/point.js b/src/types/point.js index cb1fae860..08102f406 100644 --- a/src/types/point.js +++ b/src/types/point.js @@ -1,80 +1,67 @@ -import {Element} from 'chart.js-v3'; -import {scaleValue} from '../helpers'; +import ChartJsV3, {Element} from 'chart.js-v3'; +const {drawPoint} = ChartJsV3.helpers; +import {inPointRange, getElementCenterPoint, resolvePointPosition, setBorderStyle, setShadowStyle, isImageOrCanvas} from '../helpers'; export default class PointAnnotation extends Element { - inRange(x, y) { - const {width, options} = this; - const center = this.getCenterPoint(true); - const radius = width / 2 + options.borderWidth; - - if (radius <= 0) { - return false; - } - - return (Math.pow(x - center.x, 2) + Math.pow(y - center.y, 2)) <= Math.pow(radius, 2); + inRange(mouseX, mouseY, useFinalPosition) { + const {width} = this.getProps(['width'], useFinalPosition); + return inPointRange({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), width / 2, this.options.borderWidth); } getCenterPoint(useFinalPosition) { - const {x, y} = this.getProps(['x', 'y'], useFinalPosition); - return {x, y}; + return getElementCenterPoint(this, useFinalPosition); } draw(ctx) { - const {x, y, width, options} = this; - + const options = this.options; + const borderWidth = options.borderWidth; + if (options.radius < 0.1) { + return; + } ctx.save(); - - ctx.lineWidth = options.borderWidth; - ctx.strokeStyle = options.borderColor; ctx.fillStyle = options.backgroundColor; - - ctx.setLineDash(options.borderDash); - ctx.lineDashOffset = options.borderDashOffset; - - ctx.beginPath(); - ctx.arc(x, y, width / 2, 0, Math.PI * 2); - ctx.fill(); - ctx.stroke(); - + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + options.borderWidth = 0; + drawPoint(ctx, options, this.x, this.y); + if (stroke && !isImageOrCanvas(options.pointStyle)) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } ctx.restore(); + options.borderWidth = borderWidth; } resolveElementProperties(chart, options) { - const {chartArea, scales} = chart; - const xScale = scales[options.xScaleID]; - const yScale = scales[options.yScaleID]; - let x = chartArea.width / 2; - let y = chartArea.height / 2; - - if (xScale) { - x = scaleValue(xScale, options.xValue, x); - } - - if (yScale) { - y = scaleValue(yScale, options.yValue, y); - } - - return { - x, - y, - width: options.radius * 2, - height: options.radius * 2 - }; + return resolvePointPosition(chart, options); } } PointAnnotation.id = 'pointAnnotation'; PointAnnotation.defaults = { - display: true, adjustScaleRange: true, + backgroundShadowColor: 'transparent', borderDash: [], borderDashOffset: 0, + borderShadowColor: 'transparent', borderWidth: 1, + display: true, + pointStyle: 'circle', radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xAdjust: 0, + xMax: undefined, + xMin: undefined, xScaleID: 'x', xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, yScaleID: 'y', yValue: undefined }; diff --git a/src/types/polygon.js b/src/types/polygon.js new file mode 100644 index 000000000..f2d657e27 --- /dev/null +++ b/src/types/polygon.js @@ -0,0 +1,115 @@ +import ChartJsV3, {Element} from 'chart.js-v3'; +const {PI, RAD_PER_DEG} = ChartJsV3.helpers; +import {setBorderStyle, resolvePointPosition, getElementCenterPoint, setShadowStyle} from '../helpers'; + +export default class PolygonAnnotation extends Element { + inRange(mouseX, mouseY, useFinalPosition) { + return this.options.radius >= 0.1 && this.elements.length > 1 && pointIsInPolygon(this.elements, mouseX, mouseY, useFinalPosition); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {elements, options} = this; + ctx.save(); + ctx.beginPath(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + let first = true; + for (const el of elements) { + if (first) { + ctx.moveTo(el.x, el.y); + first = false; + } else { + ctx.lineTo(el.x, el.y); + } + } + ctx.closePath(); + ctx.fill(); + // If no border, don't draw it + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + } + + resolveElementProperties(chart, options) { + const {x, y, width, height} = resolvePointPosition(chart, options); + const {sides, radius, rotation, borderWidth} = options; + const halfBorder = borderWidth / 2; + const elements = []; + const angle = (2 * PI) / sides; + let rad = rotation * RAD_PER_DEG; + for (let i = 0; i < sides; i++, rad += angle) { + const sin = Math.sin(rad); + const cos = Math.cos(rad); + elements.push({ + type: 'point', + optionScope: 'point', + properties: { + x: x + sin * radius, + y: y - cos * radius, + bX: x + sin * (radius + halfBorder), + bY: y - cos * (radius + halfBorder) + } + }); + } + return {x, y, width, height, elements, initProperties: {x, y}}; + } +} + +PolygonAnnotation.id = 'polygonAnnotation'; + +PolygonAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + point: { + radius: 0 + }, + radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + sides: 3, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: 'x', + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: 'y', + yValue: undefined +}; + +PolygonAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + + +function pointIsInPolygon(points, x, y, useFinalPosition) { + let isInside = false; + let A = points[points.length - 1].getProps(['bX', 'bY'], useFinalPosition); + for (const point of points) { + const B = point.getProps(['bX', 'bY'], useFinalPosition); + if ((B.bY > y) !== (A.bY > y) && x < (A.bX - B.bX) * (y - B.bY) / (A.bY - B.bY) + B.bX) { + isInside = !isInside; + } + A = B; + } + return isInside; +} diff --git a/test/events.js b/test/events.js new file mode 100644 index 000000000..dadc3dc2f --- /dev/null +++ b/test/events.js @@ -0,0 +1,192 @@ +const getCenterPoint = (xScale, yScale, element) => element.getCenterPoint(); + +export function testEvents(options, eventIn, eventOut) { + testEnterEvent(options, 1, eventIn); + testLeaveEvent(options, 1, eventOut); + testClickEvent(options, 1, eventIn); + testEnterEvent(options, 0, eventOut); + testLeaveEvent(options, 0, eventIn); + testClickEvent(options, 0, eventOut); +} + +function testEnterEvent(options, toBe, getEventPoint) { + const context = getTestCaseContext(toBe); + + describe('events', function() { + const pluginOpts = context.chartConfig.options.plugins.annotation; + + [pluginOpts, options].forEach(function(targetOptions) { + + it(`${context.description} detect enter event`, function(done) { + const enterSpy = jasmine.createSpy('enter'); + + targetOptions.enter = enterSpy; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(context.chartConfig); + const eventPoint = retrieveEventPoint(chart, getEventPoint); + + window.triggerMouseEvent(chart, 'mousemove', eventPoint); + window.afterEvent(chart, 'mousemove', function() { + expect(enterSpy.calls.count()).toBe(context.compare); + delete targetOptions.enter; + done(); + }); + }); + }); + }); +} + +function testLeaveEvent(options, toBe, getEventPoint) { + const context = getTestCaseContext(toBe); + + describe('events', function() { + const pluginOpts = context.chartConfig.options.plugins.annotation; + + [pluginOpts, options].forEach(function(targetOptions) { + + it(`${context.description} detect leave event`, function(done) { + const enterSpy = jasmine.createSpy('enter'); + const leaveSpy = jasmine.createSpy('leave'); + + targetOptions.enter = enterSpy; + targetOptions.leave = leaveSpy; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(context.chartConfig); + const eventPoint = retrieveEventPoint(chart, getCenterPoint); + + window.triggerMouseEvent(chart, 'mousemove', eventPoint); + window.afterEvent(chart, 'mousemove', function() { + expect(enterSpy.calls.count()).toBe(1); + + window.triggerMouseEvent(chart, 'mousemove', retrieveEventPoint(chart, getEventPoint)); + + window.afterEvent(chart, 'mousemove', function() { + expect(leaveSpy.calls.count()).toBe(context.compare); + delete targetOptions.enter; + delete targetOptions.leave; + done(); + }); + }); + }); + + it(`${context.description} detect leave (by mouseout) events`, function(done) { + const enterSpy = jasmine.createSpy('enter'); + const leaveSpy = jasmine.createSpy('leave'); + + targetOptions.enter = enterSpy; + targetOptions.leave = leaveSpy; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(context.chartConfig); + const eventPoint = retrieveEventPoint(chart, getCenterPoint); + + window.triggerMouseEvent(chart, 'mousemove', eventPoint); + window.afterEvent(chart, 'mousemove', function() { + expect(enterSpy.calls.count()).toBe(1); + + window.triggerMouseEvent(chart, 'mouseout', retrieveEventPoint(chart, getEventPoint)); + + window.afterEvent(chart, 'mouseout', function() { + expect(leaveSpy.calls.count()).toBe(1); + delete targetOptions.enter; + delete targetOptions.leave; + done(); + }); + }); + }); + }); + }); +} + +function testClickEvent(options, toBe, getEventPoint) { + const context = getTestCaseContext(toBe); + + describe('events', function() { + const pluginOpts = context.chartConfig.options.plugins.annotation; + + [pluginOpts, options].forEach(function(targetOptions) { + + it(`${context.description} detect click event`, function(done) { + const clickSpy = jasmine.createSpy('click'); + + targetOptions.click = clickSpy; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(context.chartConfig); + const eventPoint = retrieveEventPoint(chart, getEventPoint); + + window.afterEvent(chart, 'click', function() { + expect(clickSpy.calls.count()).toBe(context.compare); + delete targetOptions.click; + done(); + }); + window.triggerMouseEvent(chart, 'click', eventPoint); + }); + + it(`${context.description} detect dbl click event`, function(done) { + const dblClickSpy = jasmine.createSpy('dblclick'); + + targetOptions.dblclick = dblClickSpy; + pluginOpts.dblClickSpeed = 1000; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(context.chartConfig); + const eventPoint = retrieveEventPoint(chart, getEventPoint); + + let dblClick = false; + window.afterEvent(chart, 'click', function() { + if (!dblClick) { + dblClick = true; + window.triggerMouseEvent(chart, 'click', eventPoint); + } else { + expect(dblClickSpy.calls.count()).toBe(context.compare); + delete targetOptions.dblclick; + delete pluginOpts.dblClickSpeed; + done(); + } + }); + window.triggerMouseEvent(chart, 'click', eventPoint); + }); + }); + }); +} + +function retrieveEventPoint(chart, getEventPoint) { + const xScale = chart.scales.x; + const yScale = chart.scales.y; + return getEventPoint(xScale, yScale, window.getAnnotationElements(chart)[0]); +} + +function getTestCaseContext(toBe) { + const chartConfig = { + type: 'scatter', + options: { + animation: false, + scales: { + x: { + display: false, + min: 0, + max: 10 + }, + y: { + display: false, + min: 0, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + } + } + }, + }; + return { + description: toBe ? 'should' : 'should not', + compare: toBe, + chartConfig + }; + +} diff --git a/test/fixtures/box/adjustScaleFalse.js b/test/fixtures/box/adjustScaleFalse.js new file mode 100644 index 000000000..ef7ba5616 --- /dev/null +++ b/test/fixtures/box/adjustScaleFalse.js @@ -0,0 +1,53 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + data: { + datasets: [{ + data: [0, 5, 10, 15, 20, 22] + }] + }, + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: true + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + box1: { + type: 'box', + adjustScaleRange: false, + xMin: 1, + xMax: 3, + yMin: -2.3, + yMax: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 2 + }, + box2: { + type: 'box', + adjustScaleRange: false, + xMin: 4, + xMax: 6, + yMin: 10, + yMax: 27, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 2 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/adjustScaleFalse.png b/test/fixtures/box/adjustScaleFalse.png new file mode 100644 index 000000000..019bdd215 Binary files /dev/null and b/test/fixtures/box/adjustScaleFalse.png differ diff --git a/test/fixtures/box/adjustScaleIgnore.js b/test/fixtures/box/adjustScaleIgnore.js new file mode 100644 index 000000000..98871ce06 --- /dev/null +++ b/test/fixtures/box/adjustScaleIgnore.js @@ -0,0 +1,53 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + data: { + datasets: [{ + data: [0, 5, 10, 15, 20, 22] + }] + }, + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: true, + min: 0, + max: 25 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + box1: { + type: 'box', + xMin: 1, + xMax: 3, + yMin: -2.3, + yMax: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 2 + }, + box2: { + type: 'box', + xMin: 4, + xMax: 6, + yMin: 10, + yMax: 27, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 2 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/adjustScaleIgnore.png b/test/fixtures/box/adjustScaleIgnore.png new file mode 100644 index 000000000..019bdd215 Binary files /dev/null and b/test/fixtures/box/adjustScaleIgnore.png differ diff --git a/test/fixtures/box/adjustScaleMax.js b/test/fixtures/box/adjustScaleMax.js new file mode 100644 index 000000000..5bc209992 --- /dev/null +++ b/test/fixtures/box/adjustScaleMax.js @@ -0,0 +1,42 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + data: { + datasets: [{ + data: [0, 5, 10, 15, 20, 21] + }] + }, + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: true, + min: 0 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + box: { + type: 'box', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 27, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 2 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/adjustScaleMax.png b/test/fixtures/box/adjustScaleMax.png new file mode 100644 index 000000000..4853b0c6b Binary files /dev/null and b/test/fixtures/box/adjustScaleMax.png differ diff --git a/test/fixtures/box/adjustScaleMin.js b/test/fixtures/box/adjustScaleMin.js new file mode 100644 index 000000000..76b09b946 --- /dev/null +++ b/test/fixtures/box/adjustScaleMin.js @@ -0,0 +1,42 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + data: { + datasets: [{ + data: [0, 5, 10, 15, 20, 22] + }] + }, + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: true, + max: 30 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + box: { + type: 'box', + xMin: 1.5, + xMax: 3.5, + yMin: -2.3, + yMax: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 2 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/adjustScaleMin.png b/test/fixtures/box/adjustScaleMin.png new file mode 100644 index 000000000..fca1a0535 Binary files /dev/null and b/test/fixtures/box/adjustScaleMin.png differ diff --git a/test/fixtures/box/borderDash.js b/test/fixtures/box/borderDash.js index f7343f26f..ffd73622a 100644 --- a/test/fixtures/box/borderDash.js +++ b/test/fixtures/box/borderDash.js @@ -18,8 +18,6 @@ module.exports = { annotations: { box1: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 1.5, xMax: 3.5, yMin: 5, @@ -31,8 +29,6 @@ module.exports = { }, box2: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 'May', xMax: 'July', yMin: 11, @@ -44,8 +40,6 @@ module.exports = { }, box3: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: -0.5, xMax: 'May', yMin: 16, diff --git a/test/fixtures/box/borderRadius.js b/test/fixtures/box/borderRadius.js index 684e4dd80..ea30845dc 100644 --- a/test/fixtures/box/borderRadius.js +++ b/test/fixtures/box/borderRadius.js @@ -18,8 +18,6 @@ module.exports = { annotations: { box1: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 1.5, xMax: 3.5, yMin: 5, @@ -31,8 +29,6 @@ module.exports = { }, box2: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 'July', xMax: 'May', yMin: 11, @@ -44,8 +40,6 @@ module.exports = { }, box3: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 0, xMax: 'May', yMin: 20, diff --git a/test/fixtures/box/borderWidth0.js b/test/fixtures/box/borderWidth0.js index 760f5d824..026ba5b15 100644 --- a/test/fixtures/box/borderWidth0.js +++ b/test/fixtures/box/borderWidth0.js @@ -17,8 +17,6 @@ module.exports = { annotations: { box1: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 1.5, xMax: 3.5, yMin: 5, diff --git a/test/fixtures/box/category-index.js b/test/fixtures/box/category-index.js index 6a4557834..0b9ee28b0 100644 --- a/test/fixtures/box/category-index.js +++ b/test/fixtures/box/category-index.js @@ -17,8 +17,6 @@ module.exports = { annotations: { box1: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 1.5, xMax: 3.5, yMin: 5, @@ -29,8 +27,6 @@ module.exports = { }, box2: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: 'May', xMax: 'July', yMin: 11, @@ -41,8 +37,6 @@ module.exports = { }, box3: { type: 'box', - xScaleID: 'x', - yScaleID: 'y', xMin: -0.5, xMax: 'May', yMin: 16, diff --git a/test/fixtures/box/label-dynamic.js b/test/fixtures/box/label-dynamic.js new file mode 100644 index 000000000..0d21a3270 --- /dev/null +++ b/test/fixtures/box/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: { + box: { + type: 'box', + xMin: 1, + xMax: 9, + yMin: 1, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + enabled: false, + content: 'This is dynamic!', + }, + enter({chart, element}) { + element.options.label.enabled = true; + chart.draw(); + } + }, + } + } + } + } + }, + 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/box/label-dynamic.png b/test/fixtures/box/label-dynamic.png new file mode 100644 index 000000000..4a3a580ff Binary files /dev/null and b/test/fixtures/box/label-dynamic.png differ diff --git a/test/fixtures/box/label.js b/test/fixtures/box/label.js new file mode 100644 index 000000000..612d2d1a2 --- /dev/null +++ b/test/fixtures/box/label.js @@ -0,0 +1,93 @@ +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: { + box1: { + type: 'box', + 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: { + enabled: true, + content: 'This is a label', + } + }, + box2: { + type: 'box', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'black', + borderWidth: 15, + label: { + enabled: true, + borderColor: 'green', + content: 'This label tests clipping', + position: 'start' + } + }, + box3: { + type: 'box', + 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: { + enabled: true, + content: 'This is a label with different length', + position: 'end' + } + }, + box4: { + type: 'box', + 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: { + enabled: true, + content: 'This is the label', + color: 'red', + position: { + x: 'start' + } + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/label.png b/test/fixtures/box/label.png new file mode 100644 index 000000000..7408bbf86 Binary files /dev/null and b/test/fixtures/box/label.png differ diff --git a/test/fixtures/box/labelCanvas.js b/test/fixtures/box/labelCanvas.js new file mode 100644 index 000000000..a4eb8de98 --- /dev/null +++ b/test/fixtures/box/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: 'box', + xMin: -9, + xMax: -1, + yMin: 9, + yMax: 1, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + enabled: true, + position: 'start', + content: window.createCanvas, + width: '25%', + height: '25%' + } + }, + canvas2: { + type: 'box', + xMin: 1, + xMax: 9, + yMin: 9, + yMax: 1, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + enabled: true, + position: 'end', + content: window.createCanvas, + width: '25%', + height: '25%' + } + }, + canvas3: { + type: 'box', + xMin: -9, + xMax: -1, + yMin: -1, + yMax: -9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + enabled: true, + position: { + x: 'start', + y: 'center' + }, + content: window.createCanvas, + width: '25%', + height: '25%' + } + }, + canvas4: { + type: 'box', + xMin: 1, + xMax: 9, + yMin: -1, + yMax: -9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 2, + label: { + enabled: true, + position: { + x: 'center', + y: 'end' + }, + content: window.createCanvas, + width: '25%', + height: '25%' + } + } + } + } + } + } + } +}; diff --git a/test/fixtures/box/labelCanvas.png b/test/fixtures/box/labelCanvas.png new file mode 100644 index 000000000..99df48c0b Binary files /dev/null and b/test/fixtures/box/labelCanvas.png differ diff --git a/test/fixtures/box/labelMultiline.js b/test/fixtures/box/labelMultiline.js new file mode 100644 index 000000000..ef112c088 --- /dev/null +++ b/test/fixtures/box/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: { + box1: { + type: 'box', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + enabled: true, + content: ['This is a label', 'but this is multiline'], + } + }, + box2: { + type: 'box', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + enabled: true, + content: ['This is a label', 'but this is multiline'], + position: 'start' + } + }, + box3: { + type: 'box', + xMin: -0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + enabled: true, + content: ['This is a label', 'but this is multiline'], + position: 'end' + } + }, + box4: { + type: 'box', + xMin: 'June', + xMax: 'July', + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(178, 255, 102, 0.5)', + borderColor: 'red', + borderWidth: 5, + label: { + enabled: true, + content: (ctx) => ['This is a label', 'type:' + ctx.type], + color: 'red', + position: { + x: 'start' + } + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/labelMultiline.png b/test/fixtures/box/labelMultiline.png new file mode 100644 index 000000000..beeb3f8c9 Binary files /dev/null and b/test/fixtures/box/labelMultiline.png differ diff --git a/test/fixtures/box/labelPadding.js b/test/fixtures/box/labelPadding.js new file mode 100644 index 000000000..393f1280a --- /dev/null +++ b/test/fixtures/box/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: { + box1: { + type: 'box', + 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: { + enabled: true, + content: 'box1: This is a label', + position: 'start', + padding: 15 + } + }, + box2: { + type: 'box', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + enabled: true, + content: 'box2: This is a label', + position: 'start', + padding: {x: 20} + } + }, + box3: { + type: 'box', + xMin: -0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + enabled: true, + content: 'box3: This is a label with different length', + position: 'end', + padding: {y: 10} + } + }, + box4: { + type: 'box', + xMin: 'June', + xMax: 'July', + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 5, + label: { + enabled: true, + content: 'box4: This is the label', + color: 'red', + position: { + x: 'start', + y: 'start' + }, + padding() { + return {left: 10, top: 5}; + } + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/labelPadding.png b/test/fixtures/box/labelPadding.png new file mode 100644 index 000000000..b7bb450f2 Binary files /dev/null and b/test/fixtures/box/labelPadding.png differ diff --git a/test/fixtures/box/labelPosition.js b/test/fixtures/box/labelPosition.js new file mode 100644 index 000000000..a6284394e --- /dev/null +++ b/test/fixtures/box/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: { + box1: { + type: 'box', + xMin: -9, + yMin: 9, + xMax: -1, + yMax: 1, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + enabled: true, + content: 'p: 0%,100%', + position: { + x: '0%', + y: '100%' + } + } + }, + box2: { + type: 'box', + xMin: 1, + yMin: 9, + xMax: 9, + yMax: 1, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + enabled: true, + content: 'p: 25%,75%', + position: { + x: '25%', + y: '75%' + } + } + }, + box3: { + type: 'box', + xMin: -9, + yMin: -1, + xMax: -1, + yMax: -9, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + enabled: true, + content: 'p: 50%,50%', + position: { + x: '50%', + y: '50%' + } + } + }, + box4: { + type: 'box', + xMin: 1, + yMin: -1, + xMax: 9, + yMax: -9, + backgroundColor: 'white', + borderColor: 'red', + borderWidth: 2, + label: { + enabled: true, + content: 'p: 100%,0%', + position: { + x: '100%', + y: '0%' + } + } + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/labelPosition.png b/test/fixtures/box/labelPosition.png new file mode 100644 index 000000000..65e9f4b68 Binary files /dev/null and b/test/fixtures/box/labelPosition.png differ diff --git a/test/fixtures/box/missingBothScales.js b/test/fixtures/box/missingBothScales.js new file mode 100644 index 000000000..2749a9f2a --- /dev/null +++ b/test/fixtures/box/missingBothScales.js @@ -0,0 +1,39 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + box: { + type: 'box', + xScaleID: 'missingX', + yScaleID: 'missingY', + xMin: 'February', + xMax: 'May', + yMin: 5, + yMax: 18, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 0, + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/missingBothScales.png b/test/fixtures/box/missingBothScales.png new file mode 100644 index 000000000..f82ff15d9 Binary files /dev/null and b/test/fixtures/box/missingBothScales.png differ diff --git a/test/fixtures/box/missingXScale.js b/test/fixtures/box/missingXScale.js new file mode 100644 index 000000000..c0a36aa4f --- /dev/null +++ b/test/fixtures/box/missingXScale.js @@ -0,0 +1,39 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + box: { + type: 'box', + xScaleID: 'missing', + yScaleID: 'y', + xMin: 'February', + xMax: 'May', + yMin: 5, + yMax: 18, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 0, + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/missingXScale.png b/test/fixtures/box/missingXScale.png new file mode 100644 index 000000000..2d9431d89 Binary files /dev/null and b/test/fixtures/box/missingXScale.png differ diff --git a/test/fixtures/box/missingYScale.js b/test/fixtures/box/missingYScale.js new file mode 100644 index 000000000..a57b2d226 --- /dev/null +++ b/test/fixtures/box/missingYScale.js @@ -0,0 +1,39 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + box: { + type: 'box', + xScaleID: 'x', + yScaleID: 'missing', + xMin: 'February', + xMax: 'May', + yMin: 5, + yMax: 18, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 0, + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/missingYScale.png b/test/fixtures/box/missingYScale.png new file mode 100644 index 000000000..5ca51ecee Binary files /dev/null and b/test/fixtures/box/missingYScale.png differ diff --git a/test/fixtures/box/shadow.js b/test/fixtures/box/shadow.js new file mode 100644 index 000000000..65489659a --- /dev/null +++ b/test/fixtures/box/shadow.js @@ -0,0 +1,95 @@ +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: { + box1: { + type: 'box', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + label: { + enabled: true, + content: 'no offset' + } + }, + box2: { + type: 'box', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + label: { + enabled: true, + content: 'offset x:10' + } + }, + box3: { + type: 'box', + xMin: 0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetY: 10, + label: { + enabled: true, + content: 'offset y:10' + } + }, + box4: { + type: 'box', + xMin: 'June', + xMax: 'July', + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10, + label: { + enabled: true, + content: ['offset', 'x:10', 'y:10'] + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/shadow.png b/test/fixtures/box/shadow.png new file mode 100644 index 000000000..e416e64a5 Binary files /dev/null and b/test/fixtures/box/shadow.png differ diff --git a/test/fixtures/box/shadowColors.js b/test/fixtures/box/shadowColors.js new file mode 100644 index 000000000..3428e5dc1 --- /dev/null +++ b/test/fixtures/box/shadowColors.js @@ -0,0 +1,99 @@ +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: { + box1: { + type: 'box', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'green', + shadowBlur: 3, + label: { + enabled: true, + content: 'no offset' + } + }, + box2: { + type: 'box', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'green', + shadowBlur: 3, + shadowOffsetX: 10, + label: { + enabled: true, + content: 'offset x:10' + } + }, + box3: { + type: 'box', + xMin: 0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'green', + shadowBlur: 3, + shadowOffsetY: 10, + label: { + enabled: true, + content: 'offset y:10' + } + }, + box4: { + type: 'box', + xMin: 'June', + xMax: 'July', + yMin: 5, + yMax: 9, + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'green', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10, + label: { + enabled: true, + content: ['offset', 'x:10', 'y:10'] + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/box/shadowColors.png b/test/fixtures/box/shadowColors.png new file mode 100644 index 000000000..e9b6d1348 Binary files /dev/null and b/test/fixtures/box/shadowColors.png differ diff --git a/test/fixtures/ellipse/borderDash.js b/test/fixtures/ellipse/borderDash.js index 9e154604d..ab507e5f7 100644 --- a/test/fixtures/ellipse/borderDash.js +++ b/test/fixtures/ellipse/borderDash.js @@ -17,8 +17,6 @@ module.exports = { annotations: { box1: { type: 'ellipse', - xScaleID: 'x', - yScaleID: 'y', xMin: 1.5, xMax: 3.5, yMin: 5, @@ -30,8 +28,6 @@ module.exports = { }, box2: { type: 'ellipse', - xScaleID: 'x', - yScaleID: 'y', xMin: 'May', xMax: 'July', yMin: 11, @@ -43,8 +39,6 @@ module.exports = { }, box3: { type: 'ellipse', - xScaleID: 'x', - yScaleID: 'y', xMin: -0.5, xMax: 'May', yMin: 16, diff --git a/test/fixtures/ellipse/missingBothScales.js b/test/fixtures/ellipse/missingBothScales.js new file mode 100644 index 000000000..eaa2f9b18 --- /dev/null +++ b/test/fixtures/ellipse/missingBothScales.js @@ -0,0 +1,39 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + ellipse: { + type: 'ellipse', + xScaleID: 'missingX', + yScaleID: 'missingY', + xMin: 'February', + xMax: 'May', + yMin: 5, + yMax: 18, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 0, + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/missingBothScales.png b/test/fixtures/ellipse/missingBothScales.png new file mode 100644 index 000000000..f82ff15d9 Binary files /dev/null and b/test/fixtures/ellipse/missingBothScales.png differ diff --git a/test/fixtures/ellipse/missingXScale.js b/test/fixtures/ellipse/missingXScale.js new file mode 100644 index 000000000..9b27566d8 --- /dev/null +++ b/test/fixtures/ellipse/missingXScale.js @@ -0,0 +1,39 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + ellipse: { + type: 'ellipse', + xScaleID: 'missing', + yScaleID: 'y', + xMin: 'February', + xMax: 'May', + yMin: 5, + yMax: 18, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 0, + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/missingXScale.png b/test/fixtures/ellipse/missingXScale.png new file mode 100644 index 000000000..71754123e Binary files /dev/null and b/test/fixtures/ellipse/missingXScale.png differ diff --git a/test/fixtures/ellipse/missingYScale.js b/test/fixtures/ellipse/missingYScale.js new file mode 100644 index 000000000..b18c4c105 --- /dev/null +++ b/test/fixtures/ellipse/missingYScale.js @@ -0,0 +1,39 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + ellipse: { + type: 'ellipse', + xScaleID: 'x', + yScaleID: 'missing', + xMin: 'February', + xMax: 'May', + yMin: 5, + yMax: 18, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 0, + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/missingYScale.png b/test/fixtures/ellipse/missingYScale.png new file mode 100644 index 000000000..23f930d72 Binary files /dev/null and b/test/fixtures/ellipse/missingYScale.png differ diff --git a/test/fixtures/ellipse/shadow.js b/test/fixtures/ellipse/shadow.js new file mode 100644 index 000000000..52bbdb2fd --- /dev/null +++ b/test/fixtures/ellipse/shadow.js @@ -0,0 +1,65 @@ +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: { + box1: { + type: 'ellipse', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10 + }, + box2: { + type: 'ellipse', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetY: 10 + }, + box3: { + type: 'ellipse', + xMin: -0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/shadow.png b/test/fixtures/ellipse/shadow.png new file mode 100644 index 000000000..26de7ba45 Binary files /dev/null and b/test/fixtures/ellipse/shadow.png differ diff --git a/test/fixtures/ellipse/shadowColors.js b/test/fixtures/ellipse/shadowColors.js new file mode 100644 index 000000000..fa992f743 --- /dev/null +++ b/test/fixtures/ellipse/shadowColors.js @@ -0,0 +1,69 @@ +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: { + box1: { + type: 'ellipse', + xMin: 1.5, + xMax: 3.5, + yMin: 5, + yMax: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'green', + shadowBlur: 3, + shadowOffsetX: 10 + }, + box2: { + type: 'ellipse', + xMin: 'May', + xMax: 'July', + yMin: 11, + yMax: 15, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'green', + shadowBlur: 3, + shadowOffsetY: 10 + }, + box3: { + type: 'ellipse', + xMin: -0.5, + xMax: 'May', + yMin: 16, + yMax: 20, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'green', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/ellipse/shadowColors.png b/test/fixtures/ellipse/shadowColors.png new file mode 100644 index 000000000..63d007223 Binary files /dev/null and b/test/fixtures/ellipse/shadowColors.png differ diff --git a/test/fixtures/label/border.js b/test/fixtures/label/border.js new file mode 100644 index 000000000..b67d45eec --- /dev/null +++ b/test/fixtures/label/border.js @@ -0,0 +1,86 @@ +function content(ctx, opts) { + return window.stringifyObject({dash: opts.borderDash, width: opts.borderWidth, radius: opts.borderRadius}); +} + +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 2 + }, + y: { + display: false, + min: 0, + max: 6 + } + }, + plugins: { + annotation: { + annotations: [ + { + type: 'label', + xValue: 1, + yValue: 5, + borderColor: 'red', + borderWidth: 0, + content + }, + { + type: 'label', + xValue: 1, + yValue: 4, + backgroundColor: 'black', + borderColor: 'red', + borderDash: [6, 6], + borderWidth: 5, + content + }, + { + type: 'label', + xValue: 1, + yValue: 3, + backgroundColor: 'white', + borderColor: 'red', + borderRadius: {topLeft: 16, topRight: 8, bottomRight: 4}, + borderWidth: 3, + content + }, + { + type: 'label', + xValue: 1, + yValue: 2, + backgroundColor: 'white', + borderColor: 'red', + borderDash: [2, 6, 16], + borderWidth: 2, + borderRadius: Infinity, + content + }, + { + type: 'label', + xValue: 1, + yValue: 1, + backgroundColor: 'white', + borderColor: 'rgba(255, 0, 0, 0.75)', + borderWidth: 5, + borderRadius: 4, + content + } + ] + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/label/border.png b/test/fixtures/label/border.png new file mode 100644 index 000000000..42e73e734 Binary files /dev/null and b/test/fixtures/label/border.png differ diff --git a/test/fixtures/label/boxLocation.js b/test/fixtures/label/boxLocation.js new file mode 100644 index 000000000..559089be1 --- /dev/null +++ b/test/fixtures/label/boxLocation.js @@ -0,0 +1,112 @@ +module.exports = { + threshold: 0.2, + tolerance: 0.0071, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + ticks: { + display: false + }, + min: -10, + max: 10 + }, + y: { + display: true, + ticks: { + display: false + }, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + label1: { + type: 'label', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['This is my text', 'and this is the second row of my text'], + padding: { + x: 10, + y: 10 + }, + position: { + y: 'end' + } + }, + point1: { + type: 'point', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + radius: 10, + backgroundColor: 'red', + borderColor: 'black', + borderWidth: 1 + }, + box1: { + type: 'box', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + }, + label2: { + type: 'label', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['This is my text', 'and this is the second row of my text'], + padding: { + x: 10, + y: 10 + }, + position: { + y: 'end' + }, + }, + point2: { + type: 'point', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + radius: 10, + backgroundColor: 'red', + borderColor: 'black', + borderWidth: 1 + }, + box2: { + type: 'box', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + } + } + } + } + } + } +}; diff --git a/test/fixtures/label/boxLocation.png b/test/fixtures/label/boxLocation.png new file mode 100644 index 000000000..50a989f6a Binary files /dev/null and b/test/fixtures/label/boxLocation.png differ diff --git a/test/fixtures/label/boxNotVisible.js b/test/fixtures/label/boxNotVisible.js new file mode 100644 index 000000000..9ad1757ed --- /dev/null +++ b/test/fixtures/label/boxNotVisible.js @@ -0,0 +1,49 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + text1: { + type: 'label', + xValue: 'January', + yValue: 20, + backgroundColor: 'missing', + borderWidth: 0, + content: 'This is my text', + position: { + x: 'end' + } + }, + text3: { + type: 'label', + xValue: 'May', + yValue: 20, + backgroundColor: 'rgba(250,250,250,0)', + borderWidth: 0, + content: 'This is my text', + position: { + x: 'start' + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/boxNotVisible.png b/test/fixtures/label/boxNotVisible.png new file mode 100644 index 000000000..bea9e9432 Binary files /dev/null and b/test/fixtures/label/boxNotVisible.png differ diff --git a/test/fixtures/label/calloutBasic.js b/test/fixtures/label/calloutBasic.js new file mode 100644 index 000000000..528114e13 --- /dev/null +++ b/test/fixtures/label/calloutBasic.js @@ -0,0 +1,90 @@ +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: { + basic: { + type: 'label', + xValue: 'February', + yValue: 20, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['This is the basic', 'callout representation'], + xAdjust: 150, + yAdjust: -70, + callout: { + enabled: true, + } + }, + overlap: { + type: 'label', + xValue: 'February', + yValue: 15, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['Callout is not shown', 'because overlaps the point'], + xAdjust: 1, + yAdjust: 1, + callout: { + enabled: true, + } + }, + crossLabel: { + type: 'label', + xValue: 'April', + yValue: 15, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'Callout is crossing the label', + xAdjust: 100, + yAdjust: -50, + callout: { + enabled: true, + position: 'right', + side: 10 + } + }, + styled: { + type: 'label', + xValue: 'March', + yValue: 5, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['Callout borderColor: red', 'borderDash: [2, 3]'], + xAdjust: 150, + yAdjust: -100, + callout: { + enabled: true, + side: 10, + borderColor: 'red', + borderWidth: 2, + borderDash: [2, 3], + borderDashOffset: 0 + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/calloutBasic.png b/test/fixtures/label/calloutBasic.png new file mode 100644 index 000000000..5059fe5a1 Binary files /dev/null and b/test/fixtures/label/calloutBasic.png differ diff --git a/test/fixtures/label/calloutCanvas.js b/test/fixtures/label/calloutCanvas.js new file mode 100644 index 000000000..44ddc99fb --- /dev/null +++ b/test/fixtures/label/calloutCanvas.js @@ -0,0 +1,43 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + canvas: { + type: 'label', + xValue: 6, + yValue: -6, + content: window.createCanvas, + position: { + x: 'center', + y: 'center' + }, + xAdjust: -150, + yAdjust: -230, + callout: { + enabled: true, + position: 'bottom' + } + } + } + } + } + } + } +}; diff --git a/test/fixtures/label/calloutCanvas.png b/test/fixtures/label/calloutCanvas.png new file mode 100644 index 000000000..692a7e924 Binary files /dev/null and b/test/fixtures/label/calloutCanvas.png differ diff --git a/test/fixtures/label/calloutHorizontalPosition.js b/test/fixtures/label/calloutHorizontalPosition.js new file mode 100644 index 000000000..c6f15e02e --- /dev/null +++ b/test/fixtures/label/calloutHorizontalPosition.js @@ -0,0 +1,167 @@ +module.exports = { + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: true, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + text1: { + type: 'label', + xValue: 2.5, + yValue: 20, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: right', + position: { + x: 'end', + y: 'center' + }, + xAdjust: -50, + yAdjust: -50, + callout: { + enabled: true, + position: 'right' + } + }, + point1: { + type: 'point', + xValue: 2.5, + yValue: 20 + }, + text2: { + type: 'label', + xValue: 'May', + yValue: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: left', + position: { + x: 'start', + y: 'center' + }, + xAdjust: 50, + yAdjust: 50, + callout: { + enabled: true, + position: 'left', + }, + }, + point2: { + type: 'point', + xValue: 'May', + yValue: 10 + }, + text3: { + type: 'label', + xValue: 'June', + yValue: 16, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: auto (left)', + position: { + x: 'end', + y: 'center' + }, + xAdjust: -50, + callout: { + enabled: true, + position: 'auto', + }, + }, + point3: { + type: 'point', + xValue: 'June', + yValue: 16 + }, + text4: { + type: 'label', + xValue: 'February', + yValue: 4, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: auto (right)', + position: { + x: 'start', + y: 'center' + }, + xAdjust: 50, + callout: { + enabled: true, + position: 'auto', + }, + }, + point4: { + type: 'point', + xValue: 'February', + yValue: 4 + }, + text5: { + type: 'label', + xValue: 'January', + yValue: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'no callout', + position: { + x: 'start', + y: 'center' + }, + xAdjust: 50, + callout: { + enabled: true, + position: 'left', + borderWidth: 0 + }, + }, + point5: { + type: 'point', + xValue: 'January', + yValue: 10 + }, + text6: { + type: 'label', + xValue: 'January', + yValue: 14, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'invalid position (auto)', + position: { + x: 'start', + y: 'center' + }, + xAdjust: 50, + callout: { + enabled: true, + position: 'wrong' + }, + }, + point6: { + type: 'point', + xValue: 'January', + yValue: 14 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/calloutHorizontalPosition.png b/test/fixtures/label/calloutHorizontalPosition.png new file mode 100644 index 000000000..16b39ad03 Binary files /dev/null and b/test/fixtures/label/calloutHorizontalPosition.png differ diff --git a/test/fixtures/label/calloutScriptableOptions.js b/test/fixtures/label/calloutScriptableOptions.js new file mode 100644 index 000000000..66e23d200 --- /dev/null +++ b/test/fixtures/label/calloutScriptableOptions.js @@ -0,0 +1,63 @@ +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: { + text1: { + type: 'label', + xValue: 'February', + yValue: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['This is my text', 'and this is the second row of my text'], + position: { + x: 'center', + y: 'center' + }, + xAdjust: 150, + yAdjust: -100, + callout: { + enabled: true, + margin: () => 10, + side: () => 10, + start: () => '80%', + borderColor: () => 'green', + borderWidth: () => 2, + borderDash: () => [1], + borderDashOffset: () => 0, + } + }, + point1: { + type: 'point', + xValue: 'February', + yValue: 10, + radius: () => 10, + backgroundColor: () => 'green', + borderColor: () => 'black', + borderWidth: () => 1, + borderDash: () => [1], + borderDashOffset: () => 0 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/calloutScriptableOptions.png b/test/fixtures/label/calloutScriptableOptions.png new file mode 100644 index 000000000..c0f1f7382 Binary files /dev/null and b/test/fixtures/label/calloutScriptableOptions.png differ diff --git a/test/fixtures/label/calloutSizing.js b/test/fixtures/label/calloutSizing.js new file mode 100644 index 000000000..684d5086c --- /dev/null +++ b/test/fixtures/label/calloutSizing.js @@ -0,0 +1,207 @@ +module.exports = { + tolerance: 0.0075, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: true, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + margin10: { + type: 'label', + xValue: 2.5, + yValue: 20, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 3, + content: ['position: right', 'margin: 10'], + position: { + x: 'end', + y: 'center' + }, + xAdjust: -50, + yAdjust: -50, + callout: { + enabled: true, + margin: 10, + position: 'right', + } + }, + pointMargin10: { + type: 'point', + xValue: 2.5, + yValue: 20, + radius: 3 + }, + noMargin: { + type: 'label', + xValue: 'May', + yValue: 24, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 4, + content: ['position: left', 'margin: 0'], + position: { + x: 'start', + y: 'center' + }, + xAdjust: 50, + yAdjust: 50, + callout: { + enabled: true, + margin: 0, + position: 'left', + } + }, + pointNoMargin: { + type: 'point', + xValue: 'May', + yValue: 24, + radius: 3 + }, + side20: { + type: 'label', + xValue: 2.5, + yValue: 12.5, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['position: bottom', 'side: 20', 'start: 10%'], + position: { + x: 'end', + y: 'center' + }, + xAdjust: -50, + yAdjust: -50, + callout: { + enabled: true, + start: '10%', + side: 20, + position: 'bottom', + } + }, + pointSide20: { + type: 'point', + xValue: 2.5, + yValue: 12.5, + radius: 3 + }, + topSide20: { + type: 'label', + xValue: 'May', + yValue: 15, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['position: top', 'side: 20', 'start: 80'], + position: { + x: 'start', + y: 'center' + }, + xAdjust: 50, + yAdjust: 50, + callout: { + enabled: true, + start: 80, + side: 20, + position: 'top', + } + }, + pointTopSide20: { + type: 'point', + xValue: 'May', + yValue: 15, + radius: 3 + }, + start10Perc: { + type: 'label', + xValue: 2.5, + yValue: 6, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: bottom, start: 10%', + position: { + x: 'end', + y: 'center' + }, + xAdjust: -20, + yAdjust: -40, + callout: { + enabled: true, + start: '10%', + position: 'bottom' + } + }, + pointStart10Perc: { + type: 'point', + xValue: 2.5, + yValue: 6, + radius: 3 + }, + start80: { + type: 'label', + xValue: 3.5, + yValue: 7, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: top, start: 40', + position: { + x: 'start', + y: 'center' + }, + xAdjust: 20, + yAdjust: 50, + callout: { + enabled: true, + start: 40, + position: 'top' + } + }, + pointStart80: { + type: 'point', + xValue: 3.5, + yValue: 7, + radius: 3 + }, + startInvalid: { + type: 'label', + xValue: 'April', + yValue: 5, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: auto, start invalid', + position: 'center', + xAdjust: -70, + yAdjust: 70, + callout: { + enabled: true, + start: 'invalid' + } + }, + pointStartInvalid: { + type: 'point', + xValue: 'April', + yValue: 5, + radius: 3 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/calloutSizing.png b/test/fixtures/label/calloutSizing.png new file mode 100644 index 000000000..167ce2e70 Binary files /dev/null and b/test/fixtures/label/calloutSizing.png differ diff --git a/test/fixtures/label/calloutVerticalPosition.js b/test/fixtures/label/calloutVerticalPosition.js new file mode 100644 index 000000000..fc53c52a7 --- /dev/null +++ b/test/fixtures/label/calloutVerticalPosition.js @@ -0,0 +1,161 @@ +module.exports = { + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: true, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + text1: { + type: 'label', + xValue: 2.5, + yValue: 20, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: bottom', + position: { + x: 'end', + y: 'center' + }, + xAdjust: -50, + yAdjust: -50, + callout: { + enabled: true, + position: 'bottom', + } + }, + point1: { + type: 'point', + xValue: 2.5, + yValue: 20, + radius: 3 + }, + text2: { + type: 'label', + xValue: 'May', + yValue: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: top', + position: { + x: 'start', + y: 'center' + }, + xAdjust: 50, + yAdjust: 50, + callout: { + enabled: true, + position: 'top', + } + }, + point2: { + type: 'point', + xValue: 'May', + yValue: 10, + radius: 3 + }, + text3: { + type: 'label', + xValue: 'June', + yValue: 18, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: auto (top)', + position: 'center', + yAdjust: 50, + callout: { + enabled: true, + position: 'auto', + } + }, + point3: { + type: 'point', + xValue: 'June', + yValue: 18, + radius: 3 + }, + text4: { + type: 'label', + xValue: 'February', + yValue: 7, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'position: auto (bottom)', + position: 'center', + yAdjust: -50, + callout: { + enabled: true, + position: 'auto', + } + }, + point4: { + type: 'point', + xValue: 'February', + yValue: 7, + radius: 3 + }, + text5: { + type: 'label', + xValue: 'February', + yValue: 15, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'no callout', + position: 'center', + yAdjust: -50, + callout: { + enabled: true, + position: 'auto', + borderWidth: 0 + } + }, + point5: { + type: 'point', + xValue: 'February', + yValue: 15, + radius: 3 + }, + text6: { + type: 'label', + xValue: 'April', + yValue: 5, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: 'invalid position (auto)', + position: 'center', + yAdjust: 50, + callout: { + enabled: true, + position: 'wrong' + } + }, + point6: { + type: 'point', + xValue: 'April', + yValue: 5, + radius: 3 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/calloutVerticalPosition.png b/test/fixtures/label/calloutVerticalPosition.png new file mode 100644 index 000000000..f9357ab8c Binary files /dev/null and b/test/fixtures/label/calloutVerticalPosition.png differ diff --git a/test/fixtures/label/canvas.js b/test/fixtures/label/canvas.js new file mode 100644 index 000000000..54b3174a0 --- /dev/null +++ b/test/fixtures/label/canvas.js @@ -0,0 +1,49 @@ +const canvas = window.createCanvas(); +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: -10, + max: 10 + }, + y: { + display: false, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + canvas: { + type: 'label', + xValue: 0, + yValue: 0, + content: canvas + }, + canvasSmall: { + type: 'label', + xValue: -6, + yValue: 6, + content: canvas, + width: 100, + height: () => 100 * canvas.height / canvas.width, + }, + canvasPerc: { + type: 'label', + xValue: 6, + yValue: -6, + content: canvas, + width: '50%', + height: '50%', + } + } + } + } + } + } +}; diff --git a/test/fixtures/label/canvas.png b/test/fixtures/label/canvas.png new file mode 100644 index 000000000..82ccbd23b Binary files /dev/null and b/test/fixtures/label/canvas.png differ diff --git a/test/fixtures/label/clip-false.js b/test/fixtures/label/clip-false.js new file mode 100644 index 000000000..74fd3676e --- /dev/null +++ b/test/fixtures/label/clip-false.js @@ -0,0 +1,44 @@ +module.exports = { + tolerance: 0.0075, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + min: 0, + max: 10 + } + }, + plugins: { + annotation: { + clip: false, + annotations: { + text1: { + type: 'label', + xValue: 4, + yValue: 0, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['This is my text', 'and this is the second row of my text'], + position: { + x: 'end', + y: 'start' + }, + } + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + width: 256, + height: 256 + } + } +}; diff --git a/test/fixtures/label/clip-false.png b/test/fixtures/label/clip-false.png new file mode 100644 index 000000000..724f55f09 Binary files /dev/null and b/test/fixtures/label/clip-false.png differ diff --git a/test/fixtures/label/contentMultiline.js b/test/fixtures/label/contentMultiline.js new file mode 100644 index 000000000..855a6473d --- /dev/null +++ b/test/fixtures/label/contentMultiline.js @@ -0,0 +1,81 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + text1: { + type: 'label', + xValue: 'January', + yValue: 20, + content: ['position: {x: start, y: center}, textAlign: start, xValue: January', 'This is my text, row 2, longer than other', 'This is my text, row 3'], + position: { + x: 'start', + y: 'center' + }, + textAlign: 'start' + }, + text2: { + type: 'label', + xValue: 'April', + yValue: 10, + content: ['position: center, textAlign: center, xValue: April', 'This is my text, row 2, longer than other', 'This is my text, row 3'], + }, + text3: { + type: 'label', + xValue: 'May', + yValue: 15, + content: ['position: {x: end}, xValue: May', 'This is my text, row 2, longer than other', 'This is my text, row 3'], + position: { + x: 'end' + } + }, + noContent: { + type: 'label', + xValue: 'March', + yValue: 5, + }, + noContentAndCallout: { + type: 'label', + xValue: 'March', + yValue: 5, + callout: { + enabled: true + } + }, + emptyContent: { + type: 'label', + xValue: 'March', + yValue: 5, + content: '' + }, + emptyContentAndCallout: { + type: 'label', + xValue: 'March', + yValue: 5, + content: '', + callout: { + enabled: true + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/contentMultiline.png b/test/fixtures/label/contentMultiline.png new file mode 100644 index 000000000..99cfe3769 Binary files /dev/null and b/test/fixtures/label/contentMultiline.png differ diff --git a/test/fixtures/label/missingScales.js b/test/fixtures/label/missingScales.js new file mode 100644 index 000000000..acfbdbfed --- /dev/null +++ b/test/fixtures/label/missingScales.js @@ -0,0 +1,58 @@ +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: { + missingBoth: { + type: 'label', + xScaleID: 'missingX', + yScaleID: 'missingY', + xValue: 'February', + yValue: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['Missing x and y scales', 'located to', 'the center'] + }, + missingX: { + type: 'label', + xScaleID: 'missing', + xValue: 'February', + yValue: 2, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['Missing x scale', 'located to', 'horizontal center'] + }, + missingY: { + type: 'label', + yScaleID: 'missing', + xValue: 0.5, + yValue: 10, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + content: ['Missing y scale', 'located to', 'vertical center'] + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/missingScales.png b/test/fixtures/label/missingScales.png new file mode 100644 index 000000000..b34f674ae Binary files /dev/null and b/test/fixtures/label/missingScales.png differ diff --git a/test/fixtures/label/padding.js b/test/fixtures/label/padding.js new file mode 100644 index 000000000..4551e7909 --- /dev/null +++ b/test/fixtures/label/padding.js @@ -0,0 +1,101 @@ +function singleLine(ctx, opts) { + return 'padding: ' + window.stringifyObject(opts.padding); +} + +function multiLine(ctx, opts) { + return ['padding: ' + window.stringifyObject(opts.padding), 'align: ' + opts.textAlign]; +} + +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 10 + }, + y: { + display: false, + min: 0, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + text1: { + type: 'label', + xValue: 5, + yValue: 9, + borderWidth: 3, + borderColor: 'red', + content: singleLine, + padding: 15 + }, + text2: { + type: 'label', + xValue: 5, + yValue: 8, + borderWidth: 3, + borderColor: 'red', + content: singleLine, + padding: {x: 15} + }, + text3: { + type: 'label', + xValue: 5, + yValue: 6.5, + backgroundColor: 'transparent', + borderWidth: 3, + borderColor: 'red', + content: singleLine, + padding: {y: 30} + }, + text4: { + type: 'label', + xValue: 5, + yValue: 4.5, + borderWidth: 3, + borderColor: 'red', + content: multiLine, + textAlign: 'right', + padding() { + return {left: 40, top: 10, right: 5, bottom: 20}; + } + }, + text5: { + type: 'label', + xValue: 5, + yValue: 3, + borderWidth: 3, + borderColor: 'red', + content: multiLine, + textAlign: 'left', + padding() { + return {left: 40, top: 10, right: 5, bottom: 20}; + } + }, + text6: { + type: 'label', + xValue: 5, + yValue: 1, + borderWidth: 3, + borderColor: 'red', + content: multiLine, + padding() { + return {left: 5, top: 10, right: 50, bottom: 30}; + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/padding.png b/test/fixtures/label/padding.png new file mode 100644 index 000000000..1fdaab9a3 Binary files /dev/null and b/test/fixtures/label/padding.png differ diff --git a/test/fixtures/label/positionHorizontal.js b/test/fixtures/label/positionHorizontal.js new file mode 100644 index 000000000..cd1b0b207 --- /dev/null +++ b/test/fixtures/label/positionHorizontal.js @@ -0,0 +1,140 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'line', + data: { + datasets: [ + { + borderWidth: 1, + borderColor: 'red', + data: [7, 7, 7, 7, 7, 7, 7] + }, + { + borderWidth: 1, + borderColor: 'red', + data: [16, 16, 16, 16, 16, 16, 16] + } + ] + }, + options: { + indexAxis: 'y', + scales: { + y: { + display: false, + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + x: { + display: false, + min: 0, + max: 24 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + start: { + type: 'label', + yValue: 0.5, + xValue: 7, + content: 'position: {x: start}', + position: { + x: 'start' + } + }, + center: { + type: 'label', + yValue: 2, + xValue: 7, + content: 'position: {x: center}', + }, + end: { + type: 'label', + yValue: 3.5, + xValue: 7, + content: 'position: {x: end}', + position: { + x: 'end' + } + }, + invalid: { + type: 'label', + yValue: 5, + xValue: 7, + content: 'position: {x: invalid}', + position: { + x: 'invalid' + } + }, + perc0: { + type: 'label', + yValue: 0.5, + xValue: 16, + content: 'position: {x: 0%}', + position: { + x: '0%' + } + }, + perc25: { + type: 'label', + yValue: 1.5, + xValue: 16, + content: 'position: {x: 25%}', + position: { + x: '25%' + } + }, + perc50: { + type: 'label', + yValue: 2.5, + xValue: 16, + content: 'position: {x: 50%}', + position: { + x: '50%' + }, + }, + perc75: { + type: 'label', + yValue: 3.5, + xValue: 16, + content: 'position: {x: 75%}', + position: { + x: '75%' + }, + }, + perc100: { + type: 'label', + yValue: 4.5, + xValue: 16, + content: 'position: {x: 100%}', + position: { + x: '100%' + }, + }, + percMoreThan100: { + type: 'label', + yValue: 5.5, + xValue: 16, + content: 'position: {x: > 100%}', + position: { + x: '850%' + }, + }, + percLessThan100: { + type: 'label', + yValue: 5, + xValue: 16, + content: 'position: {x: < 0%}', + position: { + x: '-850%' + }, + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/positionHorizontal.png b/test/fixtures/label/positionHorizontal.png new file mode 100644 index 000000000..ece45e43b Binary files /dev/null and b/test/fixtures/label/positionHorizontal.png differ diff --git a/test/fixtures/label/positionVertical.js b/test/fixtures/label/positionVertical.js new file mode 100644 index 000000000..0605772aa --- /dev/null +++ b/test/fixtures/label/positionVertical.js @@ -0,0 +1,142 @@ +module.exports = { + tolerance: 0.0060, + config: { + type: 'line', + data: { + datasets: [ + { + borderWidth: 1, + borderColor: 'red', + data: [8, 8, 8, 8, 8, 8, 8] + }, + { + borderWidth: 1, + borderColor: 'red', + data: [16, 16, 16, 16, 16, 16, 16] + }, + ] + }, + options: { + scales: { + x: { + display: false, + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 24 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + center: { + type: 'label', + xValue: 0.5, + yValue: 8, + content: ['position', '{y: center}'], + position: { + y: 'center' + } + }, + start: { + type: 'label', + xValue: 2, + yValue: 8, + content: ['position', '{y: start}'], + position: { + y: 'start' + } + }, + end: { + type: 'label', + xValue: 3.5, + yValue: 8, + content: ['position', '{y: end}'], + position: { + y: 'end' + } + }, + invalid: { + type: 'label', + xValue: 5, + yValue: 8, + content: ['position', '{y: invalid}'], + position: { + y: 'invalid' + } + }, + perc0: { + type: 'label', + xValue: 0.3, + yValue: 16, + content: ['position', '{y: 0%}'], + position: { + y: '0%' + } + }, + perc25: { + type: 'label', + xValue: 1.3, + yValue: 16, + content: ['position', '{y: 25%}'], + position: { + y: '25%' + } + }, + perc50: { + type: 'label', + xValue: 2.3, + yValue: 16, + content: ['position', '{y: 50%}'], + position: { + y: '50%' + }, + }, + perc75: { + type: 'label', + xValue: 3.3, + yValue: 16, + content: ['position', '{y: 75%}'], + position: { + y: '75%' + }, + }, + perc100: { + type: 'label', + xValue: 4.3, + yValue: 16, + content: ['position', '{y: 100%}'], + position: { + y: '100%' + }, + }, + percMoreThan100: { + type: 'label', + xValue: 5.5, + yValue: 16, + content: ['position', '{y: > 100%}'], + position: { + y: '850%' + }, + }, + percLessThan100: { + type: 'label', + xValue: 5, + yValue: 16, + content: ['position', '{y: < 0%}'], + position: { + y: '-850%' + }, + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/positionVertical.png b/test/fixtures/label/positionVertical.png new file mode 100644 index 000000000..a058715d2 Binary files /dev/null and b/test/fixtures/label/positionVertical.png differ diff --git a/test/fixtures/label/scriptableOptions.js b/test/fixtures/label/scriptableOptions.js new file mode 100644 index 000000000..713106475 --- /dev/null +++ b/test/fixtures/label/scriptableOptions.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'bar', + options: { + scales: { + x: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + }, + y: { + display: false, + min: 0, + max: 25 + } + }, + plugins: { + annotation: { + annotations: { + text1: { + type: 'label', + xValue: 'January', + yValue: 20, + backgroundColor: () => 'transparent', + borderWidth: () => 0, + content: (ctx, opts) => 'xValue: ' + opts.xValue + ', position: ' + JSON.stringify(opts.position), + position() { + return {x: 'start'}; + } + }, + text2: { + type: 'label', + xValue: 'April', + yValue: 10, + backgroundColor: () => 'transparent', + borderWidth: () => 0, + content: (ctx, opts) => 'xValue: ' + opts.xValue + ', position: ' + JSON.stringify(opts.position), + }, + text3: { + type: 'label', + xValue: 'May', + yValue: 15, + backgroundColor: () => 'transparent', + borderWidth: () => 0, + content: (ctx, opts) => 'xValue: ' + opts.xValue + ', position: ' + JSON.stringify(opts.position), + position() { + return {x: 'end'}; + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/label/scriptableOptions.png b/test/fixtures/label/scriptableOptions.png new file mode 100644 index 000000000..4ae2b8c26 Binary files /dev/null and b/test/fixtures/label/scriptableOptions.png differ diff --git a/test/fixtures/label/shadow.js b/test/fixtures/label/shadow.js new file mode 100644 index 000000000..b4301c615 --- /dev/null +++ b/test/fixtures/label/shadow.js @@ -0,0 +1,95 @@ +module.exports = { + threshold: 0.2, + tolerance: 0.0071, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + ticks: { + display: false + }, + min: -10, + max: 10 + }, + y: { + display: true, + ticks: { + display: false + }, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + label1: { + type: 'label', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + content: ['This is my text', 'offset x: 10'], + padding: { + x: 10, + y: 10 + }, + position: { + y: 'end' + } + }, + label2: { + type: 'label', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetY: 10, + content: ['This is my text', 'offset y: 10'], + padding: { + x: 10, + y: 10 + }, + position: { + y: 'end' + }, + }, + label3: { + type: 'label', + xMin: -8, + yMin: 8, + xMax: -1, + yMax: 1, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10, + content: ['This is my text', 'offset x: 10, y: 10'], + padding: { + x: 10, + y: 10 + }, + } + } + } + } + } + } +}; diff --git a/test/fixtures/label/shadow.png b/test/fixtures/label/shadow.png new file mode 100644 index 000000000..72aeb2f18 Binary files /dev/null and b/test/fixtures/label/shadow.png differ diff --git a/test/fixtures/label/shadowColors.js b/test/fixtures/label/shadowColors.js new file mode 100644 index 000000000..795f076aa --- /dev/null +++ b/test/fixtures/label/shadowColors.js @@ -0,0 +1,98 @@ +module.exports = { + threshold: 0.2, + tolerance: 0.0071, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + ticks: { + display: false + }, + min: -10, + max: 10 + }, + y: { + display: true, + ticks: { + display: false + }, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + label1: { + type: 'label', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetX: 10, + content: ['This is my text', 'offset x: 10'], + padding: { + x: 10, + y: 10 + }, + position: { + y: 'end' + } + }, + label2: { + type: 'label', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetY: 10, + content: ['This is my text', 'offset y: 10'], + padding: { + x: 10, + y: 10 + }, + position: { + y: 'end' + }, + }, + label3: { + type: 'label', + xMin: -8, + yMin: 8, + xMax: -1, + yMax: 1, + backgroundColor: 'rgba(33, 101, 171, 0.5)', + borderColor: 'rgb(33, 101, 171)', + borderWidth: 1, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10, + content: ['This is my text', 'offset x: 10, y: 10'], + padding: { + x: 10, + y: 10 + }, + } + } + } + } + } + } +}; diff --git a/test/fixtures/label/shadowColors.png b/test/fixtures/label/shadowColors.png new file mode 100644 index 000000000..a5ec10e6a Binary files /dev/null and b/test/fixtures/label/shadowColors.png differ diff --git a/test/fixtures/line/arrowHeads.js b/test/fixtures/line/arrowHeads.js new file mode 100644 index 000000000..3788e13f8 --- /dev/null +++ b/test/fixtures/line/arrowHeads.js @@ -0,0 +1,218 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: 0, + max: 100 + }, + y: { + display: true, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + arrow: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'start: true, end: true', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 30, + width: 15 + }, + end: { + enabled: true, + length: 30, + width: 15 + } + } + }, + arrow1: { + type: 'line', + xMin: 90, + yMin: 10, + xMax: 80, + yMax: 50, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'start: true', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 30, + width: 15 + }, + } + }, + arrow2: { + type: 'line', + xMin: 30, + yMin: 80, + xMax: 60, + yMax: 85, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'end: true', + enabled: true + }, + arrowHeads: { + end: { + enabled: true, + length: 30, + width: 15 + }, + }, + }, + arrow3: { + type: 'line', + xMin: 65, + yMin: 70, + xMax: 70, + yMax: 100, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'start: dash', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + borderDash: [3, 6], + borderDashOffset: 0, + length: 30, + width: 15 + }, + }, + }, + arrow4: { + type: 'line', + xMin: 10, + yMin: 60, + xMax: 30, + yMax: 70, + borderWidth: 5, + label: { + position: '0%', + rotation: 'auto', + backgroundColor: 'red', + content: 'end: width 30', + enabled: true + }, + arrowHeads: { + end: { + enabled: true, + length: 40, + width: 30 + }, + }, + }, + arrow5: { + type: 'line', + xMin: 10, + yMin: 30, + xMax: 40, + yMax: 50, + borderWidth: 0, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'borderWidth 0', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 15, + width: 5 + }, + end: { + enabled: true, + length: 15, + width: 5 + } + } + }, + arrow6: { + type: 'line', + xMin: 30, + yMin: 8, + xMax: 70, + yMax: 8, + borderWidth: 1, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'horizontal', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 15, + width: 5 + }, + end: { + enabled: true, + length: 15, + width: 5 + } + } + }, + arrow7: { + type: 'line', + xMin: 10, + yMin: 70, + xMax: 10, + yMax: 98, + borderWidth: 1, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'vertical', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 15, + width: 5 + }, + end: { + enabled: true, + length: 15, + width: 5 + } + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/arrowHeads.png b/test/fixtures/line/arrowHeads.png new file mode 100644 index 000000000..733ef62e0 Binary files /dev/null and b/test/fixtures/line/arrowHeads.png differ diff --git a/test/fixtures/line/arrowHeadsDeleteAtRuntime.js b/test/fixtures/line/arrowHeadsDeleteAtRuntime.js new file mode 100644 index 000000000..3475e3b9b --- /dev/null +++ b/test/fixtures/line/arrowHeadsDeleteAtRuntime.js @@ -0,0 +1,119 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: 0, + max: 10 + }, + y: { + display: true, + min: 0, + max: 10 + } + }, + plugins: { + annotation: { + annotations: [ + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 8, + yMax: 8, + borderColor: 'blue', + borderWidth: 4, + label: { + enabled: true, + backgroundColor: 'white', + content: 'remove start' + }, + arrowHeads: { + length: 30, + width: 15, + start: { + enabled: true, + }, + end: { + enabled: true, + } + }, + click({chart, element}) { + delete element.options.arrowHeads.start; + chart.draw(); + } + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 5, + yMax: 5, + borderColor: 'purple', + borderWidth: 4, + label: { + enabled: true, + backgroundColor: 'white', + content: 'remove end' + }, + arrowHeads: { + length: 30, + width: 15, + start: { + enabled: true, + }, + end: { + enabled: true, + } + }, + click({chart, element}) { + delete element.options.arrowHeads.end; + chart.draw(); + } + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 2, + yMax: 2, + borderColor: 'red', + borderWidth: 4, + label: { + enabled: true, + backgroundColor: 'white', + content: 'remove start and end' + }, + arrowHeads: { + length: 30, + width: 15, + start: { + enabled: true, + }, + end: { + enabled: true, + } + }, + click({chart, element}) { + delete element.options.arrowHeads.start; + delete element.options.arrowHeads.end; + chart.draw(); + } + } + ] + } + } + } + }, + options: { + spriteText: true, + async run(chart) { + const elements = window.getAnnotationElements(chart); + await window.triggerMouseEvent(chart, 'click', elements[0].getCenterPoint()); + await window.triggerMouseEvent(chart, 'click', elements[1].getCenterPoint()); + await window.triggerMouseEvent(chart, 'click', elements[2].getCenterPoint()); + } + } +}; diff --git a/test/fixtures/line/arrowHeadsDeleteAtRuntime.png b/test/fixtures/line/arrowHeadsDeleteAtRuntime.png new file mode 100644 index 000000000..5789f011e Binary files /dev/null and b/test/fixtures/line/arrowHeadsDeleteAtRuntime.png differ diff --git a/test/fixtures/line/arrowHeadsFallback.js b/test/fixtures/line/arrowHeadsFallback.js new file mode 100644 index 000000000..853787b90 --- /dev/null +++ b/test/fixtures/line/arrowHeadsFallback.js @@ -0,0 +1,81 @@ +module.exports = { + config: { + type: 'line', + options: { + scales: { + x: {type: 'linear', min: 0, max: 10}, + y: {type: 'linear', min: 0, max: 10} + }, + plugins: { + legend: false, + annotation: { + annotations: [ + { + type: 'line', + scaleID: 'y', + value: 8, + borderColor: 'blue', + borderWidth: 4, + label: { + enabled: true, + content: ['start and end are enabled', 'without options'] + }, + arrowHeads: { + enabled: true, + fill: true, + borderColor: 'blue', + length: 30, + width: 15 + } + }, + { + type: 'line', + scaleID: 'y', + value: 5, + borderColor: 'purple', + borderWidth: 4, + label: { + enabled: true, + content: ['only start has options', 'and is enabled'] + }, + arrowHeads: { + fill: true, + borderColor: 'purple', + length: 30, + width: 15, + start: { + enabled: true, + } + } + }, + { + type: 'line', + scaleID: 'y', + value: 2, + borderColor: 'red', + borderWidth: 4, + label: { + enabled: true, + content: ['only start has options', 'and is not filled'] + }, + arrowHeads: { + enabled: true, + fill: true, + borderColor: 'red', + length: 30, + width: 15, + start: { + enabled: true, + fill: false + } + } + } + ] + } + }, + } + }, + options: { + spriteText: true, + } +}; diff --git a/test/fixtures/line/arrowHeadsFallback.png b/test/fixtures/line/arrowHeadsFallback.png new file mode 100644 index 000000000..8dd003737 Binary files /dev/null and b/test/fixtures/line/arrowHeadsFallback.png differ diff --git a/test/fixtures/line/arrowHeadsFill.js b/test/fixtures/line/arrowHeadsFill.js new file mode 100644 index 000000000..2f422bd85 --- /dev/null +++ b/test/fixtures/line/arrowHeadsFill.js @@ -0,0 +1,241 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: 0, + max: 100 + }, + y: { + display: true, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + arrow: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'start: true, end: true', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + fill: true, + length: 30, + width: 15 + }, + end: { + enabled: true, + fill: true, + length: 30, + width: 15 + } + } + }, + arrow1: { + type: 'line', + xMin: 90, + yMin: 10, + xMax: 80, + yMax: 50, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'start: true', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + fill: true, + backgroundColor: 'orange', + length: 30, + width: 15 + }, + } + }, + arrow2: { + type: 'line', + xMin: 30, + yMin: 80, + xMax: 60, + yMax: 85, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'end: true', + enabled: true + }, + arrowHeads: { + end: { + enabled: true, + fill: true, + backgroundColor: 'orange', + borderColor: 'orange', + length: 30, + width: 15 + }, + }, + }, + arrow3: { + type: 'line', + xMin: 65, + yMin: 70, + xMax: 70, + yMax: 100, + borderWidth: 5, + label: { + position: '100%', + rotation: 'auto', + backgroundColor: 'red', + content: 'start: dash', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + borderDash: [3, 6], + borderDashOffset: 0, + fill: true, + backgroundColor: 'transparent', + length: 30, + width: 15 + }, + }, + }, + arrow4: { + type: 'line', + xMin: 10, + yMin: 60, + xMax: 30, + yMax: 70, + borderWidth: 5, + label: { + position: '0%', + rotation: 'auto', + backgroundColor: 'red', + content: 'end: width 30', + enabled: true + }, + arrowHeads: { + end: { + enabled: true, + fill: true, + backgroundColor: 'transparent', + length: 40, + width: 30 + }, + }, + }, + arrow5: { + type: 'line', + xMin: 10, + yMin: 30, + xMax: 40, + yMax: 50, + borderWidth: 0, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'borderWidth 0', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 15, + width: 5, + fill: true, + }, + end: { + enabled: true, + length: 15, + width: 5, + fill: true, + backgroundColor: 'orange', + } + } + }, + arrow6: { + type: 'line', + xMin: 30, + yMin: 8, + xMax: 70, + yMax: 8, + borderWidth: 1, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'horizontal', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 15, + width: 5, + fill: true, + backgroundColor: 'lightGreen', + }, + end: { + enabled: true, + length: 15, + width: 5, + fill: true, + backgroundColor: 'blue', + } + } + }, + arrow7: { + type: 'line', + xMin: 10, + yMin: 70, + xMax: 10, + yMax: 98, + borderWidth: 1, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'vertical', + enabled: true + }, + arrowHeads: { + start: { + enabled: true, + length: 15, + width: 5, + fill: true, + backgroundColor: 'lightGreen', + }, + end: { + enabled: true, + length: 15, + width: 5, + fill: true, + backgroundColor: 'transparent', + } + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/arrowHeadsFill.png b/test/fixtures/line/arrowHeadsFill.png new file mode 100644 index 000000000..43f1f2948 Binary files /dev/null and b/test/fixtures/line/arrowHeadsFill.png differ diff --git a/test/fixtures/line/arrowHeadsFillScriptableOptions.js b/test/fixtures/line/arrowHeadsFillScriptableOptions.js new file mode 100644 index 000000000..8e38b088c --- /dev/null +++ b/test/fixtures/line/arrowHeadsFillScriptableOptions.js @@ -0,0 +1,107 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: 0, + max: 100 + }, + y: { + display: true, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + arrow: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'start: true, end: true', + enabled: true + }, + arrowHeads: { + start: { + enabled: () => true, + fill: () => true, + length: () => 30, + width: () => 15 + }, + end: { + enabled: () => true, + fill: () => true, + length: () => 30, + width: () => 15 + } + } + }, + arrow1: { + type: 'line', + xMin: 90, + yMin: 10, + xMax: 80, + yMax: 50, + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'start: true', + enabled: true + }, + arrowHeads: { + start() { + return { + enabled: true, + fill: true, + backgroundColor: 'orange', + length: 30, + width: 15 + }; + }, + } + }, + arrow2: { + type: 'line', + xMin: 30, + yMin: 80, + xMax: 60, + yMax: 85, + borderWidth: 5, + label: { + position: '0%', + rotation: 'auto', + backgroundColor: 'red', + content: 'end: true', + enabled: true + }, + arrowHeads: { + end() { + return { + enabled: true, + fill: true, + backgroundColor: 'orange', + borderColor: 'orange', + length: 30, + width: 15 + }; + }, + }, + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/arrowHeadsFillScriptableOptions.png b/test/fixtures/line/arrowHeadsFillScriptableOptions.png new file mode 100644 index 000000000..9a777fffd Binary files /dev/null and b/test/fixtures/line/arrowHeadsFillScriptableOptions.png differ diff --git a/test/fixtures/line/borderWidth0.js b/test/fixtures/line/borderWidth0.js new file mode 100644 index 000000000..33b8a0dbd --- /dev/null +++ b/test/fixtures/line/borderWidth0.js @@ -0,0 +1,35 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: 0, + max: 100 + }, + y: { + display: true, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + line: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderWidth: 0 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/borderWidth0.png b/test/fixtures/line/borderWidth0.png new file mode 100644 index 000000000..aacfac772 Binary files /dev/null and b/test/fixtures/line/borderWidth0.png differ diff --git a/test/fixtures/line/label-border.js b/test/fixtures/line/label-border.js index f24fcd077..a80621b03 100644 --- a/test/fixtures/line/label-border.js +++ b/test/fixtures/line/label-border.js @@ -16,7 +16,6 @@ module.exports = { }, plugins: { annotation: { - drawTime: 'afterDatasetsDraw', annotations: { line1: { type: 'line', diff --git a/test/fixtures/line/label-border.png b/test/fixtures/line/label-border.png index 4180a9a63..ccd68ef45 100644 Binary files a/test/fixtures/line/label-border.png and b/test/fixtures/line/label-border.png differ diff --git a/test/fixtures/line/label-dynamic-hide.js b/test/fixtures/line/label-dynamic-hide.js new file mode 100644 index 000000000..3e45a98fc --- /dev/null +++ b/test/fixtures/line/label-dynamic-hide.js @@ -0,0 +1,60 @@ +module.exports = { + description: 'https://github.com/chartjs/chartjs-plugin-annotation/issues/589', + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 100 + }, + y: { + display: false, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + line: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + borderColor: 'black', + borderRadius: 10, + borderWidth: 3, + borderDash: [6, 6], + content: 'dynamic label', + enabled: false + }, + enter({chart, element}) { + element.options.label.enabled = true; + chart.draw(); + }, + leave({chart, element}) { + element.options.label.enabled = false; + chart.draw(); + } + }, + } + } + } + } + }, + options: { + spriteText: true, + async run(chart) { + const el = window.getAnnotationElements(chart)[0]; + await window.triggerMouseEvent(chart, 'mousemove', el.getCenterPoint()); + await window.triggerMouseEvent(chart, 'mousemove', {x: 1, y: 1}); + } + } +}; diff --git a/test/fixtures/line/label-dynamic-hide.png b/test/fixtures/line/label-dynamic-hide.png new file mode 100644 index 000000000..9a0b8207c Binary files /dev/null and b/test/fixtures/line/label-dynamic-hide.png differ diff --git a/test/fixtures/line/label-dynamic-show.js b/test/fixtures/line/label-dynamic-show.js new file mode 100644 index 000000000..4f26c6742 --- /dev/null +++ b/test/fixtures/line/label-dynamic-show.js @@ -0,0 +1,55 @@ +module.exports = { + description: 'https://github.com/chartjs/chartjs-plugin-annotation/issues/589', + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 100 + }, + y: { + display: false, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + line: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + borderColor: 'black', + borderRadius: 10, + borderWidth: 3, + borderDash: [6, 6], + content: 'dynamic label', + enabled: false + }, + enter({chart, element}) { + element.options.label.enabled = true; + chart.draw(); + } + }, + } + } + } + } + }, + options: { + spriteText: true, + async run(chart) { + const el = window.getAnnotationElements(chart)[0]; + await window.triggerMouseEvent(chart, 'mousemove', el.getCenterPoint()); + } + } +}; diff --git a/test/fixtures/line/label-dynamic-show.png b/test/fixtures/line/label-dynamic-show.png new file mode 100644 index 000000000..566223860 Binary files /dev/null and b/test/fixtures/line/label-dynamic-show.png differ diff --git a/test/fixtures/line/labelCanvas.js b/test/fixtures/line/labelCanvas.js new file mode 100644 index 000000000..23252b0ba --- /dev/null +++ b/test/fixtures/line/labelCanvas.js @@ -0,0 +1,48 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 100 + }, + y: { + display: false, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + line1: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + borderColor: 'black', + borderRadius: 10, + borderWidth: 3, + borderDash: [6, 6], + content: window.createCanvas, + width: '25%', + height: '25%', + enabled: true + }, + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/labelCanvas.png b/test/fixtures/line/labelCanvas.png new file mode 100644 index 000000000..f028ab1fc Binary files /dev/null and b/test/fixtures/line/labelCanvas.png differ diff --git a/test/fixtures/line/labelShadow.js b/test/fixtures/line/labelShadow.js new file mode 100644 index 000000000..1641e9afe --- /dev/null +++ b/test/fixtures/line/labelShadow.js @@ -0,0 +1,92 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 100 + }, + y: { + display: false, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + left: { + type: 'line', + scaleID: 'y', + value: 25, + borderColor: 'black', + borderWidth: 5, + label: { + position: 'start', + backgroundColor: 'red', + content: 'no offset', + enabled: true, + backgroundShadowColor: 'black', + shadowBlur: 12 + }, + }, + hCenter: { + type: 'line', + scaleID: 'y', + value: 50, + borderColor: 'black', + borderWidth: 5, + label: { + position: 'center', + backgroundColor: 'red', + content: 'offset x: 10', + enabled: true, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10 + }, + }, + right: { + type: 'line', + scaleID: 'y', + value: 75, + borderColor: 'black', + borderWidth: 5, + label: { + position: 'end', + backgroundColor: 'black', + content: 'offset y: 10', + enabled: true, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetY: 10 + }, + }, + top: { + type: 'line', + scaleID: 'x', + value: 50, + borderColor: 'blue', + borderWidth: 5, + label: { + position: 'start', + backgroundColor: 'red', + content: 'offset x: 10, y:10', + enabled: true, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/labelShadow.png b/test/fixtures/line/labelShadow.png new file mode 100644 index 000000000..d6ffba449 Binary files /dev/null and b/test/fixtures/line/labelShadow.png differ diff --git a/test/fixtures/line/labelShadowColors.js b/test/fixtures/line/labelShadowColors.js new file mode 100644 index 000000000..574ccf05f --- /dev/null +++ b/test/fixtures/line/labelShadowColors.js @@ -0,0 +1,104 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 100 + }, + y: { + display: false, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + left: { + type: 'line', + scaleID: 'y', + value: 25, + borderColor: 'black', + borderWidth: 5, + label: { + position: 'start', + backgroundColor: 'red', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + content: 'no offset', + enabled: true, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 12 + }, + }, + hCenter: { + type: 'line', + scaleID: 'y', + value: 50, + borderColor: 'black', + borderWidth: 5, + label: { + position: 'center', + backgroundColor: 'red', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + content: 'offset x: 10', + enabled: true, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetX: 10 + }, + }, + right: { + type: 'line', + scaleID: 'y', + value: 75, + borderColor: 'black', + borderWidth: 5, + label: { + position: 'end', + backgroundColor: 'black', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + content: 'offset y: 10', + enabled: true, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetY: 10 + }, + }, + top: { + type: 'line', + scaleID: 'x', + value: 50, + borderColor: 'blue', + borderWidth: 5, + label: { + position: 'start', + backgroundColor: 'red', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + content: 'offset x: 10, y:10', + enabled: true, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/labelShadowColors.png b/test/fixtures/line/labelShadowColors.png new file mode 100644 index 000000000..f114c8dbe Binary files /dev/null and b/test/fixtures/line/labelShadowColors.png differ diff --git a/test/fixtures/line/label_drawTime.png b/test/fixtures/line/label_drawTime.png index d24e100b1..b112c74b0 100644 Binary files a/test/fixtures/line/label_drawTime.png and b/test/fixtures/line/label_drawTime.png differ diff --git a/test/fixtures/line/labelsScriptableOptions.png b/test/fixtures/line/labelsScriptableOptions.png index 704a19c8b..4d026932f 100644 Binary files a/test/fixtures/line/labelsScriptableOptions.png and b/test/fixtures/line/labelsScriptableOptions.png differ diff --git a/test/fixtures/line/out-of-range-value0.js b/test/fixtures/line/out-of-range-value0.js new file mode 100644 index 000000000..833a19bc1 --- /dev/null +++ b/test/fixtures/line/out-of-range-value0.js @@ -0,0 +1,35 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [40, 20, 30, 60, 55], + }], + labels: ['A', 'B', 'C', 'D', 'E'] + }, + options: { + plugins: { + legend: false, + annotation: { + annotations: { + annotation1: { + type: 'line', + scaleID: 'y', + borderWidth: 3, + borderColor: 'black', + value: 0, + label: { + backgroundColor: 'red', + content: 'should be drawn', + enabled: true + } + } + } + } + }, + } + }, + options: { + spriteText: true, + } +}; diff --git a/test/fixtures/line/out-of-range-value0.png b/test/fixtures/line/out-of-range-value0.png new file mode 100644 index 000000000..42c0efa1d Binary files /dev/null and b/test/fixtures/line/out-of-range-value0.png differ diff --git a/test/fixtures/line/padding.js b/test/fixtures/line/padding.js new file mode 100644 index 000000000..8d50e07e4 --- /dev/null +++ b/test/fixtures/line/padding.js @@ -0,0 +1,78 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 100 + }, + y: { + display: false, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + auto1: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + position: 'start', + backgroundColor: 'red', + content: 'padding: 10', + enabled: true, + padding: 10 + }, + }, + auto2: { + type: 'line', + xMin: 90, + yMin: 10, + xMax: 80, + yMax: 50, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'padding: {x: 10, y: 15}', + enabled: true, + padding: {x: 10, y: 15} + }, + }, + auto3: { + type: 'line', + xMin: 30, + yMin: 80, + xMax: 60, + yMax: 85, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'padding: {left: 10, top: 15, right: 3, bottom: 2}', + enabled: true, + padding() { + return {left: 10, top: 15, right: 3, bottom: 2}; + } + }, + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/padding.png b/test/fixtures/line/padding.png new file mode 100644 index 000000000..cd1b55ac1 Binary files /dev/null and b/test/fixtures/line/padding.png differ diff --git a/test/fixtures/line/paddingXY.js b/test/fixtures/line/paddingXY.js new file mode 100644 index 000000000..46e7b2b5a --- /dev/null +++ b/test/fixtures/line/paddingXY.js @@ -0,0 +1,79 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: 0, + max: 100 + }, + y: { + display: false, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + auto1: { + type: 'line', + scaleID: 'y', + value: 0, + endValue: 80, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + position: 'start', + backgroundColor: 'red', + content: 'xPadding: 20', + enabled: true, + xPadding: 20 + }, + }, + auto2: { + type: 'line', + xMin: 90, + yMin: 10, + xMax: 80, + yMax: 50, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'xPadding: 20, yPadding: 15', + enabled: true, + xPadding: 20, + yPadding: 15 + }, + }, + auto3: { + type: 'line', + xMin: 30, + yMin: 80, + xMax: 60, + yMax: 85, + borderColor: 'black', + borderWidth: 5, + label: { + rotation: 'auto', + backgroundColor: 'red', + content: 'yPadding: 15', + enabled: true, + yPadding() { + return 15; + }, + }, + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/paddingXY.png b/test/fixtures/line/paddingXY.png new file mode 100644 index 000000000..c83e94ee7 Binary files /dev/null and b/test/fixtures/line/paddingXY.png differ diff --git a/test/fixtures/line/partial-range.js b/test/fixtures/line/partial-range.js index ea0f57d07..8ffaae12b 100644 --- a/test/fixtures/line/partial-range.js +++ b/test/fixtures/line/partial-range.js @@ -19,8 +19,6 @@ module.exports = { annotations: { annotation1: { type: 'line', - xScale: 'x', - yScale: 'y', borderWidth: 3, borderColor: 'black', xMin: 'B', diff --git a/test/fixtures/line/position.png b/test/fixtures/line/position.png index 1c9ede522..904596edd 100644 Binary files a/test/fixtures/line/position.png and b/test/fixtures/line/position.png differ diff --git a/test/fixtures/line/positionPercent.js b/test/fixtures/line/positionPercent.js new file mode 100644 index 000000000..91220893e --- /dev/null +++ b/test/fixtures/line/positionPercent.js @@ -0,0 +1,171 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: 0, + max: 100 + }, + y: { + display: true, + min: 0, + max: 100 + } + }, + plugins: { + annotation: { + annotations: { + l0: { + type: 'line', + scaleID: 'y', + value: 0, + borderColor: 'black', + borderWidth: 5, + label: { + position: '0%', + backgroundColor: 'black', + content: '0%', + enabled: true + } + }, + l1: { + type: 'line', + scaleID: 'y', + value: 10, + borderColor: 'black', + borderWidth: 5, + label: { + position: '10%', + backgroundColor: 'black', + content: '10%', + enabled: true + } + }, + l2: { + type: 'line', + scaleID: 'y', + value: 20, + borderColor: 'black', + borderWidth: 5, + label: { + position: '20%', + backgroundColor: 'black', + content: '20%', + enabled: true + } + }, + l3: { + type: 'line', + scaleID: 'y', + value: 30, + borderColor: 'black', + borderWidth: 5, + label: { + position: '30%', + backgroundColor: 'black', + content: '30%', + enabled: true + } + }, + l4: { + type: 'line', + scaleID: 'y', + value: 40, + borderColor: 'black', + borderWidth: 5, + label: { + position: '40%', + backgroundColor: 'black', + content: '40%', + enabled: true + } + }, + l5: { + type: 'line', + scaleID: 'y', + value: 50, + borderColor: 'black', + borderWidth: 5, + label: { + position: '50%', + backgroundColor: 'black', + content: '50%', + enabled: true + } + }, + l6: { + type: 'line', + scaleID: 'y', + value: 60, + borderColor: 'black', + borderWidth: 5, + label: { + position: '60%', + backgroundColor: 'black', + content: '60%', + enabled: true + } + }, + l7: { + type: 'line', + scaleID: 'y', + value: 70, + borderColor: 'black', + borderWidth: 5, + label: { + position: '70%', + backgroundColor: 'black', + content: '70%', + enabled: true + } + }, + l8: { + type: 'line', + scaleID: 'y', + value: 80, + borderColor: 'black', + borderWidth: 5, + label: { + position: '80%', + backgroundColor: 'black', + content: '80%', + enabled: true + } + }, + l9: { + type: 'line', + scaleID: 'y', + value: 90, + borderColor: 'black', + borderWidth: 5, + label: { + position: '90%', + backgroundColor: 'black', + content: '90%', + enabled: true + } + }, + l10: { + type: 'line', + scaleID: 'y', + value: 100, + borderColor: 'black', + borderWidth: 5, + label: { + position: '100%', + backgroundColor: 'black', + content: '100%', + enabled: true + } + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/line/positionPercent.png b/test/fixtures/line/positionPercent.png new file mode 100644 index 000000000..e25fb5d11 Binary files /dev/null and b/test/fixtures/line/positionPercent.png differ diff --git a/test/fixtures/line/rotation.js b/test/fixtures/line/rotation.js index d217fefba..31168f584 100644 --- a/test/fixtures/line/rotation.js +++ b/test/fixtures/line/rotation.js @@ -16,7 +16,6 @@ module.exports = { }, plugins: { annotation: { - drawTime: 'afterDatasetsDraw', annotations: { auto1: { type: 'line', @@ -35,8 +34,6 @@ module.exports = { }, auto2: { type: 'line', - xScaleID: 'x', - yScaleID: 'y', xMin: 90, yMin: 10, xMax: 80, @@ -52,8 +49,6 @@ module.exports = { }, auto3: { type: 'line', - xScaleID: 'x', - yScaleID: 'y', xMin: 30, yMin: 80, xMax: 60, @@ -69,8 +64,6 @@ module.exports = { }, auto4: { type: 'line', - xScaleID: 'x', - yScaleID: 'y', xMin: 65, yMin: 70, xMax: 70, @@ -86,8 +79,6 @@ module.exports = { }, man1: { type: 'line', - xScaleID: 'x', - yScaleID: 'y', xMin: 10, yMin: 60, xMax: 30, @@ -103,8 +94,6 @@ module.exports = { }, man2: { type: 'line', - xScaleID: 'x', - yScaleID: 'y', xMin: 10, yMin: 30, xMax: 40, diff --git a/test/fixtures/line/rotation.png b/test/fixtures/line/rotation.png index 704a19c8b..4d026932f 100644 Binary files a/test/fixtures/line/rotation.png and b/test/fixtures/line/rotation.png differ diff --git a/test/fixtures/line/scriptableOptions.png b/test/fixtures/line/scriptableOptions.png index 704a19c8b..4d026932f 100644 Binary files a/test/fixtures/line/scriptableOptions.png and b/test/fixtures/line/scriptableOptions.png differ diff --git a/test/fixtures/line/shadow.js b/test/fixtures/line/shadow.js new file mode 100644 index 000000000..833c93ab8 --- /dev/null +++ b/test/fixtures/line/shadow.js @@ -0,0 +1,57 @@ +module.exports = { + config: { + type: 'line', + options: { + scales: { + x: {type: 'linear', min: 0, max: 10}, + y: {type: 'linear', min: 0, max: 10} + }, + plugins: { + legend: false, + annotation: { + annotations: [ + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 8, + yMax: 8, + borderColor: 'blue', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12 + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 5, + yMax: 5, + borderColor: 'purple', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10 + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 2, + yMax: 2, + borderColor: 'red', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + ] + } + }, + } + }, + options: { + spriteText: true, + } +}; diff --git a/test/fixtures/line/shadow.png b/test/fixtures/line/shadow.png new file mode 100644 index 000000000..c13ebf2c8 Binary files /dev/null and b/test/fixtures/line/shadow.png differ diff --git a/test/fixtures/line/shadowArrowHeads.js b/test/fixtures/line/shadowArrowHeads.js new file mode 100644 index 000000000..4627b6ced --- /dev/null +++ b/test/fixtures/line/shadowArrowHeads.js @@ -0,0 +1,117 @@ +module.exports = { + config: { + type: 'line', + options: { + scales: { + x: {type: 'linear', min: 0, max: 10}, + y: {type: 'linear', min: 0, max: 10} + }, + plugins: { + legend: false, + annotation: { + annotations: [ + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 8, + yMax: 8, + borderColor: 'blue', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + arrowHeads: { + start: { + enabled: true, + borderColor: 'blue', + borderShadowColor: 'black', + shadowBlur: 12, + length: 30, + width: 15 + }, + end: { + enabled: true, + borderColor: 'blue', + borderShadowColor: 'black', + shadowBlur: 12, + length: 30, + width: 15 + } + } + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 5, + yMax: 5, + borderColor: 'purple', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + arrowHeads: { + start: { + enabled: true, + borderColor: 'purple', + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + length: 30, + width: 15 + }, + end: { + enabled: true, + borderColor: 'purple', + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + length: 30, + width: 15 + } + } + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 2, + yMax: 2, + borderColor: 'red', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + shadowOffsetY: 10, + arrowHeads: { + start: { + enabled: true, + borderColor: 'red', + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + shadowOffsetY: 10, + length: 30, + width: 15 + }, + end: { + enabled: true, + borderColor: 'red', + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + shadowOffsetY: 10, + length: 30, + width: 15 + } + } + } + ] + } + }, + } + }, + options: { + spriteText: true, + } +}; diff --git a/test/fixtures/line/shadowArrowHeads.png b/test/fixtures/line/shadowArrowHeads.png new file mode 100644 index 000000000..cbf65198b Binary files /dev/null and b/test/fixtures/line/shadowArrowHeads.png differ diff --git a/test/fixtures/line/shadowArrowHeadsFill.js b/test/fixtures/line/shadowArrowHeadsFill.js new file mode 100644 index 000000000..ee31967ae --- /dev/null +++ b/test/fixtures/line/shadowArrowHeadsFill.js @@ -0,0 +1,123 @@ +module.exports = { + config: { + type: 'line', + options: { + scales: { + x: {type: 'linear', min: 0, max: 10}, + y: {type: 'linear', min: 0, max: 10} + }, + plugins: { + legend: false, + annotation: { + annotations: [ + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 8, + yMax: 8, + borderColor: 'blue', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + arrowHeads: { + start: { + enabled: true, + fill: true, + borderColor: 'blue', + backgroundShadowColor: 'black', + shadowBlur: 12, + length: 30, + width: 15 + }, + end: { + enabled: true, + fill: true, + borderColor: 'blue', + backgroundShadowColor: 'black', + shadowBlur: 12, + length: 30, + width: 15 + } + } + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 5, + yMax: 5, + borderColor: 'purple', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + arrowHeads: { + start: { + enabled: true, + fill: true, + borderColor: 'purple', + backgroundShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + length: 30, + width: 15 + }, + end: { + enabled: true, + fill: true, + borderColor: 'purple', + backgroundShadowColor: 'black', + shadowOffsetX: 10, + shadowBlur: 12, + length: 30, + width: 15 + } + } + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 2, + yMax: 2, + borderColor: 'red', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + shadowOffsetY: 10, + arrowHeads: { + start: { + enabled: true, + fill: true, + borderColor: 'red', + backgroundShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + shadowOffsetY: 10, + length: 30, + width: 15 + }, + end: { + enabled: true, + fill: true, + borderColor: 'red', + backgroundShadowColor: 'black', + shadowOffsetX: 10, + shadowOffsetY: 10, + shadowBlur: 12, + length: 30, + width: 15 + } + } + } + ] + } + }, + } + }, + options: { + spriteText: true, + } +}; diff --git a/test/fixtures/line/shadowArrowHeadsFill.png b/test/fixtures/line/shadowArrowHeadsFill.png new file mode 100644 index 000000000..7014da26a Binary files /dev/null and b/test/fixtures/line/shadowArrowHeadsFill.png differ diff --git a/test/fixtures/line/shadowBorderColor.js b/test/fixtures/line/shadowBorderColor.js new file mode 100644 index 000000000..833c93ab8 --- /dev/null +++ b/test/fixtures/line/shadowBorderColor.js @@ -0,0 +1,57 @@ +module.exports = { + config: { + type: 'line', + options: { + scales: { + x: {type: 'linear', min: 0, max: 10}, + y: {type: 'linear', min: 0, max: 10} + }, + plugins: { + legend: false, + annotation: { + annotations: [ + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 8, + yMax: 8, + borderColor: 'blue', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12 + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 5, + yMax: 5, + borderColor: 'purple', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10 + }, + { + type: 'line', + xMin: 1, + xMax: 8, + yMin: 2, + yMax: 2, + borderColor: 'red', + borderWidth: 4, + borderShadowColor: 'black', + shadowBlur: 12, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + ] + } + }, + } + }, + options: { + spriteText: true, + } +}; diff --git a/test/fixtures/line/shadowBorderColor.png b/test/fixtures/line/shadowBorderColor.png new file mode 100644 index 000000000..e4386fecf Binary files /dev/null and b/test/fixtures/line/shadowBorderColor.png differ diff --git a/test/fixtures/point/adjust.js b/test/fixtures/point/adjust.js new file mode 100644 index 000000000..234d818c8 --- /dev/null +++ b/test/fixtures/point/adjust.js @@ -0,0 +1,133 @@ +module.exports = { + tolerance: 0.0080, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point1: { + type: 'point', + xValue: 4.5, + yValue: 4.5, + xAdjust: 30, + yAdjust: 40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center1: { + type: 'label', + xValue: 4.5, + yValue: 4.5, + content: 'x: +30, y: +40', + position: 'start' + }, + pointCenter1: { + type: 'point', + xValue: 4.5, + yValue: 4.5, + backgroundColor: 'black', + radius: 3 + }, + point2: { + type: 'point', + xValue: -4.5, + yValue: 4.5, + xAdjust: 30, + yAdjust: -40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center2: { + type: 'label', + xValue: -4.5, + yValue: 4.5, + content: 'x: +30, y: -40', + position: { + x: 'start', + y: 'end' + } + }, + pointCenter2: { + type: 'point', + xValue: -4.5, + yValue: 4.5, + backgroundColor: 'black', + radius: 3 + }, + point3: { + type: 'point', + xValue: -4.5, + yValue: -4.5, + xAdjust: -30, + yAdjust: -40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center3: { + type: 'label', + xValue: -4.5, + yValue: -4.5, + content: 'x: -30, y: -40', + position: 'end' + }, + pointCenter3: { + type: 'point', + xValue: -4.5, + yValue: -4.5, + backgroundColor: 'black', + radius: 3 + }, + point4: { + type: 'point', + xValue: 4.5, + yValue: -4.5, + xAdjust: -30, + yAdjust: 40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center4: { + type: 'label', + xValue: 4.5, + yValue: -4.5, + content: 'x: -30, y: +40', + position: { + x: 'end', + y: 'start' + } + }, + pointCenter4: { + type: 'point', + xValue: 4.5, + yValue: -4.5, + backgroundColor: 'black', + radius: 3 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/adjust.png b/test/fixtures/point/adjust.png new file mode 100644 index 000000000..2bd2abde6 Binary files /dev/null and b/test/fixtures/point/adjust.png differ diff --git a/test/fixtures/point/basic.js b/test/fixtures/point/basic.js index 1ca44deb5..a9045c8e7 100644 --- a/test/fixtures/point/basic.js +++ b/test/fixtures/point/basic.js @@ -17,12 +17,9 @@ module.exports = { plugins: { legend: false, annotation: { - drawTime: 'afterDraw', annotations: { point: { type: 'point', - xScaleID: 'x', - yScaleID: 'y', xValue: 1, yValue: 1, backgroundColor: 'rgba(101, 33, 171, 0.5)', diff --git a/test/fixtures/point/basicBorderWidth0.js b/test/fixtures/point/basicBorderWidth0.js new file mode 100644 index 000000000..2965752bc --- /dev/null +++ b/test/fixtures/point/basicBorderWidth0.js @@ -0,0 +1,35 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: -10, + max: 10 + }, + y: { + display: false, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point: { + type: 'point', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 0, + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/basicBorderWidth0.png b/test/fixtures/point/basicBorderWidth0.png new file mode 100644 index 000000000..6fb4459ab Binary files /dev/null and b/test/fixtures/point/basicBorderWidth0.png differ diff --git a/test/fixtures/point/borderDash.js b/test/fixtures/point/borderDash.js index 7861abf92..5448fb276 100644 --- a/test/fixtures/point/borderDash.js +++ b/test/fixtures/point/borderDash.js @@ -17,12 +17,9 @@ module.exports = { plugins: { legend: false, annotation: { - drawTime: 'afterDraw', annotations: { point: { type: 'point', - xScaleID: 'x', - yScaleID: 'y', xValue: 1, yValue: 1, backgroundColor: 'rgba(101, 33, 171, 0.5)', diff --git a/test/fixtures/point/boundToPoint0.js b/test/fixtures/point/boundToPoint0.js new file mode 100644 index 000000000..872aa9239 --- /dev/null +++ b/test/fixtures/point/boundToPoint0.js @@ -0,0 +1,36 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -4, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/boundToPoint0.png b/test/fixtures/point/boundToPoint0.png new file mode 100644 index 000000000..58759d59a Binary files /dev/null and b/test/fixtures/point/boundToPoint0.png differ diff --git a/test/fixtures/point/boxLocation.js b/test/fixtures/point/boxLocation.js new file mode 100644 index 000000000..8bfc97527 --- /dev/null +++ b/test/fixtures/point/boxLocation.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0075, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point1: { + type: 'point', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + box1: { + type: 'box', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + }, + point2: { + type: 'point', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + box2: { + type: 'box', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/boxLocation.png b/test/fixtures/point/boxLocation.png new file mode 100644 index 000000000..14bc8f7ee Binary files /dev/null and b/test/fixtures/point/boxLocation.png differ diff --git a/test/fixtures/point/boxLocationAdjust.js b/test/fixtures/point/boxLocationAdjust.js new file mode 100644 index 000000000..f5747cf25 --- /dev/null +++ b/test/fixtures/point/boxLocationAdjust.js @@ -0,0 +1,157 @@ +module.exports = { + tolerance: 0.0080, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point1: { + type: 'point', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + xAdjust: 30, + yAdjust: 40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center1: { + type: 'label', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + content: 'x: +30, y: +40', + position: 'start' + }, + pointCenter1: { + type: 'point', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'black', + radius: 3 + }, + point2: { + type: 'point', + xMin: -1, + yMin: 1, + xMax: -8, + yMax: 8, + xAdjust: 30, + yAdjust: -40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center2: { + type: 'label', + xMin: -1, + yMin: 1, + xMax: -8, + yMax: 8, + content: 'x: +30, y: -40', + position: { + x: 'start', + y: 'end' + } + }, + pointCenter2: { + type: 'point', + xMin: -1, + yMin: 1, + xMax: -8, + yMax: 8, + backgroundColor: 'black', + radius: 3 + }, + point3: { + type: 'point', + xMin: -1, + yMin: -1, + xMax: -8, + yMax: -8, + xAdjust: -30, + yAdjust: -40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center3: { + type: 'label', + xMin: -1, + yMin: -1, + xMax: -8, + yMax: -8, + content: 'x: -30, y: -40', + position: 'end' + }, + pointCenter3: { + type: 'point', + xMin: -1, + yMin: -1, + xMax: -8, + yMax: -8, + backgroundColor: 'black', + radius: 3 + }, + point4: { + type: 'point', + xMin: 1, + yMin: -1, + xMax: 8, + yMax: -8, + xAdjust: -30, + yAdjust: 40, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center4: { + type: 'label', + xMin: 1, + yMin: -1, + xMax: 8, + yMax: -8, + content: 'x: -30, y: +40', + position: { + x: 'end', + y: 'start' + } + }, + pointCenter4: { + type: 'point', + xMin: 1, + yMin: -1, + xMax: 8, + yMax: -8, + backgroundColor: 'black', + radius: 3 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/boxLocationAdjust.png b/test/fixtures/point/boxLocationAdjust.png new file mode 100644 index 000000000..84ea70061 Binary files /dev/null and b/test/fixtures/point/boxLocationAdjust.png differ diff --git a/test/fixtures/point/boxLocationRadius.js b/test/fixtures/point/boxLocationRadius.js new file mode 100644 index 000000000..fb5d3f434 --- /dev/null +++ b/test/fixtures/point/boxLocationRadius.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0075, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point1: { + type: 'point', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 10 + }, + box1: { + type: 'box', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + }, + point2: { + type: 'point', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 120 + }, + box2: { + type: 'box', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/boxLocationRadius.png b/test/fixtures/point/boxLocationRadius.png new file mode 100644 index 000000000..1f3fac96b Binary files /dev/null and b/test/fixtures/point/boxLocationRadius.png differ diff --git a/test/fixtures/point/canvas.js b/test/fixtures/point/canvas.js new file mode 100644 index 000000000..d48b36b92 --- /dev/null +++ b/test/fixtures/point/canvas.js @@ -0,0 +1,35 @@ +const canvas = window.createCanvas(); +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + canvas: { + type: 'point', + xValue: 0, + yValue: 0, + pointStyle: canvas, + radius: Math.max(canvas.width, canvas.height) + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/canvas.png b/test/fixtures/point/canvas.png new file mode 100644 index 000000000..413d9a8c5 Binary files /dev/null and b/test/fixtures/point/canvas.png differ diff --git a/test/fixtures/point/cross.js b/test/fixtures/point/cross.js new file mode 100644 index 000000000..a2577e322 --- /dev/null +++ b/test/fixtures/point/cross.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + crossSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 3, + pointStyle: 'cross', + radius: 10 + }, + cross: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + pointStyle: 'cross', + radius: 25 + }, + crossBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'cross', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/cross.png b/test/fixtures/point/cross.png new file mode 100644 index 000000000..b63e93a7b Binary files /dev/null and b/test/fixtures/point/cross.png differ diff --git a/test/fixtures/point/crossRot.js b/test/fixtures/point/crossRot.js new file mode 100644 index 000000000..18c2df444 --- /dev/null +++ b/test/fixtures/point/crossRot.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + crossRotSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 3, + pointStyle: 'crossRot', + radius: 10 + }, + crossRot: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 5, + pointStyle: 'crossRot', + radius: 25 + }, + crossRotBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 15, + pointStyle: 'crossRot', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/crossRot.png b/test/fixtures/point/crossRot.png new file mode 100644 index 000000000..5a31c3308 Binary files /dev/null and b/test/fixtures/point/crossRot.png differ diff --git a/test/fixtures/point/crossRotShadow.js b/test/fixtures/point/crossRotShadow.js new file mode 100644 index 000000000..b5fd16ba8 --- /dev/null +++ b/test/fixtures/point/crossRotShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + crossRotSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 3, + pointStyle: 'crossRot', + radius: 10, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + crossRot: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 5, + pointStyle: 'crossRot', + radius: 25, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + crossRotBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 15, + pointStyle: 'crossRot', + radius: 50, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/crossRotShadow.png b/test/fixtures/point/crossRotShadow.png new file mode 100644 index 000000000..91e681a89 Binary files /dev/null and b/test/fixtures/point/crossRotShadow.png differ diff --git a/test/fixtures/point/crossShadow.js b/test/fixtures/point/crossShadow.js new file mode 100644 index 000000000..a1036cef7 --- /dev/null +++ b/test/fixtures/point/crossShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + crossSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 3, + pointStyle: 'cross', + radius: 10, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + cross: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + pointStyle: 'cross', + radius: 25, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + crossBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'cross', + radius: 50, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/crossShadow.png b/test/fixtures/point/crossShadow.png new file mode 100644 index 000000000..50d69554f Binary files /dev/null and b/test/fixtures/point/crossShadow.png differ diff --git a/test/fixtures/point/dash.js b/test/fixtures/point/dash.js new file mode 100644 index 000000000..250e74b83 --- /dev/null +++ b/test/fixtures/point/dash.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + dashSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 3, + pointStyle: 'dash', + radius: 10 + }, + dash: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 5, + pointStyle: 'dash', + radius: 25 + }, + dashBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 15, + pointStyle: 'dash', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/dash.png b/test/fixtures/point/dash.png new file mode 100644 index 000000000..81bd8c6d3 Binary files /dev/null and b/test/fixtures/point/dash.png differ diff --git a/test/fixtures/point/dashShadow.js b/test/fixtures/point/dashShadow.js new file mode 100644 index 000000000..d5343baf2 --- /dev/null +++ b/test/fixtures/point/dashShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + dashSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 3, + pointStyle: 'dash', + radius: 10, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + dash: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 5, + pointStyle: 'dash', + radius: 25, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + dashBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 15, + pointStyle: 'dash', + radius: 50, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/dashShadow.png b/test/fixtures/point/dashShadow.png new file mode 100644 index 000000000..fc348f8a4 Binary files /dev/null and b/test/fixtures/point/dashShadow.png differ diff --git a/test/fixtures/point/line.js b/test/fixtures/point/line.js new file mode 100644 index 000000000..d4e24c389 --- /dev/null +++ b/test/fixtures/point/line.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + lineSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 3, + pointStyle: 'line', + radius: 10 + }, + line: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + pointStyle: 'line', + radius: 25 + }, + lineBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'line', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/line.png b/test/fixtures/point/line.png new file mode 100644 index 000000000..0913059e0 Binary files /dev/null and b/test/fixtures/point/line.png differ diff --git a/test/fixtures/point/lineShadow.js b/test/fixtures/point/lineShadow.js new file mode 100644 index 000000000..edafc36fc --- /dev/null +++ b/test/fixtures/point/lineShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0065, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + lineSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 3, + pointStyle: 'line', + radius: 10, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + line: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + pointStyle: 'line', + radius: 25, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + lineBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'line', + radius: 50, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/lineShadow.png b/test/fixtures/point/lineShadow.png new file mode 100644 index 000000000..b281fbdb6 Binary files /dev/null and b/test/fixtures/point/lineShadow.png differ diff --git a/test/fixtures/point/rect.js b/test/fixtures/point/rect.js new file mode 100644 index 000000000..b7ea90185 --- /dev/null +++ b/test/fixtures/point/rect.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rectSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 3, + pointStyle: 'rect', + radius: 10 + }, + rect: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 5, + pointStyle: 'rect', + radius: 25 + }, + rectBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 15, + pointStyle: 'rect', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/rect.png b/test/fixtures/point/rect.png new file mode 100644 index 000000000..7004d10df Binary files /dev/null and b/test/fixtures/point/rect.png differ diff --git a/test/fixtures/point/rectRot.js b/test/fixtures/point/rectRot.js new file mode 100644 index 000000000..6e995a52d --- /dev/null +++ b/test/fixtures/point/rectRot.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rectRotSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 3, + pointStyle: 'rectRot', + radius: 10 + }, + rectRot: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + pointStyle: 'rectRot', + radius: 25 + }, + rectRotBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'rectRot', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/rectRot.png b/test/fixtures/point/rectRot.png new file mode 100644 index 000000000..69546f8cf Binary files /dev/null and b/test/fixtures/point/rectRot.png differ diff --git a/test/fixtures/point/rectRotShadow.js b/test/fixtures/point/rectRotShadow.js new file mode 100644 index 000000000..ce0688ef4 --- /dev/null +++ b/test/fixtures/point/rectRotShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rectRotSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 3, + pointStyle: 'rectRot', + radius: 10, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + rectRot: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + pointStyle: 'rectRot', + radius: 25, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + rectRotBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'rectRot', + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/rectRotShadow.png b/test/fixtures/point/rectRotShadow.png new file mode 100644 index 000000000..e0827fd49 Binary files /dev/null and b/test/fixtures/point/rectRotShadow.png differ diff --git a/test/fixtures/point/rectRounded.js b/test/fixtures/point/rectRounded.js new file mode 100644 index 000000000..abfe53223 --- /dev/null +++ b/test/fixtures/point/rectRounded.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rectRoundedSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 3, + pointStyle: 'rectRounded', + radius: 10 + }, + rectRounded: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 5, + pointStyle: 'rectRounded', + radius: 25 + }, + rectRoundedBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 15, + pointStyle: 'rectRounded', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/rectRounded.png b/test/fixtures/point/rectRounded.png new file mode 100644 index 000000000..ddc227f77 Binary files /dev/null and b/test/fixtures/point/rectRounded.png differ diff --git a/test/fixtures/point/rectRoundedShadow.js b/test/fixtures/point/rectRoundedShadow.js new file mode 100644 index 000000000..be53c0d8a --- /dev/null +++ b/test/fixtures/point/rectRoundedShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rectRoundedSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 3, + pointStyle: 'rectRounded', + radius: 10, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + rectRounded: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 5, + pointStyle: 'rectRounded', + radius: 25, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + rectRoundedBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 15, + pointStyle: 'rectRounded', + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/rectRoundedShadow.png b/test/fixtures/point/rectRoundedShadow.png new file mode 100644 index 000000000..8e6a75ac8 Binary files /dev/null and b/test/fixtures/point/rectRoundedShadow.png differ diff --git a/test/fixtures/point/rectShadow.js b/test/fixtures/point/rectShadow.js new file mode 100644 index 000000000..0fe1c0f70 --- /dev/null +++ b/test/fixtures/point/rectShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rectSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 3, + pointStyle: 'rect', + radius: 10, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + rect: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 5, + pointStyle: 'rect', + radius: 25, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + rectBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 15, + pointStyle: 'rect', + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/rectShadow.png b/test/fixtures/point/rectShadow.png new file mode 100644 index 000000000..7631b1d50 Binary files /dev/null and b/test/fixtures/point/rectShadow.png differ diff --git a/test/fixtures/point/rotation.js b/test/fixtures/point/rotation.js new file mode 100644 index 000000000..3f953747b --- /dev/null +++ b/test/fixtures/point/rotation.js @@ -0,0 +1,60 @@ +module.exports = { + tolerance: 0.0075, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + dash: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'dash', + radius: 7, + rotation: 20 + }, + triangle: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + pointStyle: 'triangle', + radius: 50, + rotation: 110 + }, + rectRounded: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + pointStyle: 'rectRounded', + radius: 50, + rotation: 300 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/rotation.png b/test/fixtures/point/rotation.png new file mode 100644 index 000000000..b2e9340d0 Binary files /dev/null and b/test/fixtures/point/rotation.png differ diff --git a/test/fixtures/point/scriptableOptions.js b/test/fixtures/point/scriptableOptions.js index 9f6ff6b26..f4289bd6f 100644 --- a/test/fixtures/point/scriptableOptions.js +++ b/test/fixtures/point/scriptableOptions.js @@ -1,4 +1,5 @@ module.exports = { + tolerance: 0.0055, config: { type: 'scatter', options: { @@ -17,9 +18,8 @@ module.exports = { plugins: { legend: false, annotation: { - drawTime: 'afterDraw', annotations: { - point: { + circle: { type: 'point', xScaleID() { return 'x'; @@ -28,10 +28,10 @@ module.exports = { return 'y'; }, xValue() { - return 1; + return -5; }, yValue() { - return 1; + return -5; }, backgroundColor() { return 'rgba(101, 33, 171, 0.5)'; @@ -48,6 +48,78 @@ module.exports = { radius() { return 50; } + }, + triangle: { + type: 'point', + xScaleID() { + return 'x'; + }, + yScaleID() { + return 'y'; + }, + xValue() { + return 0; + }, + yValue() { + return 0; + }, + backgroundColor() { + return 'rgba(101, 33, 171, 0.5)'; + }, + borderColor() { + return 'rgb(101, 33, 171)'; + }, + borderWidth() { + return 3; + }, + borderDash() { + return [6, 6]; + }, + radius() { + return 50; + }, + rotation() { + return 45; + }, + pointStyle() { + return 'triangle'; + } + }, + rect: { + type: 'point', + xScaleID() { + return 'x'; + }, + yScaleID() { + return 'y'; + }, + xValue() { + return 5; + }, + yValue() { + return 5; + }, + backgroundColor() { + return 'rgba(101, 33, 171, 0.5)'; + }, + borderColor() { + return 'rgb(101, 33, 171)'; + }, + borderWidth() { + return 5; + }, + borderDash() { + return [6, 6]; + }, + radius() { + return 50; + }, + rotation() { + return 20; + }, + pointStyle() { + return 'rect'; + } } } } diff --git a/test/fixtures/point/scriptableOptions.png b/test/fixtures/point/scriptableOptions.png index 214da0ce7..f8b430792 100644 Binary files a/test/fixtures/point/scriptableOptions.png and b/test/fixtures/point/scriptableOptions.png differ diff --git a/test/fixtures/point/shadow.js b/test/fixtures/point/shadow.js new file mode 100644 index 000000000..7891c4ae7 --- /dev/null +++ b/test/fixtures/point/shadow.js @@ -0,0 +1,39 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: -10, + max: 10 + }, + y: { + display: false, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point: { + type: 'point', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/shadow.png b/test/fixtures/point/shadow.png new file mode 100644 index 000000000..63ca14dce Binary files /dev/null and b/test/fixtures/point/shadow.png differ diff --git a/test/fixtures/point/shadowColors.js b/test/fixtures/point/shadowColors.js new file mode 100644 index 000000000..ecfeb1d1a --- /dev/null +++ b/test/fixtures/point/shadowColors.js @@ -0,0 +1,40 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: -10, + max: 10 + }, + y: { + display: false, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point: { + type: 'point', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 5, + radius: 50, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/shadowColors.png b/test/fixtures/point/shadowColors.png new file mode 100644 index 000000000..2aac474bb Binary files /dev/null and b/test/fixtures/point/shadowColors.png differ diff --git a/test/fixtures/point/shadowColorsBorderWidth0.js b/test/fixtures/point/shadowColorsBorderWidth0.js new file mode 100644 index 000000000..34794cc8f --- /dev/null +++ b/test/fixtures/point/shadowColorsBorderWidth0.js @@ -0,0 +1,40 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: -10, + max: 10 + }, + y: { + display: false, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + point: { + type: 'point', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 0, + radius: 50, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/shadowColorsBorderWidth0.png b/test/fixtures/point/shadowColorsBorderWidth0.png new file mode 100644 index 000000000..49bc89e93 Binary files /dev/null and b/test/fixtures/point/shadowColorsBorderWidth0.png differ diff --git a/test/fixtures/point/star.js b/test/fixtures/point/star.js new file mode 100644 index 000000000..fc3d92d60 --- /dev/null +++ b/test/fixtures/point/star.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + starSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 3, + pointStyle: 'star', + radius: 10 + }, + star: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 5, + pointStyle: 'star', + radius: 25 + }, + starBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 15, + pointStyle: 'star', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/star.png b/test/fixtures/point/star.png new file mode 100644 index 000000000..15a5e2ff2 Binary files /dev/null and b/test/fixtures/point/star.png differ diff --git a/test/fixtures/point/starShadow.js b/test/fixtures/point/starShadow.js new file mode 100644 index 000000000..5efcf0623 --- /dev/null +++ b/test/fixtures/point/starShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + starSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 3, + pointStyle: 'star', + radius: 10, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + star: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 5, + pointStyle: 'star', + radius: 25, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + starBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 15, + pointStyle: 'star', + radius: 50, + borderShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/starShadow.png b/test/fixtures/point/starShadow.png new file mode 100644 index 000000000..0a464ccac Binary files /dev/null and b/test/fixtures/point/starShadow.png differ diff --git a/test/fixtures/point/triangle.js b/test/fixtures/point/triangle.js new file mode 100644 index 000000000..026aac041 --- /dev/null +++ b/test/fixtures/point/triangle.js @@ -0,0 +1,57 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangleSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 3, + pointStyle: 'triangle', + radius: 10 + }, + triangle: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 5, + pointStyle: 'triangle', + radius: 25 + }, + triangleBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 15, + pointStyle: 'triangle', + radius: 50 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/triangle.png b/test/fixtures/point/triangle.png new file mode 100644 index 000000000..20ca4829d Binary files /dev/null and b/test/fixtures/point/triangle.png differ diff --git a/test/fixtures/point/triangleShadow.js b/test/fixtures/point/triangleShadow.js new file mode 100644 index 000000000..bfb66c6c7 --- /dev/null +++ b/test/fixtures/point/triangleShadow.js @@ -0,0 +1,69 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangleSmall: { + type: 'point', + xValue: -5, + yValue: -5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 3, + pointStyle: 'triangle', + radius: 10, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + triangle: { + type: 'point', + xValue: 0, + yValue: 0, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 5, + pointStyle: 'triangle', + radius: 25, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + }, + triangleBig: { + type: 'point', + xValue: 5, + yValue: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 15, + pointStyle: 'triangle', + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 15, + shadowOffsetY: 15 + } + } + } + } + } + } +}; diff --git a/test/fixtures/point/triangleShadow.png b/test/fixtures/point/triangleShadow.png new file mode 100644 index 000000000..f31e0d16d Binary files /dev/null and b/test/fixtures/point/triangleShadow.png differ diff --git a/test/fixtures/polygon/adjust.js b/test/fixtures/polygon/adjust.js new file mode 100644 index 000000000..ffc20ce2b --- /dev/null +++ b/test/fixtures/polygon/adjust.js @@ -0,0 +1,139 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + polygon1: { + type: 'polygon', + xValue: 4.5, + yValue: 4.5, + xAdjust: 30, + yAdjust: 40, + sides: 6, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center1: { + type: 'label', + xValue: 4.5, + yValue: 4.5, + content: 'x: +30, y: +40', + position: 'start' + }, + point1: { + type: 'point', + xValue: 4.5, + yValue: 4.5, + backgroundColor: 'black', + radius: 3 + }, + polygon2: { + type: 'polygon', + xValue: -4.5, + yValue: 4.5, + xAdjust: 30, + yAdjust: -40, + sides: 6, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center2: { + type: 'label', + xValue: -4.5, + yValue: 4.5, + content: 'x: +30, y: -40', + position: { + x: 'start', + y: 'end' + } + }, + point2: { + type: 'point', + xValue: -4.5, + yValue: 4.5, + backgroundColor: 'black', + radius: 3 + }, + polygon3: { + type: 'polygon', + xValue: -4.5, + yValue: -4.5, + xAdjust: -30, + yAdjust: -40, + sides: 6, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center3: { + type: 'label', + xValue: -4.5, + yValue: -4.5, + content: 'x: -30, y: -40', + position: 'end', + }, + point3: { + type: 'point', + xValue: -4.5, + yValue: -4.5, + backgroundColor: 'black', + radius: 3 + }, + polygon4: { + type: 'polygon', + xValue: 4.5, + yValue: -4.5, + xAdjust: -30, + yAdjust: 40, + sides: 6, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 80 + }, + center4: { + type: 'label', + xValue: 4.5, + yValue: -4.5, + content: 'x: -30, y: +40', + position: { + x: 'end', + y: 'start' + } + }, + point4: { + type: 'point', + xValue: 4.5, + yValue: -4.5, + backgroundColor: 'black', + radius: 3 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/adjust.png b/test/fixtures/polygon/adjust.png new file mode 100644 index 000000000..05009fe80 Binary files /dev/null and b/test/fixtures/polygon/adjust.png differ diff --git a/test/fixtures/polygon/borderDash.js b/test/fixtures/polygon/borderDash.js new file mode 100644 index 000000000..acc0c7394 --- /dev/null +++ b/test/fixtures/polygon/borderDash.js @@ -0,0 +1,61 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangle: { + type: 'polygon', + xValue: -6, + yValue: -6, + backgroundColor: 'rgba(255, 99, 132, 0.25', + borderColor: 'black', + borderDash: [2, 2], + borderWidth: 6, + radius: 50 + }, + pentagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderDash: [4, 4], + borderWidth: 6, + radius: 50 + }, + rhombus: { + type: 'polygon', + xValue: 6, + yValue: 6, + sides: 4, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderDash: [6, 6], + borderWidth: 5, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/borderDash.png b/test/fixtures/polygon/borderDash.png new file mode 100644 index 000000000..93ecf3268 Binary files /dev/null and b/test/fixtures/polygon/borderDash.png differ diff --git a/test/fixtures/polygon/boundToPoint0.js b/test/fixtures/polygon/boundToPoint0.js new file mode 100644 index 000000000..6d224ce95 --- /dev/null +++ b/test/fixtures/polygon/boundToPoint0.js @@ -0,0 +1,39 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -4, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + polygon: { + type: 'polygon', + xValue: 0, + yValue: 0, + sides: 5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 15, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/boundToPoint0.png b/test/fixtures/polygon/boundToPoint0.png new file mode 100644 index 000000000..b1b718825 Binary files /dev/null and b/test/fixtures/polygon/boundToPoint0.png differ diff --git a/test/fixtures/polygon/boxLocation.js b/test/fixtures/polygon/boxLocation.js new file mode 100644 index 000000000..b97eb5129 --- /dev/null +++ b/test/fixtures/polygon/boxLocation.js @@ -0,0 +1,93 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangle1: { + type: 'polygon', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + box1: { + type: 'box', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + }, + circle1: { + type: 'point', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + radius: NaN + }, + triangle2: { + type: 'polygon', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + box2: { + type: 'box', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + }, + circle2: { + type: 'point', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + radius: NaN + }, + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/boxLocation.png b/test/fixtures/polygon/boxLocation.png new file mode 100644 index 000000000..f546dde25 Binary files /dev/null and b/test/fixtures/polygon/boxLocation.png differ diff --git a/test/fixtures/polygon/boxLocationAdjust.js b/test/fixtures/polygon/boxLocationAdjust.js new file mode 100644 index 000000000..4dc8ed67b --- /dev/null +++ b/test/fixtures/polygon/boxLocationAdjust.js @@ -0,0 +1,167 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + polygon1: { + type: 'polygon', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + xAdjust: 30, + yAdjust: 40, + sides: 8, + rotation: 22.5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center1: { + type: 'label', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + content: 'x: +30, y: +40', + position: 'start' + }, + pointCenter1: { + type: 'point', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'black', + radius: 3 + }, + polygon2: { + type: 'polygon', + xMin: -1, + yMin: 1, + xMax: -8, + yMax: 8, + xAdjust: 30, + yAdjust: -40, + sides: 8, + rotation: 22.5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center2: { + type: 'label', + xMin: -1, + yMin: 1, + xMax: -8, + yMax: 8, + content: 'x: +30, y: -40', + position: { + x: 'start', + y: 'end' + } + }, + pointCenter2: { + type: 'point', + xMin: -1, + yMin: 1, + xMax: -8, + yMax: 8, + backgroundColor: 'black', + radius: 3 + }, + polygon3: { + type: 'polygon', + xMin: -1, + yMin: -1, + xMax: -8, + yMax: -8, + xAdjust: -30, + yAdjust: -40, + sides: 8, + rotation: 22.5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center3: { + type: 'label', + xMin: -1, + yMin: -1, + xMax: -8, + yMax: -8, + content: 'x: -30, y: -40', + position: 'end' + }, + pointCenter3: { + type: 'point', + xMin: -1, + yMin: -1, + xMax: -8, + yMax: -8, + backgroundColor: 'black', + radius: 3 + }, + polygon4: { + type: 'polygon', + xMin: 1, + yMin: -1, + xMax: 8, + yMax: -8, + xAdjust: -30, + yAdjust: 40, + sides: 8, + rotation: 22.5, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: NaN + }, + center4: { + type: 'label', + xMin: 1, + yMin: -1, + xMax: 8, + yMax: -8, + content: 'x: -30, y: +40', + position: { + x: 'end', + y: 'start' + } + }, + pointCenter4: { + type: 'point', + xMin: 1, + yMin: -1, + xMax: 8, + yMax: -8, + backgroundColor: 'black', + radius: 3 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/boxLocationAdjust.png b/test/fixtures/polygon/boxLocationAdjust.png new file mode 100644 index 000000000..869f66027 Binary files /dev/null and b/test/fixtures/polygon/boxLocationAdjust.png differ diff --git a/test/fixtures/polygon/boxLocationRadius.js b/test/fixtures/polygon/boxLocationRadius.js new file mode 100644 index 000000000..f78e2d53f --- /dev/null +++ b/test/fixtures/polygon/boxLocationRadius.js @@ -0,0 +1,71 @@ +module.exports = { + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangle1: { + type: 'polygon', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 10 + }, + box1: { + type: 'box', + xMin: 1, + yMin: 1, + xMax: 8, + yMax: 8, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + }, + triangle2: { + type: 'polygon', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'rgba(101, 33, 171, 0.5)', + borderColor: 'rgb(101, 33, 171)', + borderWidth: 2, + radius: 130 + }, + box2: { + type: 'box', + xMin: -8, + yMin: -8, + xMax: 1, + yMax: 1, + backgroundColor: 'transparent', + borderColor: 'red', + borderWidth: 1, + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/boxLocationRadius.png b/test/fixtures/polygon/boxLocationRadius.png new file mode 100644 index 000000000..2cd78a3c1 Binary files /dev/null and b/test/fixtures/polygon/boxLocationRadius.png differ diff --git a/test/fixtures/polygon/decagon.js b/test/fixtures/polygon/decagon.js new file mode 100644 index 000000000..4ac8a5c89 --- /dev/null +++ b/test/fixtures/polygon/decagon.js @@ -0,0 +1,40 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + decagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 10, + backgroundColor: 'rgba(255, 99, 132, 0.25)', + borderColor: 'rgb(255, 99, 132)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/decagon.png b/test/fixtures/polygon/decagon.png new file mode 100644 index 000000000..abcb28096 Binary files /dev/null and b/test/fixtures/polygon/decagon.png differ diff --git a/test/fixtures/polygon/heptagon.js b/test/fixtures/polygon/heptagon.js new file mode 100644 index 000000000..3bf1fb477 --- /dev/null +++ b/test/fixtures/polygon/heptagon.js @@ -0,0 +1,40 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + heptagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 7, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/heptagon.png b/test/fixtures/polygon/heptagon.png new file mode 100644 index 000000000..bb8a01dfd Binary files /dev/null and b/test/fixtures/polygon/heptagon.png differ diff --git a/test/fixtures/polygon/hexagon.js b/test/fixtures/polygon/hexagon.js new file mode 100644 index 000000000..a6b6d1d1e --- /dev/null +++ b/test/fixtures/polygon/hexagon.js @@ -0,0 +1,40 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + hexagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 6, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/hexagon.png b/test/fixtures/polygon/hexagon.png new file mode 100644 index 000000000..b78e7be42 Binary files /dev/null and b/test/fixtures/polygon/hexagon.png differ diff --git a/test/fixtures/polygon/hexagonWithPoints.js b/test/fixtures/polygon/hexagonWithPoints.js new file mode 100644 index 000000000..04dbf6b43 --- /dev/null +++ b/test/fixtures/polygon/hexagonWithPoints.js @@ -0,0 +1,52 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: false, + min: -10, + max: 10 + }, + y: { + display: false, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + drawTime: 'afterDraw', + annotations: { + octagon: { + type: 'polygon', + xScaleID: 'x', + yScaleID: 'y', + xValue: 1, + yValue: 1, + sides: 6, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + point: { + radius: 3, + backgroundColor: 'red', + borderColor: 'black', + borderWidth: 1 + } + } + } + } + } + } + }, + options: { + canvas: { + width: 256, + height: 256 + } + } +}; diff --git a/test/fixtures/polygon/hexagonWithPoints.png b/test/fixtures/polygon/hexagonWithPoints.png new file mode 100644 index 000000000..97a4cddc2 Binary files /dev/null and b/test/fixtures/polygon/hexagonWithPoints.png differ diff --git a/test/fixtures/polygon/invalidSides.js b/test/fixtures/polygon/invalidSides.js new file mode 100644 index 000000000..d67fb51ef --- /dev/null +++ b/test/fixtures/polygon/invalidSides.js @@ -0,0 +1,50 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + sides0: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 0, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + }, + sidesString: { + type: 'polygon', + xValue: 8, + yValue: 8, + sides: 'wrong', + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/invalidSides.png b/test/fixtures/polygon/invalidSides.png new file mode 100644 index 000000000..97194761b Binary files /dev/null and b/test/fixtures/polygon/invalidSides.png differ diff --git a/test/fixtures/polygon/nonagon.js b/test/fixtures/polygon/nonagon.js new file mode 100644 index 000000000..aa4bdabc7 --- /dev/null +++ b/test/fixtures/polygon/nonagon.js @@ -0,0 +1,40 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + nonagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 9, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/nonagon.png b/test/fixtures/polygon/nonagon.png new file mode 100644 index 000000000..1694b907d Binary files /dev/null and b/test/fixtures/polygon/nonagon.png differ diff --git a/test/fixtures/polygon/octagon.js b/test/fixtures/polygon/octagon.js new file mode 100644 index 000000000..f8a17e226 --- /dev/null +++ b/test/fixtures/polygon/octagon.js @@ -0,0 +1,40 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + octagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 8, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/octagon.png b/test/fixtures/polygon/octagon.png new file mode 100644 index 000000000..c39164b75 Binary files /dev/null and b/test/fixtures/polygon/octagon.png differ diff --git a/test/fixtures/polygon/pentagon.js b/test/fixtures/polygon/pentagon.js new file mode 100644 index 000000000..ec42d9c4f --- /dev/null +++ b/test/fixtures/polygon/pentagon.js @@ -0,0 +1,40 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + pentagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/pentagon.png b/test/fixtures/polygon/pentagon.png new file mode 100644 index 000000000..8cf2c9fca Binary files /dev/null and b/test/fixtures/polygon/pentagon.png differ diff --git a/test/fixtures/polygon/pentagonShadow.js b/test/fixtures/polygon/pentagonShadow.js new file mode 100644 index 000000000..8772f33a2 --- /dev/null +++ b/test/fixtures/polygon/pentagonShadow.js @@ -0,0 +1,44 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + pentagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/pentagonShadow.png b/test/fixtures/polygon/pentagonShadow.png new file mode 100644 index 000000000..3e5185f4a Binary files /dev/null and b/test/fixtures/polygon/pentagonShadow.png differ diff --git a/test/fixtures/polygon/pentagonShadowColors.js b/test/fixtures/polygon/pentagonShadowColors.js new file mode 100644 index 000000000..2c9e5fda6 --- /dev/null +++ b/test/fixtures/polygon/pentagonShadowColors.js @@ -0,0 +1,45 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + pentagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 3, + radius: 50, + backgroundShadowColor: 'black', + borderShadowColor: 'orange', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/pentagonShadowColors.png b/test/fixtures/polygon/pentagonShadowColors.png new file mode 100644 index 000000000..9969f9ae2 Binary files /dev/null and b/test/fixtures/polygon/pentagonShadowColors.png differ diff --git a/test/fixtures/polygon/rhombus.js b/test/fixtures/polygon/rhombus.js new file mode 100644 index 000000000..3e63b5f1d --- /dev/null +++ b/test/fixtures/polygon/rhombus.js @@ -0,0 +1,40 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rhombus: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 4, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/rhombus.png b/test/fixtures/polygon/rhombus.png new file mode 100644 index 000000000..d3c8eeba9 Binary files /dev/null and b/test/fixtures/polygon/rhombus.png differ diff --git a/test/fixtures/polygon/rhombusShadow.js b/test/fixtures/polygon/rhombusShadow.js new file mode 100644 index 000000000..759136fbc --- /dev/null +++ b/test/fixtures/polygon/rhombusShadow.js @@ -0,0 +1,44 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + rhombus: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: 4, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/rhombusShadow.png b/test/fixtures/polygon/rhombusShadow.png new file mode 100644 index 000000000..135f70a44 Binary files /dev/null and b/test/fixtures/polygon/rhombusShadow.png differ diff --git a/test/fixtures/polygon/rotation.js b/test/fixtures/polygon/rotation.js new file mode 100644 index 000000000..a80ca6384 --- /dev/null +++ b/test/fixtures/polygon/rotation.js @@ -0,0 +1,51 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangle: { + type: 'polygon', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + rotation: 180 + }, + pentagon: { + type: 'polygon', + xValue: 6, + yValue: 6, + sides: 5, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + rotation: 180 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/rotation.png b/test/fixtures/polygon/rotation.png new file mode 100644 index 000000000..23cb3cb18 Binary files /dev/null and b/test/fixtures/polygon/rotation.png differ diff --git a/test/fixtures/polygon/scriptableOptions.js b/test/fixtures/polygon/scriptableOptions.js new file mode 100644 index 000000000..673dcfb96 --- /dev/null +++ b/test/fixtures/polygon/scriptableOptions.js @@ -0,0 +1,60 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangle: { + type: 'polygon', + xValue: -6, + yValue: -6, + backgroundColor: () => 'rgba(255, 99, 132, 0.25)', + borderColor: () => 'black', + borderWidth: () => 2, + radius: () => 50 + }, + pentagon: { + type: 'polygon', + xValue: 1, + yValue: 1, + sides: () => 5, + backgroundColor: () => 'rgba(101, 33, 171, 0.5)', + borderColor: () => 'red', + borderDash: () => [4, 4], + borderWidth: () => 3, + radius: () => 50 + }, + rhombus: { + type: 'polygon', + xValue: 6, + yValue: 6, + sides: () => 4, + backgroundColor: () => 'rgba(153, 153, 102, 0.5)', + borderColor: () => 'green', + borderWidth: () => 1, + radius: () => 60 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/scriptableOptions.png b/test/fixtures/polygon/scriptableOptions.png new file mode 100644 index 000000000..7aca88310 Binary files /dev/null and b/test/fixtures/polygon/scriptableOptions.png differ diff --git a/test/fixtures/polygon/square.js b/test/fixtures/polygon/square.js new file mode 100644 index 000000000..f8fea15da --- /dev/null +++ b/test/fixtures/polygon/square.js @@ -0,0 +1,41 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + square: { + type: 'polygon', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + sides: 4, + rotation: 45 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/square.png b/test/fixtures/polygon/square.png new file mode 100644 index 000000000..b4b78157d Binary files /dev/null and b/test/fixtures/polygon/square.png differ diff --git a/test/fixtures/polygon/squareShadow.js b/test/fixtures/polygon/squareShadow.js new file mode 100644 index 000000000..abdf98db1 --- /dev/null +++ b/test/fixtures/polygon/squareShadow.js @@ -0,0 +1,45 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + square: { + type: 'polygon', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + sides: 4, + rotation: 45, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/squareShadow.png b/test/fixtures/polygon/squareShadow.png new file mode 100644 index 000000000..23090c368 Binary files /dev/null and b/test/fixtures/polygon/squareShadow.png differ diff --git a/test/fixtures/polygon/triangle.js b/test/fixtures/polygon/triangle.js new file mode 100644 index 000000000..e17451498 --- /dev/null +++ b/test/fixtures/polygon/triangle.js @@ -0,0 +1,39 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangle: { + type: 'polygon', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/triangle.png b/test/fixtures/polygon/triangle.png new file mode 100644 index 000000000..0ca4c6410 Binary files /dev/null and b/test/fixtures/polygon/triangle.png differ diff --git a/test/fixtures/polygon/triangleShadow.js b/test/fixtures/polygon/triangleShadow.js new file mode 100644 index 000000000..1610f5198 --- /dev/null +++ b/test/fixtures/polygon/triangleShadow.js @@ -0,0 +1,43 @@ +module.exports = { + tolerance: 0.0055, + config: { + type: 'scatter', + options: { + scales: { + x: { + display: true, + min: -10, + max: 10 + }, + y: { + display: true, + min: -10, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations: { + triangle: { + type: 'polygon', + xValue: 1, + yValue: 1, + backgroundColor: 'rgba(153, 153, 102, 0.5)', + borderColor: 'rgb(153, 153, 102)', + borderWidth: 1, + radius: 50, + backgroundShadowColor: 'black', + shadowBlur: 3, + shadowOffsetX: 10, + shadowOffsetY: 10 + } + } + } + } + } + }, + options: { + spriteText: true + } +}; diff --git a/test/fixtures/polygon/triangleShadow.png b/test/fixtures/polygon/triangleShadow.png new file mode 100644 index 000000000..109054294 Binary files /dev/null and b/test/fixtures/polygon/triangleShadow.png differ diff --git a/test/index.js b/test/index.js index 8855511fa..11cd7351d 100644 --- a/test/index.js +++ b/test/index.js @@ -1,9 +1,18 @@ import {acquireChart, addMatchers, releaseCharts, specsFromFixtures, triggerMouseEvent, afterEvent} from 'chartjs-test-utils'; +import {testEvents} from './events'; +import {createCanvas, getAnnotationElements, scatter10x10, stringifyObject} from './utils'; +import * as helpers from '../src/helpers'; +window.helpers = helpers; window.devicePixelRatio = 1; window.acquireChart = acquireChart; window.afterEvent = afterEvent; window.triggerMouseEvent = triggerMouseEvent; +window.testEvents = testEvents; +window.createCanvas = createCanvas; +window.getAnnotationElements = getAnnotationElements; +window.scatter10x10 = scatter10x10; +window.stringifyObject = stringifyObject; jasmine.fixtures = specsFromFixtures; @@ -14,3 +23,5 @@ beforeEach(function() { afterEach(function() { releaseCharts(); }); + +console.warn('Testing with chart.js v' + Chart.version); diff --git a/test/integration/.eslintrc.yml b/test/integration/.eslintrc.yml new file mode 100644 index 000000000..b4e00d7d2 --- /dev/null +++ b/test/integration/.eslintrc.yml @@ -0,0 +1,2 @@ +rules: + no-console: "off" diff --git a/test/integration/integration-test.js b/test/integration/integration-test.js new file mode 100644 index 000000000..6fbcc42c3 --- /dev/null +++ b/test/integration/integration-test.js @@ -0,0 +1,46 @@ +'use strict'; + +const os = require('os'); +const fs = require('fs-extra'); +const path = require('path'); +const childProcess = require('child_process'); + +const {describe, it} = require('mocha'); + +function exec(command, options = {}) { + const output = childProcess.execSync(command, { + encoding: 'utf-8', + ...options, + }); + return output && output.trimEnd(); +} + +describe('Integration Tests', () => { + const tmpDir = path.join(os.tmpdir(), 'chartjs-plugin-annotation-tmp'); + fs.rmSync(tmpDir, {recursive: true, force: true}); + fs.mkdirSync(tmpDir); + + const distDir = path.resolve('./'); + const archiveName = exec(`npm --quiet pack ${distDir}`, {cwd: tmpDir}); + fs.renameSync( + path.join(tmpDir, archiveName), + path.join(tmpDir, 'plugin.tgz'), + ); + + function testOnNodeProject(projectName) { + const projectPath = path.join(__dirname, projectName); + + const packageJSONPath = path.join(projectPath, 'package.json'); + const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, 'utf-8')); + + it(packageJSON.description, () => { + const cwd = path.join(tmpDir, projectName); + fs.copySync(projectPath, cwd); + + exec('npm --quiet install', {cwd, stdio: 'inherit'}); + exec('npm --quiet test', {cwd, stdio: 'inherit'}); + }).timeout(5 * 60 * 1000); + } + + testOnNodeProject('ts'); +}); diff --git a/test/integration/ts/basic.ts b/test/integration/ts/basic.ts new file mode 100644 index 000000000..8ed682633 --- /dev/null +++ b/test/integration/ts/basic.ts @@ -0,0 +1,34 @@ +import { Chart } from 'chart.js'; +import Annotation from 'chartjs-plugin-annotation'; + +Chart.register(Annotation); + +const chart = new Chart('id', { + type: 'bar', + data: { + labels: [], + datasets: [{ + data: [] + }] + }, + options: { + plugins: { + annotation: { + clip: false, + annotations: [{ + type: 'line', + label: { + content: ['test', 'multiple'], + width: '100%' + } + }, { + type: 'box', + backgroundColor: 'red', + borderColor: (ctx, options) => options.type === 'box' ? 'red' : 'green', + } + ] + } + } + }, + plugins: [Annotation] +}); diff --git a/test/integration/ts/package.json b/test/integration/ts/package.json new file mode 100644 index 000000000..743ac29af --- /dev/null +++ b/test/integration/ts/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "description": "chartjs-plugin-annotation should compile with all supported TS versions", + "scripts": { + "test": "node test.js" + }, + "dependencies": { + "chart.js": "^3.1.0", + "chartjs-plugin-annotation": "file:../plugin.tgz", + "typescript-3.8": "npm:typescript@3.8.x", + "typescript-3.9": "npm:typescript@3.9.x", + "typescript-4.0": "npm:typescript@4.0.x", + "typescript-4.1": "npm:typescript@4.1.x", + "typescript-4.2": "npm:typescript@4.2.x", + "typescript-4.3": "npm:typescript@4.3.x", + "typescript-4.4": "npm:typescript@4.4.x", + "typescript-4.5": "npm:typescript@4.5.x" + } +} diff --git a/test/integration/ts/test.js b/test/integration/ts/test.js new file mode 100644 index 000000000..2dc0fea6d --- /dev/null +++ b/test/integration/ts/test.js @@ -0,0 +1,19 @@ +'use strict'; + +const path = require('path'); +const childProcess = require('child_process'); + +const {dependencies} = require('./package.json'); + +const tsVersions = Object.keys(dependencies) + .filter((pkg) => pkg.startsWith('typescript-')) + .sort((a, b) => b.localeCompare(a)); + +for (const version of tsVersions) { + console.log(`Testing on ${version} ...`); + childProcess.execSync('node ' + tscPath(version), {stdio: 'inherit'}); +} + +function tscPath(version) { + return path.join(__dirname, 'node_modules', version, 'bin/tsc'); +} diff --git a/test/integration/ts/tsconfig.json b/test/integration/ts/tsconfig.json new file mode 100644 index 000000000..cc1fc5314 --- /dev/null +++ b/test/integration/ts/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES6", + "moduleResolution": "Node", + "alwaysStrict": true, + "noEmit": true + } +} diff --git a/test/seed-reporter.js b/test/seed-reporter.js new file mode 100644 index 000000000..6328b0bcd --- /dev/null +++ b/test/seed-reporter.js @@ -0,0 +1,13 @@ +const SeedReporter = function(baseReporterDecorator) { + baseReporterDecorator(this); + + this.onBrowserComplete = function(browser, result) { + if (result.order && result.order.random && result.order.seed) { + this.write('%s: Randomized with seed %s\n', browser, result.order.seed); + } + }; +}; + +module.exports = { + 'reporter:jasmine-seed': ['type', SeedReporter] +}; diff --git a/test/specs/annotation.spec.js b/test/specs/annotation.spec.js new file mode 100644 index 000000000..18e7414cc --- /dev/null +++ b/test/specs/annotation.spec.js @@ -0,0 +1,41 @@ +describe('Annotation plugin', function() { + it('should emit console warning when unknown element type is used', function() { + const origWarn = console.warn; + console.warn = jasmine.createSpy('warn'); + + acquireChart({ + type: 'line', + options: { + plugins: { + annotation: { + annotations: [{ + id: 'test', + type: 'invalid' + }] + } + } + } + }); + + expect(console.warn).toHaveBeenCalledWith('Unknown annotation type: \'invalid\', defaulting to \'line\''); + + acquireChart({ + type: 'line', + options: { + plugins: { + annotation: { + annotations: { + test: { + type: 'invalid2' + } + } + } + } + } + }); + + expect(console.warn).toHaveBeenCalledWith('Unknown annotation type: \'invalid2\', defaulting to \'line\''); + + console.warn = origWarn; + }); +}); diff --git a/test/specs/box.spec.js b/test/specs/box.spec.js index 9c528fc5b..b2d8b2dd3 100644 --- a/test/specs/box.spec.js +++ b/test/specs/box.spec.js @@ -1,3 +1,48 @@ describe('Box annotation', function() { describe('auto', jasmine.fixtures('box')); + + describe('inRange', function() { + const annotation = { + type: 'box', + xMin: 2, + yMin: 4, + xMax: 8, + yMax: 6, + borderWidth: 0, + rotation: 0 + }; + + const chart = window.scatter10x10({test: annotation}); + const element = window.getAnnotationElements(chart)[0]; + + it('should return true inside element', function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + for (const x of [element.x - halfBorder, element.x + element.width / 2, element.x + element.width + halfBorder]) { + for (const y of [element.y - halfBorder, element.y + element.height / 2, element.y + element.height + halfBorder]) { + expect(element.inRange(x, y)).toEqual(true); + } + } + } + }); + + it('should return false outside element', function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + + for (const x of [element.x - halfBorder - 1, element.x + element.width + halfBorder + 1]) { + for (const y of [element.y, element.y + element.height / 2, element.y + element.height]) { + expect(element.inRange(x, y)).toEqual(false); + } + } + for (const x of [element.x, element.x + element.width / 2, element.x + element.width]) { + for (const y of [element.y - halfBorder - 1, element.y + element.height + halfBorder + 1]) { + expect(element.inRange(x, y)).toEqual(false); + } + } + } + }); + }); }); diff --git a/test/specs/display.spec.js b/test/specs/display.spec.js deleted file mode 100644 index 3e3b4c671..000000000 --- a/test/specs/display.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -describe('Display options', function() { - - it('should not throw any exception', function() { - function createAndUpdateChart() { - const config = { - type: 'line', - data: { - labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], - datasets: [ - { - label: '# of Votes', - data: [12, 19, 3, 5, 2, 3] - } - ] - }, - options: { - scales: { - y: { - beginAtZero: true - } - }, - plugins: { - annotation: { - drawTime: 'afterDatasetsDraw', - dblClickSpeed: 350, - annotations: { - my: { - display: true, - type: 'line', - scaleID: 'y', - value: 10, - borderColor: 'red', - borderWidth: 2 - } - } - } - } - } - }; - - var chart = acquireChart(config); - - chart.update(); - chart.options.plugins.annotation.annotations.my.display = false; - chart.update(); - chart.options.plugins.annotation.annotations.my.display = function() { - return true; - }; - chart.update(); - chart.options.plugins.annotation.annotations.my.display = function() { - return false; - }; - chart.update(); - chart.options.plugins.annotation.annotations.my.display = function() { - return null; - }; - chart.update(); - } - expect(createAndUpdateChart).not.toThrow(); - }); -}); diff --git a/test/specs/ellipse.spec.js b/test/specs/ellipse.spec.js index 9f112a3da..f144d6d50 100644 --- a/test/specs/ellipse.spec.js +++ b/test/specs/ellipse.spec.js @@ -1,3 +1,74 @@ describe('Ellipse annotation', function() { describe('auto', jasmine.fixtures('ellipse')); + + describe('inRange', function() { + const rotated = window.helpers.rotated; + + for (const rotation of [0, 45, 90, 135, 180, 225, 270, 315]) { + const annotation = { + type: 'ellipse', + xMin: 3, + yMin: 4, + xMax: 7, + yMax: 5, + borderWidth: 0, + rotation + }; + + const chart = window.scatter10x10({test: annotation}); + const element = window.getAnnotationElements(chart)[0]; + const center = element.getCenterPoint(); + const xRadius = element.width / 2; + const yRadius = element.height / 2; + + it(`should return true when point is inside element\n{x: ${center.x}, y: ${center.y}, xRadius: ${xRadius.toFixed(1)}, yRadius: ${yRadius.toFixed(1)}}`, function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + + for (const angle of [0, 45, 90, 135, 180, 225, 270, 315]) { + const rad = angle / 180 * Math.PI; + + const {x, y} = rotated({ + x: center.x + Math.cos(rad) * (xRadius + halfBorder), + y: center.y + Math.sin(rad) * (yRadius + halfBorder) + }, center, rotation / 180 * Math.PI); + + expect(element.inRange(x, y)).withContext(`rotation: ${rotation}, angle: ${angle}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(true); + } + } + }); + + it(`should return false when point is outside element\n{x: ${center.x}, y: ${center.y}, xRadius: ${xRadius.toFixed(1)}, yRadius: ${yRadius.toFixed(1)}}`, function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + + for (const angle of [0, 45, 90, 135, 180, 225, 270, 315]) { + const rad = angle / 180 * Math.PI; + + const {x, y} = rotated({ + x: center.x + Math.cos(rad) * (xRadius + halfBorder + 1), + y: center.y + Math.sin(rad) * (yRadius + halfBorder + 1) + }, center, rotation / 180 * Math.PI); + + expect(element.inRange(x, y)).withContext(`rotation: ${rotation}, angle: ${angle}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(false); + } + } + }); + } + + it('should refurn false for zero width/height ellipse', function() { + const chart = window.scatter10x10([ + {type: 'ellipse', xMin: 1, xMax: 1, yMin: 1, yMax: 1}, + {type: 'ellipse', xMin: 2, xMax: 3, yMin: 1, yMax: 1}, + {type: 'ellipse', xMin: 1, xMax: 1, yMin: 2, yMax: 3} + ]); + const elements = window.getAnnotationElements(chart); + for (const element of elements) { + const center = element.getCenterPoint(); + expect(element.inRange(center.x, center.y)).toEqual(false); + } + }); + }); }); diff --git a/test/specs/events.spec.js b/test/specs/events.spec.js index 82d1eb505..7dc0753b0 100644 --- a/test/specs/events.spec.js +++ b/test/specs/events.spec.js @@ -1,59 +1,160 @@ -describe('Event callbacks', function() { - - it('should not throw any exception', function() { - function createAndUpdateChart() { - const config = { - type: 'line', - data: { - labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], - datasets: [ - { - label: '# of Votes', - data: [12, 19, 3, 5, 2, 3] - } - ] - }, - options: { - scales: { - y: { - beginAtZero: true - } +describe('Common', function() { + + describe('events', function() { + const chartConfig = { + type: 'scatter', + options: { + animation: false, + scales: { + x: { + display: false, + min: 0, + max: 10 }, - plugins: { - annotation: { - drawTime: 'afterDatasetsDraw', - dblClickSpeed: 350, - annotations: { - my: { - display: true, - type: 'line', - scaleID: 'y', - value: 10, - borderColor: 'red', - borderWidth: 2, - click() { - } - }, - mydisable: { - display: false, - type: 'line', - scaleID: 'y', - value: 20, - borderColor: 'red', - borderWidth: 2, - click() { - } - } - } - } + y: { + display: false, + min: 0, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { } } - }; + } + }; + const options = { + type: 'box', + id: 'test', + xMin: 2, + yMin: 2, + xMax: 4, + yMax: 4 + }; + const x0y0 = {x: 0, y: 0}; + const center = function(chart) { + const xCenter = options.xMax - options.xMin; + const yCenter = options.yMax - options.yMin; + const xScale = chart.scales.x; + const yScale = chart.scales.y; + return {x: xScale.getPixelForValue(xCenter), y: yScale.getPixelForValue(yCenter)}; + }; + + const pluginOpts = chartConfig.options.plugins.annotation; + + [pluginOpts, options].forEach(function(targetOptions) { + it('should not detect any move events (because no callback is set)', function(done) { + const enterSpy = jasmine.createSpy('enter'); + const leaveSpy = jasmine.createSpy('leave'); + + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(chartConfig); + window.triggerMouseEvent(chart, 'mousemove', center(chart)); + window.afterEvent(chart, 'mousemove', function() { + expect(enterSpy.calls.count()).toBe(0); + + window.triggerMouseEvent(chart, 'mousemove', x0y0); + + window.afterEvent(chart, 'mousemove', function() { + expect(leaveSpy.calls.count()).toBe(0); + done(); + }); + }); + }); + + it('should not detect any events (because unmanaged event)', function(done) { + const clickSpy = jasmine.createSpy('click'); + + targetOptions.click = clickSpy; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(chartConfig); + window.afterEvent(chart, 'touchstart', function() { + expect(clickSpy.calls.count()).toBe(0); + delete targetOptions.click; + done(); + }); + window.triggerMouseEvent(chart, 'touchstart', center(chart)); + }); - var chart = acquireChart(config); + it('should not call removed hook', function(done) { + const enterSpy = jasmine.createSpy('enter'); + const leaveSpy = jasmine.createSpy('leave'); - chart.update(); - } - expect(createAndUpdateChart).not.toThrow(); + pluginOpts.enter = enterSpy; + pluginOpts.leave = leaveSpy; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(chartConfig); + pluginOpts.enter = undefined; + chart.update(); + + window.triggerMouseEvent(chart, 'mousemove', center(chart)); + window.afterEvent(chart, 'mousemove', function() { + expect(enterSpy.calls.count()).toBe(0); + + window.triggerMouseEvent(chart, 'mousemove', x0y0); + + window.afterEvent(chart, 'mousemove', function() { + expect(leaveSpy.calls.count()).toBe(1); + delete pluginOpts.enter; + delete pluginOpts.leave; + done(); + }); + }); + }); + + it('should persist properties set in context', function(done) { + targetOptions.enter = function(ctx) { + ctx.persistency = true; + }; + targetOptions.leave = function(ctx) { + expect(ctx.persistency).toBe(true); + done(); + }; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(chartConfig); + window.triggerMouseEvent(chart, 'mousemove', center(chart)); + window.afterEvent(chart, 'mousemove', function() { + + window.triggerMouseEvent(chart, 'mousemove', x0y0); + + window.afterEvent(chart, 'mousemove', function() { + delete targetOptions.enter; + delete targetOptions.leave; + }); + }); + }); + + it('should detect a click event even if 2 clicks are fired', function(done) { + const dblClickSpy = jasmine.createSpy('dblclick'); + + targetOptions.dblclick = dblClickSpy; + pluginOpts.dblClickSpeed = 1; + pluginOpts.annotations = [options]; + + const chart = window.acquireChart(chartConfig); + const eventPoint = center(chart); + + let dblClick = false; + window.afterEvent(chart, 'click', function() { + if (!dblClick) { + dblClick = true; + setTimeout(() => { + window.triggerMouseEvent(chart, 'click', eventPoint); + }, 50); + } else { + expect(dblClickSpy.calls.count()).toBe(0); + delete targetOptions.dblclick; + delete pluginOpts.dblClickSpeed; + done(); + } + }); + window.triggerMouseEvent(chart, 'click', eventPoint); + }); + }); }); }); diff --git a/test/specs/helpers.spec.js b/test/specs/helpers.spec.js new file mode 100644 index 000000000..06d3d646d --- /dev/null +++ b/test/specs/helpers.spec.js @@ -0,0 +1,25 @@ +describe('helpers', function() { + + describe('requireVersion', function() { + const requireVersion = window.helpers.requireVersion; + + it('should throw error for too old version', function() { + expect(() => requireVersion('test', '3.7', '2.9.3')).toThrowError(); + expect(() => requireVersion('test', '3.7', '3.6.99-alpha3')).toThrowError(); + expect(() => requireVersion('test', '16.13.2.8', '16.13.2.8-beta')).toThrowError(); + }); + + it('should not throw error for new enough version', function() { + expect(() => requireVersion('test', '3.7', '3.7.0-beta.1')).not.toThrowError(); + expect(() => requireVersion('test', '3.7.1', '3.7.19')).not.toThrowError(); + expect(() => requireVersion('test', '3.7', '4.0.0')).not.toThrowError(); + expect(() => requireVersion('test', '16.13.2', '16.13.3-rc')).not.toThrowError(); + }); + + it('should return boolean when `strict` parameter is false', function() { + expect(requireVersion('test', '3.7', '2.9.3', false)).toBeFalse(); + expect(requireVersion('test', '3.7', '3.8', false)).toBeTrue(); + }); + }); + +}); diff --git a/test/specs/label.spec.js b/test/specs/label.spec.js new file mode 100644 index 000000000..10a07b9c7 --- /dev/null +++ b/test/specs/label.spec.js @@ -0,0 +1,47 @@ +describe('Label annotation', function() { + describe('auto', jasmine.fixtures('label')); + + describe('inRange', function() { + const annotation = { + type: 'label', + id: 'test', + xValue: 5, + yValue: 5, + content: 'This is my text', + position: 'center', + }; + + const chart = window.scatter10x10({test: annotation}); + const element = window.getAnnotationElements(chart)[0]; + + it('should return true inside element', function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + for (const x of [element.x - halfBorder, element.x + element.width / 2, element.x + element.width + halfBorder]) { + for (const y of [element.y - halfBorder, element.y + element.height / 2, element.y + element.height + halfBorder]) { + expect(element.inRange(x, y)).toEqual(true); + } + } + } + }); + + it('should return false outside element', function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + + for (const x of [element.x - halfBorder - 1, element.x + element.width + halfBorder + 1]) { + for (const y of [element.y, element.y + element.height / 2, element.y + element.height]) { + expect(element.inRange(x, y)).toEqual(false); + } + } + for (const x of [element.x, element.x + element.width / 2, element.x + element.width]) { + for (const y of [element.y - halfBorder - 1, element.y + element.height + halfBorder + 1]) { + expect(element.inRange(x, y)).toEqual(false); + } + } + } + }); + }); +}); diff --git a/test/specs/line.spec.js b/test/specs/line.spec.js index caec9bd59..e65644616 100644 --- a/test/specs/line.spec.js +++ b/test/specs/line.spec.js @@ -1,3 +1,23 @@ describe('Line annotation', function() { describe('auto', jasmine.fixtures('line')); + + const eventIn = function(xScale, yScale, element) { + const options = element.options; + const adjust = options.borderWidth / 2 - 1; + return {x: element.x - adjust, y: element.y}; + }; + const eventOut = function(xScale, yScale, element) { + const options = element.options; + const adjust = options.borderWidth / 2 + 1; + return {x: element.x - adjust, y: element.y}; + }; + + window.testEvents({ + type: 'line', + id: 'test', + scaleID: 'y', + value: 5, + borderWidth: 10 + }, eventIn, eventOut); + }); diff --git a/test/specs/lineLabel.spec.js b/test/specs/lineLabel.spec.js new file mode 100644 index 000000000..ec3aaa5ab --- /dev/null +++ b/test/specs/lineLabel.spec.js @@ -0,0 +1,28 @@ +describe('Label of line annotation', function() { + + const eventIn = function(xScale, yScale, element) { + const options = element.options.label; + const adjust = options.borderWidth / 2 - 1; + return {x: element.labelX + element.labelWidth / 2, y: element.labelY - element.labelHeight / 2 - adjust}; + }; + const eventOut = function(xScale, yScale, element) { + const options = element.options.label; + const adjust = options.borderWidth / 2 + 1; + return {x: element.labelX + element.labelWidth / 2, y: element.labelY - element.labelHeight / 2 - adjust}; + }; + + window.testEvents({ + type: 'line', + id: 'test', + scaleID: 'x', + value: 5, + borderWidth: 0, + label: { + enabled: true, + content: ['This is the first row', 'This is the second row'], + borderWidth: 10, + rotation: 0 + } + }, eventIn, eventOut); + +}); diff --git a/test/specs/point.spec.js b/test/specs/point.spec.js index 8984f6ecd..7c05651fb 100644 --- a/test/specs/point.spec.js +++ b/test/specs/point.spec.js @@ -1,162 +1,77 @@ describe('Point annotation', function() { describe('auto', jasmine.fixtures('point')); - describe('events', function() { - const Annotation = window['chartjs-plugin-annotation']; - - it('should detect events', function(done) { - const enterSpy = jasmine.createSpy('enter'); - const leaveSpy = jasmine.createSpy('leave'); - - const chart = window.acquireChart({ - type: 'scatter', - options: { - animation: false, - scales: { - x: { - display: false, - min: 0, - max: 10 - }, - y: { - display: false, - min: 0, - max: 10 - } - }, - plugins: { - legend: false, - annotation: { - annotations: { - point: { - type: 'point', - radius: 10, - borderWidth: 5, - enter: enterSpy, - leave: leaveSpy - }, - point2: { - type: 'point', - xScaleID: 'x', - yScaleID: 'y', - xValue: 8, - yValue: 8, - radius: 0, - borderWidth: 0, - enter: enterSpy - } - } - } + describe('inRange', function() { + const annotation1 = { + type: 'point', + xValue: 7, + yValue: 7, + radius: 30 + }; + const annotation2 = { + type: 'point', + xValue: 3, + yValue: 3, + radius: 5 + }; + const annotation3 = { + type: 'point', + xValue: 5, + yValue: 5, + radius: 0 + }; + + const chart = window.scatter10x10({annotation1, annotation2, annotation3}); + const elems = window.getAnnotationElements(chart).filter(el => el.options.radius > 0); + const elemsNoRad = window.getAnnotationElements(chart).filter(el => el.options.radius === 0); + + elems.forEach(function(element) { + it(`should return true inside element '${element.options.id}'`, function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + const radius = element.height / 2; + for (const angle of [0, 45, 90, 135, 180, 225, 270, 315]) { + const rad = angle * (Math.PI / 180); + const {x, y} = { + x: element.x + Math.cos(rad) * (radius + halfBorder - 1), + y: element.y + Math.sin(rad) * (radius + halfBorder - 1) + }; + expect(element.inRange(x, y)).withContext(`angle: ${angle}, radius: ${radius}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(true); } - }, + } }); - const state = Annotation._getState(chart); - const point = state.elements[0]; - const point2 = state.elements[1]; - - // should be centered when there are no scales or values - expect(point.x).toEqual(256); - expect(point.y).toEqual(256); - - expect(enterSpy.calls.count()).toBe(0); - expect(leaveSpy.calls.count()).toBe(0); - - window.triggerMouseEvent(chart, 'mousemove', point); - - window.afterEvent(chart, 'mousemove', function() { - expect(enterSpy.calls.count()).toBe(1); - - window.triggerMouseEvent(chart, 'mousemove', { - x: point.x + 16, - y: point.y - }); - - window.afterEvent(chart, 'mousemove', function() { - expect(leaveSpy.calls.count()).toBe(1); - - window.triggerMouseEvent(chart, 'mousemove', { - x: point.x + 14.5, - y: point.y - }); - - window.afterEvent(chart, 'mousemove', function() { - expect(enterSpy.calls.count()).toBe(2); - - window.triggerMouseEvent(chart, 'mousemove', point2); - - window.afterEvent(chart, 'mousemove', function() { - expect(leaveSpy.calls.count()).toBe(2); - expect(enterSpy.calls.count()).toBe(2); - done(); - }); - }); - }); + it(`should return false outside element '${element.options.id}'`, function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + const radius = element.height / 2; + for (const angle of [0, 45, 90, 135, 180, 225, 270, 315]) { + const rad = angle * (Math.PI / 180); + const {x, y} = { + x: element.x + Math.cos(rad) * (radius + halfBorder + 1), + y: element.y + Math.sin(rad) * (radius + halfBorder + 1) + }; + expect(element.inRange(x, y)).withContext(`angle: ${angle}, radius: ${radius}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(false); + } + } }); - }); - }); - describe('applying defaults', function() { - - it('should not throw any exception', function() { - function createAndUpdateChart() { - const config = { - type: 'scatter', - options: { - animation: false, - scales: { - x: { - display: false, - min: 0, - max: 10 - }, - y: { - display: false, - min: 0, - max: 10 - } - }, - plugins: { - legend: false, - annotation: { - annotations: { - point: { - type: 'point', - borderWidth: 5, - display(context, options) { - if (options) { - context.chart.annotationRadius1 = options.radius; - } - return true; - }, - }, - point2: { - type: 'point', - xScaleID: 'x', - yScaleID: 'y', - xValue: 8, - yValue: 8, - borderWidth: 0, - display(context, options) { - if (options) { - context.chart.annotationRadius2 = options.radius; - } - return true; - }, - } - } - } - } - }, - }; - - var chart = acquireChart(config); - if (isNaN(chart.annotationRadius1) || isNaN(chart.annotationRadius2)) { - throw new Error('Defaults radius is not applied to annotaions : 1-' + chart.annotationRadius1 + ', 2-' + chart.annotationRadius2); + elemsNoRad.forEach(function(element) { + it(`should return false radius is 0 element '${element.options.id}'`, function() { + for (const borderWidth of [0, 10]) { + const halfBorder = borderWidth / 2; + element.options.borderWidth = borderWidth; + for (const x of [element.x - halfBorder, element.x + halfBorder]) { + expect(element.inRange(x, element.y)).toEqual(false); + } + for (const y of [element.y - halfBorder, element.y + halfBorder]) { + expect(element.inRange(element.x, y)).toEqual(false); + } } - } - expect(createAndUpdateChart).not.toThrow(); + }); }); }); }); diff --git a/test/specs/polygon.spec.js b/test/specs/polygon.spec.js new file mode 100644 index 000000000..a77f07cf3 --- /dev/null +++ b/test/specs/polygon.spec.js @@ -0,0 +1,115 @@ +describe('Polygon annotation', function() { + describe('auto', jasmine.fixtures('polygon')); + + describe('inRange', function() { + const annotation1 = { + type: 'polygon', + xValue: 1, + yValue: 1, + borderWidth: 0, + sides: 3, + radius: 30 + }; + const annotation2 = { + type: 'polygon', + xValue: 2, + yValue: 2, + borderWidth: 10, + sides: 4, + radius: 5 + }; + const annotation3 = { + type: 'polygon', + xValue: 3, + yValue: 3, + borderWidth: 0, + sides: 5, + radius: 27 + }; + const annotation4 = { + type: 'polygon', + xValue: 4, + yValue: 4, + sides: 3, + borderWidth: 10, + rotation: 21, + radius: 20 + }; + const annotation5 = { + type: 'polygon', + xValue: 5, + yValue: 5, + sides: 4, + borderWidth: 0, + rotation: 131, + radius: 33 + }; + const annotation6 = { + type: 'polygon', + xValue: 6, + yValue: 6, + sides: 5, + borderWidth: 10, + rotation: 241, + radius: 24 + }; + const annotation7 = { + type: 'polygon', + xValue: 7, + yValue: 7, + sides: 5, + radius: 0 + }; + const annotation8 = { + type: 'polygon', + xValue: 8, + yValue: 8, + borderWidth: 10, + sides: 5, + radius: 0 + }; + + const chart = window.scatter10x10({annotation1, annotation2, annotation3, annotation4, annotation5, annotation6, annotation7, annotation8}); + const elems = window.getAnnotationElements(chart).filter(el => el.options.radius > 0); + const elemsNoRad = window.getAnnotationElements(chart).filter(el => el.options.radius === 0); + + elems.forEach(function(element) { + const center = element.getCenterPoint(); + const rotation = element.options.rotation; + const sides = element.options.sides; + const borderWidth = element.options.borderWidth; + const radius = element.height / 2; + const angle = (2 * Math.PI) / sides; + + it(`should return true inside element '${element.options.id}'`, function() { + const halfBorder = borderWidth / 2; + let rad = rotation * (Math.PI / 180); + for (let i = 0; i < sides; i++, rad += angle) { + const sin = Math.sin(rad); + const cos = Math.cos(rad); + const x = center.x + sin * (radius + halfBorder - 1); + const y = center.y - cos * (radius + halfBorder - 1); + expect(element.inRange(x, y)).withContext(`sides: ${sides}, rotation: ${rotation}, radius: ${radius}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(true); + } + }); + + it(`should return false outside element '${element.options.id}'`, function() { + const halfBorder = borderWidth / 2; + let rad = rotation * (Math.PI / 180); + for (let i = 0; i < sides; i++, rad += angle) { + const sin = Math.sin(rad); + const cos = Math.cos(rad); + const x = center.x + sin * (radius + halfBorder + 1); + const y = center.y - cos * (radius + halfBorder + 1); + expect(element.inRange(x, y)).withContext(`sides: ${sides}, rotation: ${rotation}, radius: ${radius}, borderWidth: ${borderWidth}, {x: ${x.toFixed(1)}, y: ${y.toFixed(1)}}`).toEqual(false); + } + }); + }); + + elemsNoRad.forEach(function(element) { + it(`should return false radius is 0 element '${element.options.id}'`, function() { + expect(element.inRange(element.x, element.y)).toEqual(false); + }); + }); + }); +}); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 000000000..189b863eb --- /dev/null +++ b/test/utils.js @@ -0,0 +1,55 @@ +export function createCanvas() { + const canvas = document.createElement('canvas'); + canvas.width = 230; + canvas.height = 210; + const ctx = canvas.getContext('2d'); + ctx.lineWidth = 10; + ctx.strokeRect(40, 90, 150, 110); + ctx.fillRect(95, 140, 40, 60); + ctx.beginPath(); + ctx.moveTo(15, 90); + ctx.lineTo(115, 10); + ctx.lineTo(215, 90); + ctx.closePath(); + ctx.stroke(); + return canvas; +} + +export function getAnnotationElements(chart) { + return window['chartjs-plugin-annotation']._getState(chart).elements; +} + +export function scatter10x10(annotations) { + return window.acquireChart({ + type: 'scatter', + options: { + animation: false, + scales: { + x: { + display: false, + min: 0, + max: 10 + }, + y: { + display: false, + min: 0, + max: 10 + } + }, + plugins: { + legend: false, + annotation: { + annotations + } + } + } + }); +} + +function keepInf(key, value) { + return value === Infinity ? 'Infinity' : value; +} + +export function stringifyObject(obj) { + return JSON.stringify(obj, keepInf).replaceAll('"', '').replaceAll(':', ': ').replaceAll(',', ', '); +} diff --git a/types/events.d.ts b/types/events.d.ts index 8182b392d..f2043c115 100644 --- a/types/events.d.ts +++ b/types/events.d.ts @@ -4,6 +4,8 @@ import { AnnotationElement } from './element'; export interface EventContext { chart: Chart, element: AnnotationElement, + id: string, + type: string } /** @@ -13,6 +15,8 @@ export interface EventContext { export interface PartialEventContext { chart: Chart, element?: Partial, + id?: string, + type?: string } export interface AnnotationEvents { diff --git a/types/index.d.ts b/types/index.d.ts index 06523bf6a..a713ce1e1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,5 +1,5 @@ import { ChartType, Plugin } from 'chart.js'; -import { AnnotationPluginOptions, BoxAnnotationOptions, EllipseAnnotationOptions, LineAnnotationOptions, PointAnnotationOptions } from './options'; +import { AnnotationPluginOptions, BoxAnnotationOptions, EllipseAnnotationOptions, LabelAnnotationOptions, LineAnnotationOptions, PointAnnotationOptions, PolygonAnnotationOptions } from './options'; declare module 'chart.js' { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -9,9 +9,11 @@ declare module 'chart.js' { interface ElementOptionsByType { boxAnnotation: BoxAnnotationOptions; - lineAnnotation: LineAnnotationOptions; ellipseAnnotation: EllipseAnnotationOptions; + labelAnnotation: LabelAnnotationOptions; + lineAnnotation: LineAnnotationOptions; pointAnnotation: PointAnnotationOptions; + polygonAnnotation: PolygonAnnotationOptions; } } diff --git a/types/label.d.ts b/types/label.d.ts index 0de011cee..f02850ea5 100644 --- a/types/label.d.ts +++ b/types/label.d.ts @@ -1,93 +1,141 @@ -import { Color, FontSpec } from 'chart.js'; +import { Color, FontSpec, BorderRadius } from 'chart.js'; import { PartialEventContext } from './events'; -import { DrawTime, Scriptable } from './options'; +import { DrawTime, Scriptable, ShadowOptions } from './options'; -export interface LabelOptions { - backgroundColor?: Scriptable, +export interface CoreLabelOptions { drawTime?: Scriptable, font?: FontSpec color?: Scriptable, /** - * Padding of label to add left/right - * @default 6 - */ - xPadding?: Scriptable, + * Padding of label + * @default 6 + */ + padding?: Scriptable, /** - * Padding of label to add top/bottom - * @default 6 - */ - yPadding?: Scriptable, - + * Text alignment when the content of the label is multi-line. + * @default 'center' + */ + textAlign?: Scriptable, + /** + * Adjustment along x-axis (left-right) of label relative to above number (can be negative) + * For horizontal lines positioned left or right, negative values move + * the label toward the edge, and positive values toward the center. + * @default 0 + */ + xAdjust?: Scriptable, + /** + * Adjustment along y-axis (top-bottom) of label relative to above number (can be negative) + * For vertical lines positioned top or bottom, negative values move + * the label toward the edge, and positive values toward the center. + * @default 0 + */ + yAdjust?: Scriptable, + /** + * Text to display in label. Provide an array to display multiple lines + */ + content: Scriptable, /** - * Border radius of the label rectangle - * @default 6 - */ - borderRadius?: Scriptable, + * Overrides the width of the image. Could be set in pixel by a number, + * or in percentage of current width of image by a string + */ + width?: Scriptable, + /** + * Overrides the height of the image. Could be set in pixel by a number, + * or in percentage of current height of image by a string + */ + height?: Scriptable, +} +export interface ContainedLabelOptions extends CoreLabelOptions { + backgroundColor?: Scriptable, + borderWidth?: Scriptable, + borderColor?: Scriptable, + /** + * Border line cap style. See MDN. + * @default 'butt' + */ + borderCapStyle?: Scriptable, + /** + * Border line dash. See MDN. + * @default [] + */ + borderDash?: Scriptable, + /** + * Border line dash offset. See MDN. + * @default 0.0 + */ + borderDashOffset?: Scriptable, + /** + * Border line join style. See MDN. + * @default 'miter' + */ + borderJoinStyle?: Scriptable, + /** + * Border radius of the label rectangle + * @default 6 + */ + borderRadius?: Scriptable, /** * @deprecated replaced by borderRadius * @todo remove at v2 */ - cornerRadius?: Scriptable, + cornerRadius?: Scriptable, +} - /** - * Anchor position of label on line. - * @default 'center' - */ +export interface LabelOptions extends ContainedLabelOptions, ShadowOptions { position?: Scriptable, - /** - * Text alignment when the content of the label is multi-line. - * @default 'center' - */ - textAlign?: Scriptable, - + * Whether the label is enabled and should be displayed + * @default true + */ + enabled?: Scriptable, /** - * Adjustment along x-axis (left-right) of label relative to above number (can be negative) - * For horizontal lines positioned left or right, negative values move - * the label toward the edge, and positive values toward the center. - * @default 0 - */ - xAdjust?: Scriptable, - + * Rotation of label, in degrees, or 'auto' to use the degrees of the line, default is 0 + * @default 90 + */ + rotation?: Scriptable /** - * Adjustment along y-axis (top-bottom) of label relative to above number (can be negative) - * For vertical lines positioned top or bottom, negative values move - * the label toward the edge, and positive values toward the center. - * @default 0 - */ - yAdjust?: Scriptable, + * Padding of label to add left/right + * @default 6 + * @deprecated + */ + xPadding?: Scriptable, + /** + * Padding of label to add top/bottom + * @default 6 + * @deprecated + */ + yPadding?: Scriptable, +} +export interface BoxLabelOptions extends CoreLabelOptions { + position?: Scriptable, /** - * Whether the label is enabled and should be displayed - * @default true - */ + * Whether the label is enabled and should be displayed + * @default true + */ enabled?: Scriptable, +} - /** - * Text to display in label. Provide an array to display multiple lines - */ - content: Scriptable, +export interface LabelTypeOptions extends ContainedLabelOptions { + position?: Scriptable, +} - /** - * Overrides the width of the image. Could be set in pixel by a number, - * or in percentage of current width of image by a string - */ - width?: Scriptable, +type percentString = string; +export type LabelPosition = 'start' | 'center' | 'end' | percentString; - /** - * Overrides the height of the image. Could be set in pixel by a number, - * or in percentage of current height of image by a string - */ - height?: Scriptable, +export type LabelTextAlign = 'left' | 'start' | 'center' | 'right' | 'end'; - /** - * Rotation of label, in degrees, or 'auto' to use the degrees of the line, default is 0 - * @default 90 - */ - rotation?: Scriptable +interface LabelPositionObject { + x?: LabelPosition, + y?: LabelPosition } -export type LabelPosition = 'start' | 'center' | 'end'; - -export type LabelTextAlign = 'start' | 'center' | 'end'; +interface LabelPadding { + top?: number, + left?: number, + right?: number, + bottom?: number, + x?: number, + y?: number +} diff --git a/types/options.d.ts b/types/options.d.ts index c4a180967..65ead6615 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -1,14 +1,16 @@ -import { Color } from 'chart.js'; +import { Color, PointStyle, BorderRadius } from 'chart.js'; import { AnnotationEvents, PartialEventContext } from './events'; -import { LabelOptions } from './label'; +import { LabelOptions, BoxLabelOptions, LabelTypeOptions } from './label'; export type DrawTime = 'afterDraw' | 'afterDatasetsDraw' | 'beforeDraw' | 'beforeDatasetsDraw'; export interface AnnotationTypeRegistry { - line: LineAnnotationOptions box: BoxAnnotationOptions ellipse: EllipseAnnotationOptions + label: LabelAnnotationOptions + line: LineAnnotationOptions point: PointAnnotationOptions + polygon: PolygonAnnotationOptions } export type AnnotationType = keyof AnnotationTypeRegistry; @@ -16,14 +18,21 @@ export type AnnotationType = keyof AnnotationTypeRegistry; export type AnnotationOptions = { [key in TYPE]: { type: key } & AnnotationTypeRegistry[key] }[TYPE] +interface ShadowOptions { + backgroundShadowColor?: Scriptable, + borderShadowColor?: Scriptable, + shadowBlur?: Scriptable, + shadowOffsetX?: Scriptable, + shadowOffsetY?: Scriptable +} -export interface CoreAnnotationOptions extends AnnotationEvents { +export interface CoreAnnotationOptions extends AnnotationEvents, ShadowOptions { id?: string, display?: Scriptable, adjustScaleRange?: Scriptable, borderColor?: Scriptable, borderWidth?: Scriptable, - borderDash?: Scriptable<[number, number], PartialEventContext>, + borderDash?: Scriptable, borderDashOffset?: Scriptable, drawTime?: Scriptable, endValue?: Scriptable, @@ -31,6 +40,7 @@ export interface CoreAnnotationOptions extends AnnotationEvents { value?: Scriptable, xScaleID?: Scriptable, yScaleID?: Scriptable, + yPadding?: Scriptable } export type Scriptable = T | ((ctx: TContext, options: AnnotationOptions) => T); @@ -42,34 +52,113 @@ interface AnnotationCoordinates { yMin?: Scriptable, } +interface AnnotationPointCoordinates extends AnnotationCoordinates { + xValue?: Scriptable, + yValue?: Scriptable, +} + +export interface ArrowHeadOptions extends ShadowOptions { + backgroundColor?: Scriptable, + borderColor?: Scriptable, + borderDash?: Scriptable, + borderDashOffset?: Scriptable, + borderWidth?: Scriptable, + enabled?: Scriptable, + fill?: Scriptable, + length?: Scriptable, + width?: Scriptable, +} + +export interface ArrowHeadsOptions extends ArrowHeadOptions{ + end?: ArrowHeadOptions, + start?: ArrowHeadOptions, +} + export interface LineAnnotationOptions extends CoreAnnotationOptions, AnnotationCoordinates { + arrowHeads?: ArrowHeadsOptions, label?: LabelOptions } export interface BoxAnnotationOptions extends CoreAnnotationOptions, AnnotationCoordinates { backgroundColor?: Scriptable, - borderRadius?: Scriptable, + /** + * Border line cap style. See MDN. + * @default 'butt' + */ + borderCapStyle?: Scriptable, + /** + * Border line dash. See MDN. + * @default [] + */ + borderDash?: Scriptable, + /** + * Border line dash offset. See MDN. + * @default 0.0 + */ + borderDashOffset?: Scriptable, + /** + * Border line join style. See MDN. + * @default 'miter' + */ + borderJoinStyle?: Scriptable, + borderRadius?: Scriptable, /** * @deprecated replaced by borderRadius * @todo remove at v2 */ - cornerRadius?: Scriptable + cornerRadius?: Scriptable, + label?: BoxLabelOptions } -interface EllipseAnnotationOptions extends CoreAnnotationOptions, AnnotationCoordinates { +export interface EllipseAnnotationOptions extends CoreAnnotationOptions, AnnotationCoordinates { backgroundColor?: Scriptable, + rotation?: Scriptable +} + +export interface PointAnnotationOptions extends CoreAnnotationOptions, AnnotationPointCoordinates { + backgroundColor: Scriptable, + pointStyle?: Scriptable, + radius?: Scriptable, + rotation?: Scriptable, + xAdjust?: Scriptable, + yAdjust?: Scriptable, +} + +export type CalloutPosition = 'left' | 'top' | 'bottom' | 'right' | 'auto'; + +export interface CalloutOptions { + borderCapStyle?: Scriptable, + borderColor?: Scriptable, + borderDash?: Scriptable, + borderDashOffset?: Scriptable, + borderJoinStyle?: Scriptable, + borderWidth?: Scriptable, + enabled?: Scriptable, + margin?: Scriptable, + position?: Scriptable, + side?: Scriptable, + start?: Scriptable, +} + +export interface LabelAnnotationOptions extends CoreAnnotationOptions, LabelTypeOptions, AnnotationPointCoordinates { + callout?: CalloutOptions; } -interface PointAnnotationOptions extends CoreAnnotationOptions { +interface PolygonAnnotationOptions extends CoreAnnotationOptions, AnnotationPointCoordinates { backgroundColor: Scriptable, + borderCapStyle?: Scriptable, + borderJoinStyle?: Scriptable, radius?: Scriptable, - xValue?: Scriptable; - yValue?: Scriptable; + rotation?: Scriptable, + sides?: Scriptable, + xAdjust?: Scriptable, + yAdjust?: Scriptable, } export interface AnnotationPluginOptions extends AnnotationEvents { annotations: AnnotationOptions[] | Record, + clip?: boolean, dblClickSpeed?: Scriptable, drawTime?: Scriptable, - animations: Record, + animations?: Record, } diff --git a/types/tests/exports.ts b/types/tests/exports.ts index 69c42ac59..726ef6257 100644 --- a/types/tests/exports.ts +++ b/types/tests/exports.ts @@ -15,6 +15,7 @@ const chart = new Chart('id', { options: { plugins: { annotation: { + clip: false, annotations: [{ type: 'line', label: {