From 28ae3e8076528a052f58a58468288495c5d80f89 Mon Sep 17 00:00:00 2001 From: netil Date: Mon, 7 Oct 2024 19:25:56 +0900 Subject: [PATCH] feat(axis): Intent to ship axis.evalTextSize Implement axis text's size evaluation option Ref #3889 --- src/ChartInternal/Axis/AxisRenderer.ts | 8 ++- src/ChartInternal/Axis/AxisRendererHelper.ts | 10 +-- src/config/Options/axis/axis.ts | 30 +++++++++ test/api/axis-spec.ts | 2 +- test/internals/axis-spec.ts | 67 ++++++++++++++++++++ types/axis.d.ts | 7 ++ 6 files changed, 117 insertions(+), 7 deletions(-) diff --git a/src/ChartInternal/Axis/AxisRenderer.ts b/src/ChartInternal/Axis/AxisRenderer.ts index 54e4e21ff..5c4c4efac 100644 --- a/src/ChartInternal/Axis/AxisRenderer.ts +++ b/src/ChartInternal/Axis/AxisRenderer.ts @@ -81,6 +81,7 @@ export default class AxisRenderer { tick: axisShow ? params.config[`${prefix}_tick_show`] : false, text: axisShow ? params.config[`${prefix}_tick_text_show`] : false }; + const evalTextSize = params.config.axis_evalTextSize; let $g; @@ -136,10 +137,13 @@ export default class AxisRenderer { tickShow.tick && tickEnter.append("line"); tickShow.text && tickEnter.append("text"); - const sizeFor1Char = Helper.getSizeFor1Char(tick); + const tickText = tick.select("text"); + const sizeFor1Char = isFunction(evalTextSize) ? + evalTextSize.bind(ctx.params.owner.api)(tickText.node()) : + Helper.getSizeFor1Char(tickText, evalTextSize); const counts: number[] = []; - let tspan: d3Selection = tick.select("text") + let tspan: d3Selection = tickText .selectAll("tspan") .data((d, index) => { const split = params.tickMultiline ? diff --git a/src/ChartInternal/Axis/AxisRendererHelper.ts b/src/ChartInternal/Axis/AxisRendererHelper.ts index 2e402b125..b560bca7a 100644 --- a/src/ChartInternal/Axis/AxisRendererHelper.ts +++ b/src/ChartInternal/Axis/AxisRendererHelper.ts @@ -30,17 +30,17 @@ export default class AxisRendererHelper { /** * Compute a character dimension - * @param {d3.selection} node node + * @param {d3.selection} text SVG text selection + * @param {boolean} memoize memoize the calculated size * @returns {{w: number, h: number}} * @private */ - static getSizeFor1Char(node: d3Selection): {w: number, h: number} { + static getSizeFor1Char(text: d3Selection, memoize = true): {w: number, h: number} { // default size for one character const size = { w: 5.5, h: 11.5 }; - const text = node.select("text"); !text.empty() && text .text("0") @@ -57,7 +57,9 @@ export default class AxisRendererHelper { } }); - this.getSizeFor1Char = () => size; + if (memoize) { + this.getSizeFor1Char = () => size; + } return size; } diff --git a/src/config/Options/axis/axis.ts b/src/config/Options/axis/axis.ts index 007c239df..5e88a00b8 100644 --- a/src/config/Options/axis/axis.ts +++ b/src/config/Options/axis/axis.ts @@ -10,6 +10,36 @@ import y2 from "./y2"; * y Axis config options */ export default { + /** + * Setup the way to evaluate tick text size. + * - **NOTE:** + * - Setting `false` or custom evaluator, highly recommended to memoize evaluated text dimension value to not degrade performance. + * @name axis․evalTextSize + * @memberof Options + * @type {boolean|Function} + * @default true + * @example + * axis: { + * // will evaluate getting text size every time. + * evalTextSize: false. + * + * // set a custom evaluator + * evalTextSize: function(textElement) { + * // set some character to be evaluated + * text.textContent = "0"; + * + * // get the size + * const box = text.getBBox(); + * + * // clear text + * text.textContent = ""; + * + * return { w: 7, h: 12}; + * } + * } + */ + axis_evalTextSize: {w: number, h: number})>true, + /** * Switch x and y axis position. * @name axis․rotated diff --git a/test/api/axis-spec.ts b/test/api/axis-spec.ts index 7560553b6..458ca00dc 100644 --- a/test/api/axis-spec.ts +++ b/test/api/axis-spec.ts @@ -42,7 +42,7 @@ describe("API axis", function() { }, onrendered: function() { main = this.internal.$el.main; - resolve(); + resolve(1); } }); }); diff --git a/test/internals/axis-spec.ts b/test/internals/axis-spec.ts index d0f30aa08..08ed4436b 100644 --- a/test/internals/axis-spec.ts +++ b/test/internals/axis-spec.ts @@ -5,6 +5,7 @@ /* eslint-disable */ // @ts-nocheck import {beforeEach, beforeAll, afterAll, describe, expect, it} from "vitest"; +import sinon from "sinon"; import {select as d3Select} from "d3-selection"; import {format as d3Format} from "d3-format"; import {timeMinute as d3TimeMinute} from "d3-time"; @@ -3655,4 +3656,70 @@ describe("AXIS", function() { }); }); }); + + describe("axis.evalTextSize", () => { + beforeAll(() => { + args = { + data: { + columns: [ + ["data2", 130, 100, 140, 200, 150] + ] + }, + axis: { + x: { + type: "category", + categories: [ + "Some label with a very long text which will definitely be wrapped in an odd way", + "Some label with a very long text which will definitely be wrapped in an odd way", + "Some label with a very long text which will definitely be wrapped in an odd way", + "Some label with a very long text which will definitely be wrapped in an odd way", + "Some label with a very long text which will definitely be wrapped in an odd way" + ] + }, + evalTextSize: sinon.spy(function(text) { + return { + w: 5, + h: 5 + } + }) + } + }; + }); + + it("check custom evaluator", () => { + const tick = chart.internal.$el.axis.x.select(".tick").node(); + const {width, height} = tick.getBoundingClientRect(); + + expect(width).to.be.closeTo(116, 3); + expect(height).to.be.closeTo(35, 3); + + expect(args.axis.evalTextSize.called).to.be.true; + expect(args.axis.evalTextSize.args[0][0].tagName === "text").to.be.true; + }); + + it("set options: axis.evalTextSize=false", () => { + args.axis.evalTextSize = false; + }); + + it("check dimension evaluation not memoized.", () => new Promise(done => { + const text = chart.internal.$el.axis.x.select(".tick text"); + + // when + text.style("font-size", "5px"); // change font size + chart.resize({width: 300}); + + setTimeout(() => { + const tick = chart.internal.$el.axis.x.select(".tick").node(); + const {width, height} = tick.getBoundingClientRect(); + + expect(width < 41).to.be.true; + // expect(height < 60).to.be.true; + + // reset font-size + text.style("font-size", null); + + done(1); + }, 300); + })); + }); }); diff --git a/types/axis.d.ts b/types/axis.d.ts index db5e55d8f..89602b4fb 100644 --- a/types/axis.d.ts +++ b/types/axis.d.ts @@ -5,6 +5,13 @@ import {Chart} from "./chart"; * billboard.js project is licensed under the MIT license */ export interface Axis { + /** + * Setup the way to evaluate tick text size. + * - **NOTE:** + * - Setting `false` or custom evaluator, highly recommended to memoize evaluated text dimension value to not degrade performance. + */ + evalTextSize?: boolean | ((text: SVGTextElement) => {w: number, h: number}); + /** * Switch x and y axis position. */