From 472b04e2771522f2e1595c5aca49397c09cd9a9a Mon Sep 17 00:00:00 2001 From: Charles McNulty Date: Wed, 2 Nov 2022 11:26:31 -0500 Subject: [PATCH] fix #10836 - add publc getLabelItems() --- src/core/core.scale.js | 40 ++++++---- test/specs/core.scale.tests.js | 128 ++++++++++++++++++++++++++++++ types/helpers/helpers.canvas.d.ts | 34 +++++++- types/index.esm.d.ts | 9 +++ 4 files changed, 194 insertions(+), 17 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index a565235b04d..b89f2630b83 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -364,6 +364,14 @@ export default class Scale extends Element { return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; } + /** + * @return {import('../types').LabelItem[]} + */ + getLabelItems(chartArea = this.chart.chartArea) { + const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea)); + return items; + } + // When a new layout is created, reset the data limits cache beforeLayout() { this._cache = {}; @@ -1292,17 +1300,20 @@ export default class Scale extends Element { } items.push({ - rotation, label, font, - color, - strokeColor, - strokeWidth, textOffset, - textAlign: tickTextAlign, - textBaseline, - translation: [x, y], - backdrop, + renderTextOptions: { + rotation, + color, + strokeColor, + strokeWidth, + textOffset, + textAlign: tickTextAlign, + textBaseline, + translation: [x, y], + backdrop, + } }); } @@ -1549,16 +1560,13 @@ export default class Scale extends Element { clipArea(ctx, area); } - const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea)); - let i, ilen; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - const item = items[i]; + const items = this.getLabelItems(chartArea); + for (const item of items) { + const renderTextOptions = item.renderTextOptions; const tickFont = item.font; const label = item.label; - - let y = item.textOffset; - renderText(ctx, label, 0, y, tickFont, item); + const y = item.textOffset; + renderText(ctx, label, 0, y, tickFont, renderTextOptions); } if (area) { diff --git a/test/specs/core.scale.tests.js b/test/specs/core.scale.tests.js index 04ce097edb9..d1ff082693b 100644 --- a/test/specs/core.scale.tests.js +++ b/test/specs/core.scale.tests.js @@ -571,4 +571,132 @@ describe('Core.scale', function() { expect(chart.scales.y.max).toEqual(10); }); }); + + describe('overrides', () => { + it('should create new scale', () => { + const chart = window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [{x: 100, y: 100}, {x: -100, y: -100}] + }, { + data: [{x: 10, y: 10}, {x: -10, y: -10}] + }] + }, + options: { + scales: { + x2: { + type: 'linear', + min: -20, + max: 20 + } + } + } + }); + + expect(Object.keys(chart.scales).sort()).toEqual(['x', 'x2', 'y']); + }); + + it('should create new scale with custom name', () => { + const chart = window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [{x: 100, y: 100}, {x: -100, y: -100}] + }, { + data: [{x: 10, y: 10}, {x: -10, y: -10}] + }] + }, + options: { + scales: { + scaleX: { + axis: 'x', + type: 'linear', + min: -20, + max: 20 + } + } + } + }); + + expect(Object.keys(chart.scales).sort()).toEqual(['scaleX', 'x', 'y']); + }); + + it('should throw error on scale with custom name without axis type', () => { + expect(() => window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [{x: 100, y: 100}, {x: -100, y: -100}] + }, { + data: [{x: 10, y: 10}, {x: -10, y: -10}] + }] + }, + options: { + scales: { + scaleX: { + type: 'linear', + min: -20, + max: 20 + } + } + } + })).toThrow(); + }); + + it('should read options first to determine axis', () => { + const chart = window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [{x: 100, y: 100}, {x: -100, y: -100}] + }, { + data: [{x: 10, y: 10}, {x: -10, y: -10}] + }] + }, + options: { + scales: { + xavier: { + axis: 'y', + type: 'linear', + min: -20, + max: 20 + } + } + } + }); + + expect(chart.scales.xavier.axis).toBe('y'); + }); + it('should center labels when rotated in x axis', () => { + const chart = window.acquireChart({ + type: 'line', + data: { + labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], + datasets: [{ + label: '# of Votes', + data: [12, 19, 3, 5, 2, 3] + }] + }, + options: { + scales: { + x: { + ticks: { + minRotation: 90, + } + } + } + } + }); + const mapper = item => parseFloat(item.renderTextOptions.translation[0].toFixed(2)); + const expected = [20.15, 113.6, 207.05, 300.5, 393.95, 487.4]; + const actual = chart.scales.x.getLabelItems().map(mapper); + const len = expected.length; + for (let i = 0; i < len; ++i) { + const actualValue = actual[i]; + const expectedValue = expected[i]; + expect(actualValue).toBeCloseTo(expectedValue, 1); + } + }); + }); }); diff --git a/types/helpers/helpers.canvas.d.ts b/types/helpers/helpers.canvas.d.ts index e6961af9355..f1cd11b3d08 100644 --- a/types/helpers/helpers.canvas.d.ts +++ b/types/helpers/helpers.canvas.d.ts @@ -1,4 +1,4 @@ -import { PointStyle } from '../index.esm'; +import { PointStyle, Scriptable, ScriptableScaleContext } from '../index.esm'; import { Color } from '../color'; import { ChartArea, RoundedRect } from '../geometric'; import { CanvasFontSpec } from './helpers.options'; @@ -89,6 +89,38 @@ export interface RenderTextOpts { * Underline the text */ underline?: boolean; + + /** + * Dimensions for drawing the label backdrop + */ + backdrop?: BackdropOptions; +} + +export interface BackdropOptions { + /** + * Left position of backdrop as pixel + */ + left: number; + + /** + * Top position of backdrop as pixel + */ + top: number; + + /** + * Width of backdrop in pixels + */ + width: number; + + /** + * Height of backdrop in pixels + */ + height: number; + + /** + * Color of label backdrops. + */ + color: Scriptable; } export function renderText( diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index 4284e2d1019..42ce5b2b249 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -7,6 +7,8 @@ import { Color } from './color'; import { Element } from './element'; import { ChartArea, Point } from './geometric'; import { LayoutItem, LayoutPosition } from './layout'; +import { RenderTextOpts } from './helpers/helpers.canvas'; +import { CanvasFontSpec } from './helpers'; export { DateAdapter, TimeUnit, _adapters } from './adapters'; export { Animation, Animations, Animator, AnimationEvent } from './animation'; @@ -1309,6 +1311,7 @@ export interface Scale extends El getMinMax(canStack: boolean): { min: number; max: number }; getTicks(): Tick[]; getLabels(): string[]; + getLabelItems(chartArea?: ChartArea): LabelItem[]; beforeUpdate(): void; configure(): void; afterUpdate(): void; @@ -1352,6 +1355,12 @@ export interface ScriptableScalePointLabelContext { type: string; } +export interface LabelItem { + label: string | string[]; + font: CanvasFontSpec; + textOffset: number; + renderTextOptions: RenderTextOpts; +} export const Ticks: { formatters: {