diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/array-of-values-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/array-of-values-chrome-linux.png index bb0ee1ff89..c981626ed5 100644 Binary files a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/array-of-values-chrome-linux.png and b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/array-of-values-chrome-linux.png differ diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/basic-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/basic-chrome-linux.png index b61c1b56b5..e68bb0b859 100644 Binary files a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/basic-chrome-linux.png and b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/basic-chrome-linux.png differ diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/grid-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/grid-chrome-linux.png index b5a7e8729b..a7949662ff 100644 Binary files a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/grid-chrome-linux.png and b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/grid-chrome-linux.png differ diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/trend-multiple-data-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/trend-multiple-data-chrome-linux.png index 6f7ad4ac40..4847494f7b 100644 Binary files a/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/trend-multiple-data-chrome-linux.png and b/e2e/screenshots/all.test.ts-snapshots/baselines/metric-alpha/trend-multiple-data-chrome-linux.png differ diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/test-cases/metric-trend-length-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/test-cases/metric-trend-length-chrome-linux.png index 814d4571c7..7de5f229bf 100644 Binary files a/e2e/screenshots/all.test.ts-snapshots/baselines/test-cases/metric-trend-length-chrome-linux.png and b/e2e/screenshots/all.test.ts-snapshots/baselines/test-cases/metric-trend-length-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-chrome-linux.png index 6a16f3a17a..5c9354895b 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png index a49f781143..c7d2d41c7d 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png index edf96d1095..00ed762ebe 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png index fee9e1796d..6d686d0fe2 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-horizontal/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-chrome-linux.png index 46979d7f82..c7c83c6c42 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png index b112e39d64..daa7ec0b4e 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-negative-values-reversed-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png index cd71c75f31..5b29c16ed1 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-chrome-linux.png differ diff --git a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png index 2ae3c2c159..13072a4ea8 100644 Binary files a/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png and b/e2e/screenshots/bullet_stories.test.ts-snapshots/bullet-stories/bullet-as-metric-two-thirds-circle/bullet-as-metric/should-render-with-positive-negative-values-reversed-chrome-linux.png differ diff --git a/e2e/screenshots/chart.test.ts-snapshots/chart/sizing/should-accommodate-chart-title-and-description-metric-sm-chrome-linux.png b/e2e/screenshots/chart.test.ts-snapshots/chart/sizing/should-accommodate-chart-title-and-description-metric-sm-chrome-linux.png index f7d931c33b..337c5b6d71 100644 Binary files a/e2e/screenshots/chart.test.ts-snapshots/chart/sizing/should-accommodate-chart-title-and-description-metric-sm-chrome-linux.png and b/e2e/screenshots/chart.test.ts-snapshots/chart/sizing/should-accommodate-chart-title-and-description-metric-sm-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png index d1bac63b24..77405f8419 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png index bb040b1637..6550f068cd 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png index 5a0ac127b6..362966e3dc 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png index 8b170353b0..8fc2cb2e74 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png index d072a7f453..94283d375a 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png index 47cab87362..aa2c7f8122 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/should-render-with-blended-background-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/should-render-with-blended-background-color-chrome-linux.png index a954d8468b..0c537107fb 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/should-render-with-blended-background-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-bar-type/should-render-with-blended-background-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png index 6462261728..6f78e4537f 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png index fdea6c753c..c136680bca 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png index 1e54c50303..4bd329fb68 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png index 3687ab317b..8224580c58 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png index 4ce6297fba..6dddc2961e 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png index 1a439aa338..9a1a88d9db 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/should-render-with-blended-background-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/should-render-with-blended-background-color-chrome-linux.png index 87d47a23ad..4a7dbec491 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/should-render-with-blended-background-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-none-type/should-render-with-blended-background-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png index 573076b854..fff6a43410 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-click-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png index 7e5a420f36..bd1e644072 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-hover-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png index a173aaddf4..7eb3823b8f 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/dark-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png index eedadfa0c1..ad98a6722f 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-click-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png index 665f2ef38e..01b666db14 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-hover-interaction-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png index 41cf3f0c8d..f38aec4d73 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/light-theme/should-render-metric-with-transparent-bg-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/should-render-with-blended-background-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/should-render-with-blended-background-color-chrome-linux.png index 47c3f2938a..a621fccbd8 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/should-render-with-blended-background-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/metric-trend-type/should-render-with-blended-background-color-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-chrome-linux.png index c31ab3e1df..a69bf1a01a 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-in-dark-mode-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-in-dark-mode-chrome-linux.png index b5db04347c..f348d2babe 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-in-dark-mode-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-horizontal-progress-bar-in-dark-mode-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-metric-value-fit-font-size-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-metric-value-fit-font-size-chrome-linux.png index 3790598590..15aa2e172d 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-metric-value-fit-font-size-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-metric-value-fit-font-size-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-chrome-linux.png index c31ab3e1df..a69bf1a01a 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-in-dark-mode-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-in-dark-mode-chrome-linux.png index b5db04347c..f348d2babe 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-in-dark-mode-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-no-progress-bar-in-dark-mode-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-vertical-progress-bar-in-dark-mode-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-vertical-progress-bar-in-dark-mode-chrome-linux.png index 49cdb75a70..2dc9234fdc 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-vertical-progress-bar-in-dark-mode-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-vertical-progress-bar-in-dark-mode-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-with-empty-and-missing-background-colors-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-with-empty-and-missing-background-colors-chrome-linux.png index e9aa43110a..c166ca0467 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-with-empty-and-missing-background-colors-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/should-render-with-empty-and-missing-background-colors-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-default-font-size-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-default-font-size-chrome-linux.png new file mode 100644 index 0000000000..7bccf441c0 Binary files /dev/null and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-default-font-size-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-fit-font-size-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-fit-font-size-chrome-linux.png new file mode 100644 index 0000000000..518c05e2c7 Binary files /dev/null and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-fit-font-size-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-fixed-font-size-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-fixed-font-size-chrome-linux.png new file mode 100644 index 0000000000..30c2557e18 Binary files /dev/null and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/small-size-with-fixed-font-size-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/text-value-with-trend-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/text-value-with-trend-chrome-linux.png index 82edcdd5da..62b7857fb3 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/text-value-with-trend-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/text-value-with-trend-chrome-linux.png differ diff --git a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/value-icon-and-value-color-chrome-linux.png b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/value-icon-and-value-color-chrome-linux.png index 873e8dfe32..a429892d70 100644 Binary files a/e2e/screenshots/metric_stories.test.ts-snapshots/metric/value-icon-and-value-color-chrome-linux.png and b/e2e/screenshots/metric_stories.test.ts-snapshots/metric/value-icon-and-value-color-chrome-linux.png differ diff --git a/e2e/tests/metric_stories.test.ts b/e2e/tests/metric_stories.test.ts index dec9587eee..d390400acb 100644 --- a/e2e/tests/metric_stories.test.ts +++ b/e2e/tests/metric_stories.test.ts @@ -68,6 +68,31 @@ test.describe('Metric', () => { ); }); + test('small size with fixed font size', async ({ page }) => { + await common.expectChartAtUrlToMatchScreenshot(page)( + `http://localhost:9001/?path=/story/metric-alpha--basic&knob-value%20font%20mode=custom&knob-value%20font%20size%20(px)=100`, + { + action: async () => await common.setResizeDimensions(page)({ height: 180, width: 180 }), + }, + ); + }); + test('small size with fit font size', async ({ page }) => { + await common.expectChartAtUrlToMatchScreenshot(page)( + `http://localhost:9001/?path=/story/metric-alpha--basic&knob-value%20font%20mode=fit&knob-value%20font%20size%20(px)=100`, + { + action: async () => await common.setResizeDimensions(page)({ height: 180, width: 180 }), + }, + ); + }); + test('small size with default font size', async ({ page }) => { + await common.expectChartAtUrlToMatchScreenshot(page)( + `http://localhost:9001/?path=/story/metric-alpha--basic&knob-value%20font%20mode=default&knob-value%20font%20size%20(px)=100`, + { + action: async () => await common.setResizeDimensions(page)({ height: 180, width: 180 }), + }, + ); + }); + pwEach.describe(['trend', 'bar', 'none'])( (v) => `Metric - ${v} type`, (type) => { diff --git a/packages/charts/src/chart_types/bullet_graph/renderer/canvas/index.tsx b/packages/charts/src/chart_types/bullet_graph/renderer/canvas/index.tsx index 26bfc92896..6390b92580 100644 --- a/packages/charts/src/chart_types/bullet_graph/renderer/canvas/index.tsx +++ b/packages/charts/src/chart_types/bullet_graph/renderer/canvas/index.tsx @@ -35,7 +35,8 @@ import { Point } from '../../../../utils/point'; import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; import { MetricStyle } from '../../../../utils/themes/theme'; import { Metric } from '../../../metric/renderer/dom/metric'; -import { BulletMetricWProgress, MetricDatum } from '../../../metric/specs'; +import { getMetricTextPartDimensions, getSnappedFontSizes } from '../../../metric/renderer/dom/text_measurements'; +import { BulletMetricWProgress } from '../../../metric/specs'; import { ActiveValue, getActiveValues } from '../../selectors/get_active_values'; import { getBulletSpec } from '../../selectors/get_bullet_spec'; import { getChartSize } from '../../selectors/get_chart_size'; @@ -190,30 +191,40 @@ class Component extends React.Component { ) : undefined, }; + const bulletToMetricStyle = mergePartial(metricStyle, { + barBackground: colorScale(datum.value).hex(), + emptyBackground: Colors.Transparent.keyword, + border: 'gray', + minHeight: 0, + textLightColor: 'white', + textDarkColor: 'black', + nonFiniteText: 'N/A', + valueFontSize: 'default', + }); + const panel = { width: size.width / stats.columns, height: size.height / stats.rows }; + + const textDimensions = getMetricTextPartDimensions(bulletDatum, panel, bulletToMetricStyle, locale); + const sizes = getSnappedFontSizes( + textDimensions.heightBasedSizes.valueFontSize, + panel.height, + bulletToMetricStyle, + ); + textDimensions.heightBasedSizes.valueFontSize = sizes.valueFontSize; + textDimensions.heightBasedSizes.valuePartFontSize = sizes.valuePartFontSize; + return ( ); }} diff --git a/packages/charts/src/chart_types/metric/renderer/dom/index.tsx b/packages/charts/src/chart_types/metric/renderer/dom/index.tsx index 4d3f66aacb..b7fe370cb2 100644 --- a/packages/charts/src/chart_types/metric/renderer/dom/index.tsx +++ b/packages/charts/src/chart_types/metric/renderer/dom/index.tsx @@ -15,7 +15,14 @@ import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { Metric as MetricComponent } from './metric'; -import { getFitValueFontSize, getMetricTextPartDimensions } from './text'; +import { + getFittedFontSizes, + getFitValueFontSize, + getFixedFontSizes, + getMetricTextPartDimensions, + getSnappedFontSizes, + MetricTextDimensions, +} from './text_measurements'; import { ColorContrastOptions, combineColors, highContrastColor } from '../../../../common/color_calcs'; import { colorToRgba, RGBATupleToString } from '../../../../common/color_library_wrappers'; import { Color } from '../../../../common/colors'; @@ -33,7 +40,7 @@ import { getResolvedBackgroundColorSelector } from '../../../../state/selectors/ import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec'; import { LIGHT_THEME } from '../../../../utils/themes/light_theme'; import { MetricStyle } from '../../../../utils/themes/theme'; -import { MetricSpec } from '../../specs'; +import { MetricDatum, MetricSpec } from '../../specs'; import { chartSize } from '../../state/selectors/chart_size'; import { getMetricSpecs } from '../../state/selectors/data'; import { hasChartTitles } from '../../state/selectors/has_chart_titles'; @@ -86,9 +93,7 @@ function Component({ const { data } = spec; const totalRows = data.length; - const maxColumns = data.reduce((acc, row) => { - return Math.max(acc, row.length); - }, 0); + const maxColumns = data.reduce((acc, row) => Math.max(acc, row.length), 0); const panel = { width: width / maxColumns, height: height / totalRows }; const contrastOptions: ColorContrastOptions = { @@ -98,31 +103,92 @@ function Component({ const emptyBackgroundRGBA = combineColors(colorToRgba(style.emptyBackground), colorToRgba(backgroundColor)); const emptyBackground = RGBATupleToString(emptyBackgroundRGBA); - const { color: emptyForegroundColor } = highContrastColor(emptyBackgroundRGBA, undefined, contrastOptions); - - const fittedValueFontSize = - style.valueFontSize !== 'fit' - ? NaN - : data - .flat() - .filter((d) => d !== undefined) - .reduce((acc, datum) => { - const { sizes, progressBarWidth, visibility, textParts } = getMetricTextPartDimensions( - datum, - panel, - style, - locale, - ); - const fontSize = getFitValueFontSize( - sizes.valueFontSize, - panel.width - progressBarWidth, - visibility.gapHeight, - textParts, - style.minValueFontSize, - datum.valueIcon !== undefined, - ); - return Math.min(acc, fontSize); - }, Number.MAX_SAFE_INTEGER); + const emptyForegroundColor = highContrastColor(emptyBackgroundRGBA, undefined, contrastOptions).color; + + const metricsConfigs = data.reduce<{ + fittedValueFontSize: number; + configs: Array< + | { key: string; className: string; type: 'left-empty' | 'right-empty' } + | { + key: string; + rowIndex: number; + type: 'metric'; + columnIndex: number; + textDimensions: MetricTextDimensions; + datum: MetricDatum; + } + >; + }>( + (acc, columns, rowIndex) => { + acc.configs = acc.configs.concat( + columns.map((datum, columnIndex) => { + const key = `${columnIndex}-${rowIndex}`; + if (!datum) { + // fill with empty panels at the beginning of the row + return { + key, + type: 'left-empty', + className: classNames('echMetric', { + 'echMetric--rightBorder': columnIndex < maxColumns - 1, + 'echMetric--bottomBorder': rowIndex < totalRows - 1, + 'echMetric--topBorder': hasTitles && rowIndex === 0, + }), + }; + } + const textDimensions = getMetricTextPartDimensions(datum, panel, style, locale); + + const fontSize = getFitValueFontSize( + textDimensions.heightBasedSizes.valueFontSize, + panel.width - textDimensions.progressBarWidth, + textDimensions.visibility.gapHeight, + textDimensions.textParts, + style.minValueFontSize, + datum.valueIcon !== undefined, + ); + acc.fittedValueFontSize = Math.min(acc.fittedValueFontSize, fontSize); + + return { + type: 'metric', + key, + datum, + columnIndex, + rowIndex, + textDimensions, + }; + }), + // adding all missing panels to fill up the row + Array.from({ length: maxColumns - columns.length }, (_, zeroBasedColumnIndex) => { + const columnIndex = zeroBasedColumnIndex + columns.length; + return { + key: `missing-${columnIndex}-${rowIndex}`, + type: 'right-empty', + className: classNames('echMetric', { + 'echMetric--bottomBorder': rowIndex < totalRows - 1, + 'echMetric--topBorder': hasTitles && rowIndex === 0, + }), + }; + }), + ); + + return acc; + }, + { configs: [], fittedValueFontSize: Number.MAX_SAFE_INTEGER }, + ); + + // update the configs with the globally aligned valueFontSize + const { valueFontSize, valuePartFontSize } = + typeof style.valueFontSize === 'number' + ? getFixedFontSizes(style.valueFontSize) + : style.valueFontSize === 'default' + ? getSnappedFontSizes(metricsConfigs.fittedValueFontSize, panel.height, style) + : getFittedFontSizes(metricsConfigs.fittedValueFontSize); + + metricsConfigs.configs.forEach((config) => { + if (config.type === 'metric') { + config.textDimensions.heightBasedSizes.valueFontSize = valueFontSize; + config.textDimensions.heightBasedSizes.valuePartFontSize = valuePartFontSize; + } + }); return ( // eslint-disable-next-line jsx-a11y/no-redundant-roles @@ -136,64 +202,35 @@ function Component({ gridTemplateRows: `repeat(${totalRows}, minmax(${style.minHeight}px, 1fr)`, }} > - {data.flatMap((columns, rowIndex) => { - return [ - ...columns.map((datum, columnIndex) => { - // fill undefined with empty panels - const emptyMetricClassName = classNames('echMetric', { - 'echMetric--rightBorder': columnIndex < maxColumns - 1, - 'echMetric--bottomBorder': rowIndex < totalRows - 1, - 'echMetric--topBorder': hasTitles && rowIndex === 0, - }); - return !datum ? ( -
  • -
    -
    -
    -
  • - ) : ( -
  • - -
  • - ); - }), - // fill the grid row with empty panels - ...Array.from({ length: maxColumns - columns.length }, (_, zeroBasedColumnIndex) => { - const columnIndex = zeroBasedColumnIndex + columns.length; - const emptyMetricClassName = classNames('echMetric', { - 'echMetric--bottomBorder': rowIndex < totalRows - 1, - 'echMetric--topBorder': hasTitles && rowIndex === 0, - }); - return ( -
  • -
    -
  • - ); - }), - ]; + {metricsConfigs.configs.map((config) => { + return config.type !== 'metric' ? ( +
  • +
    + {config.type === 'left-empty' && ( +
    + )} +
    +
  • + ) : ( +
  • + +
  • + ); })} ); diff --git a/packages/charts/src/chart_types/metric/renderer/dom/metric.tsx b/packages/charts/src/chart_types/metric/renderer/dom/metric.tsx index d461ca1d2c..f7733e8a69 100644 --- a/packages/charts/src/chart_types/metric/renderer/dom/metric.tsx +++ b/packages/charts/src/chart_types/metric/renderer/dom/metric.tsx @@ -12,6 +12,7 @@ import React, { CSSProperties, useState } from 'react'; import { ProgressBar } from './progress'; import { SparkLine, getSparkLineColor } from './sparkline'; import { MetricText } from './text'; +import { MetricTextDimensions } from './text_measurements'; import { ColorContrastOptions, combineColors } from '../../../../common/color_calcs'; import { RGBATupleToString, changeColorLightness, colorToRgba } from '../../../../common/color_library_wrappers'; import { Color } from '../../../../common/colors'; @@ -25,7 +26,6 @@ import { MetricElementEvent, } from '../../../../specs'; import { LayoutDirection, isNil } from '../../../../utils/common'; -import { Size } from '../../../../utils/dimensions'; import { MetricStyle } from '../../../../utils/themes/theme'; import { MetricWNumber, isMetricWProgress, isMetricWTrend } from '../../specs'; @@ -38,12 +38,10 @@ export const Metric: React.FunctionComponent<{ totalColumns: number; totalRows: number; datum: MetricDatum; - panel: Size; style: MetricStyle; backgroundColor: Color; contrastOptions: ColorContrastOptions; - locale: string; - fittedValueFontSize: number; + textDimensions: MetricTextDimensions; onElementClick?: ElementClickListener; onElementOver?: ElementOverListener; onElementOut?: BasicListener; @@ -55,15 +53,13 @@ export const Metric: React.FunctionComponent<{ totalColumns, totalRows, datum, - panel, style, backgroundColor: chartBackgroundColor, contrastOptions, - locale, + textDimensions, onElementClick, onElementOver, onElementOut, - fittedValueFontSize, }) => { const progressBarSize = 'small'; // currently we provide only the small progress bar; const [mouseState, setMouseState] = useState<'leave' | 'enter' | 'down'>('leave'); @@ -96,10 +92,7 @@ export const Metric: React.FunctionComponent<{ combineColors(colorToRgba(interactionColor), blendingBackgroundColor), ); - const datumWithInteractionColor: MetricDatum = { - ...datum, - color: blendedInteractionColor, - }; + const datumWithInteractionColor: MetricDatum = { ...datum, color: blendedInteractionColor }; const event: MetricElementEvent = { type: 'metricElementEvent', rowIndex, columnIndex }; @@ -132,7 +125,7 @@ export const Metric: React.FunctionComponent<{ } const onElementClickHandler = () => onElementClick && onElementClick([event]); - + const hasMouseEventsHandler = onElementOut || onElementOver || onElementClick; return ( // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions,jsx-a11y/click-events-have-key-events
    { - if (onElementOut || onElementOver || onElementClick) setMouseState('leave'); + if (hasMouseEventsHandler) setMouseState('leave'); if (onElementOut) onElementOut(); }} onMouseEnter={() => { - if (onElementOut || onElementOver || onElementClick) setMouseState('enter'); + if (hasMouseEventsHandler) setMouseState('enter'); if (onElementOver) onElementOver([event]); }} onMouseDown={() => { - if (onElementOut || onElementOver || onElementClick) setMouseState('down'); + if (hasMouseEventsHandler) setMouseState('down'); setLastMouseDownTimestamp(Date.now()); }} onMouseUp={() => { - if (onElementOut || onElementOver || onElementClick) setMouseState('enter'); + if (hasMouseEventsHandler) setMouseState('enter'); if (Date.now() - lastMouseDownTimestamp < 200 && onElementClick) { onElementClickHandler(); } }} onFocus={() => { - if (onElementOut || onElementOver || onElementClick) setMouseState('enter'); + if (hasMouseEventsHandler) setMouseState('enter'); }} onBlur={() => { - if (onElementOut || onElementOver || onElementClick) setMouseState('leave'); + if (hasMouseEventsHandler) setMouseState('leave'); }} onClick={(e) => { e.stopPropagation(); @@ -171,13 +164,11 @@ export const Metric: React.FunctionComponent<{ {isMetricWTrend(datumWithInteractionColor) && } {isMetricWProgress(datumWithInteractionColor) && ( diff --git a/packages/charts/src/chart_types/metric/renderer/dom/text.tsx b/packages/charts/src/chart_types/metric/renderer/dom/text.tsx index 208df06d9c..175e3876c8 100644 --- a/packages/charts/src/chart_types/metric/renderer/dom/text.tsx +++ b/packages/charts/src/chart_types/metric/renderer/dom/text.tsx @@ -9,187 +9,11 @@ import classNames from 'classnames'; import React, { CSSProperties } from 'react'; +import { MetricTextDimensions, PADDING } from './text_measurements'; import { Color } from '../../../../common/colors'; -import { DEFAULT_FONT_FAMILY } from '../../../../common/default_theme_attributes'; -import { Font } from '../../../../common/text_utils'; -import { TextMeasure, withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; -import { isFiniteNumber, isNil, LayoutDirection, renderWithProps } from '../../../../utils/common'; -import { Size } from '../../../../utils/dimensions'; -import { wrapText } from '../../../../utils/text/wrap'; +import { LayoutDirection, renderWithProps } from '../../../../utils/common'; import { MetricStyle } from '../../../../utils/themes/theme'; -import { - isMetricWNumber, - isMetricWNumberArrayValues, - isMetricWProgress, - MetricDatum, - MetricWNumber, -} from '../../specs'; - -interface TextParts { - emphasis: 'small' | 'normal'; - text: string; -} - -type BreakPoint = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'; - -// synced with scss variables -const PROGRESS_BAR_WIDTH = 10; -const PROGRESS_BAR_TARGET_WIDTH = 4; - -const HEIGHT_BP: [number, number, BreakPoint][] = [ - [0, 200, 'xs'], - [200, 300, 's'], - [300, 400, 'm'], - [400, 500, 'l'], - [500, 600, 'xl'], - [600, Infinity, 'xxl'], -]; - -const PADDING = 8; -const LINE_HEIGHT = 1.2; // aligned with our CSS -const ICON_SIZE: Record = { xs: 16, s: 16, m: 24, l: 24, xl: 32, xxl: 42 }; - -const TITLE_FONT_SIZE: Record = { xs: 16, s: 16, m: 24, l: 24, xl: 32, xxl: 42 }; -const SUBTITLE_FONT_SIZE: Record = { xs: 14, s: 14, m: 16, l: 20, xl: 26, xxl: 36 }; -const EXTRA_FONT_SIZE: Record = { xs: 14, s: 14, m: 16, l: 20, xl: 26, xxl: 36 }; -const VALUE_FONT_SIZE: Record = { xs: 36, s: 36, m: 56, l: 72, xl: 104, xxl: 170 }; -const VALUE_PART_FONT_SIZE: Record = { xs: 24, s: 24, m: 42, l: 56, xl: 80, xxl: 130 }; -const VALUE_PART_FONT_RATIO = 1.3; - -const TITLE_FONT: Font = { - fontStyle: 'normal', - fontFamily: DEFAULT_FONT_FAMILY, - fontVariant: 'normal', - fontWeight: 'bold', - textColor: 'black', -}; -const VALUE_FONT = TITLE_FONT; -const SUBTITLE_FONT: Font = { - ...TITLE_FONT, - fontWeight: 'normal', -}; - -interface Sizes { - iconSize: number; - titleFontSize: number; - subtitleFontSize: number; - extraFontSize: number; - valueFontSize: number; - valuePartFontSize: number; -} - -function getFontSizes(ranges: [number, number, BreakPoint][], value: number, style: MetricStyle): Sizes { - const range = ranges.find(([min, max]) => min <= value && value < max); - const size = range ? range[2] : ranges[0]?.[2] ?? 's'; - const valueFontSize = typeof style.valueFontSize === 'number' ? style.valueFontSize : VALUE_FONT_SIZE[size]; - const valuePartFontSize = - typeof style.valueFontSize === 'number' - ? Math.ceil(valueFontSize / VALUE_PART_FONT_RATIO) - : VALUE_PART_FONT_SIZE[size]; - - return { - iconSize: ICON_SIZE[size], - titleFontSize: TITLE_FONT_SIZE[size], - subtitleFontSize: SUBTITLE_FONT_SIZE[size], - extraFontSize: EXTRA_FONT_SIZE[size], - valueFontSize, - valuePartFontSize, - }; -} - -type ElementVisibility = { - titleMaxLines: number; - subtitleMaxLines: number; - title: boolean; - subtitle: boolean; - extra: boolean; -}; - -function elementVisibility( - datum: MetricDatum, - panel: Size, - sizes: Sizes, - locale: string, - fit: boolean, -): ElementVisibility & { - titleLines: string[]; - subtitleLines: string[]; - gapHeight: number; -} { - const maxTitlesWidth = 0.95 * panel.width - (datum.icon ? 24 : 0) - 2 * PADDING; - const titleHeight = (maxLines: number, textMeasure: TextMeasure) => { - return datum.title - ? PADDING + - wrapText(datum.title, TITLE_FONT, sizes.titleFontSize, maxTitlesWidth, maxLines, textMeasure, locale).length * - sizes.titleFontSize * - LINE_HEIGHT - : 0; - }; - - const subtitleHeight = (maxLines: number, textMeasure: TextMeasure) => { - return datum.subtitle - ? PADDING + - wrapText(datum.subtitle, SUBTITLE_FONT, sizes.subtitleFontSize, maxTitlesWidth, maxLines, textMeasure, locale) - .length * - sizes.subtitleFontSize * - LINE_HEIGHT - : 0; - }; - - const extraHeight = sizes.extraFontSize * LINE_HEIGHT; - const valueHeight = sizes.valueFontSize * LINE_HEIGHT; - - const responsiveBreakPoints: Array = [ - { titleMaxLines: 3, subtitleMaxLines: 2, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, - { titleMaxLines: 3, subtitleMaxLines: 1, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, - { titleMaxLines: 2, subtitleMaxLines: 1, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, - { titleMaxLines: 1, subtitleMaxLines: 1, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, - { titleMaxLines: 1, subtitleMaxLines: 0, title: !!datum.title, subtitle: false, extra: !!datum.extra }, - { titleMaxLines: 1, subtitleMaxLines: 0, title: !!datum.title, subtitle: false, extra: false }, - { titleMaxLines: 1, subtitleMaxLines: 0, title: !!datum.title, subtitle: false, extra: false }, - ]; - - const getCombinedHeight = ( - { titleMaxLines, subtitleMaxLines, title, subtitle, extra }: ElementVisibility, - measure: TextMeasure, - ) => - (title && titleMaxLines > 0 ? titleHeight(titleMaxLines, measure) : 0) + - (subtitle && subtitleMaxLines > 0 ? subtitleHeight(subtitleMaxLines, measure) : 0) + - (extra ? extraHeight : 0) + - valueHeight + - PADDING; - - const isVisible = (ev: ElementVisibility, measure: TextMeasure) => getCombinedHeight(ev, measure) < panel.height; - - return withTextMeasure((textMeasure) => { - const visibilityBreakpoint = fit - ? responsiveBreakPoints.at(0)! - : responsiveBreakPoints.find((breakpoint) => isVisible(breakpoint, textMeasure)) ?? responsiveBreakPoints.at(-1)!; - - return { - ...visibilityBreakpoint, - gapHeight: Math.max(0, panel.height - getCombinedHeight(visibilityBreakpoint, textMeasure)), - titleLines: wrapText( - datum.title ?? '', - TITLE_FONT, - sizes.titleFontSize, - maxTitlesWidth, - visibilityBreakpoint.titleMaxLines, - textMeasure, - locale, - ), - subtitleLines: wrapText( - datum.subtitle ?? '', - SUBTITLE_FONT, - sizes.subtitleFontSize, - maxTitlesWidth, - visibilityBreakpoint.subtitleMaxLines, - textMeasure, - locale, - ), - }; - }); -} +import { isMetricWNumber, MetricDatum } from '../../specs'; function lineClamp(maxLines: number): CSSProperties { return { @@ -207,45 +31,21 @@ function lineClamp(maxLines: number): CSSProperties { export const MetricText: React.FunctionComponent<{ id: string; datum: MetricDatum; - panel: Size; style: MetricStyle; onElementClick?: () => void; highContrastTextColor: Color; progressBarSize: 'small'; - fittedValueFontSize: number; - locale: string; -}> = ({ - id, - datum, - panel, - style, - onElementClick, - highContrastTextColor, - progressBarSize, - locale, - fittedValueFontSize, -}) => { - const { sizes, hasProgressBar, progressBarDirection, visibility, textParts } = getMetricTextPartDimensions( - datum, - panel, - style, - locale, - ); + textDimensions: MetricTextDimensions; +}> = ({ id, datum, style, onElementClick, highContrastTextColor, progressBarSize, textDimensions }) => { + const { heightBasedSizes: sizes, hasProgressBar, progressBarDirection, visibility, textParts } = textDimensions; const { extra, body } = datum; + const containerClassName = classNames('echMetricText', { [`echMetricText--${progressBarSize}`]: hasProgressBar, 'echMetricText--vertical': progressBarDirection === LayoutDirection.Vertical, 'echMetricText--horizontal': progressBarDirection === LayoutDirection.Horizontal, }); - const { valueFontSize, valuePartFontSize } = - style.valueFontSize === 'fit' - ? { - valueFontSize: fittedValueFontSize, - valuePartFontSize: fittedValueFontSize / VALUE_PART_FONT_RATIO, - } - : sizes; - const TitleElement = () => ( {text} @@ -356,14 +156,14 @@ export const MetricText: React.FunctionComponent<{

    {renderWithProps(datum.valueIcon, { - width: valuePartFontSize, - height: valuePartFontSize, + width: sizes.valuePartFontSize, + height: sizes.valuePartFontSize, color: datum.valueColor ?? highContrastTextColor, verticalAlign: 'middle', })} @@ -374,91 +174,3 @@ export const MetricText: React.FunctionComponent<{

    ); }; - -function getTextParts(datum: MetricDatum, style: MetricStyle): TextParts[] { - const values = Array.isArray(datum.value) ? datum.value : [datum.value]; - const valueFormatter = - isMetricWNumber(datum) || isMetricWNumberArrayValues(datum) ? datum.valueFormatter : (v: number) => `${v}`; - const textParts = values.reduce((acc, value, i, { length }) => { - const parts: TextParts[] = - typeof value === 'number' - ? isFiniteNumber(value) - ? splitNumericSuffixPrefix(valueFormatter(value)) - : [{ emphasis: 'normal', text: style.nonFiniteText }] - : [{ emphasis: 'normal', text: value }]; - - if (i < length - 1) { - parts.push({ emphasis: 'normal', text: ', ' }); - } - return [...acc, ...parts]; - }, []); - - if (!Array.isArray(datum.value)) return textParts; - - return [{ emphasis: 'normal', text: '[' }, ...textParts, { emphasis: 'normal', text: ']' }]; -} - -function splitNumericSuffixPrefix(text: string): TextParts[] { - return text - .split('') - .reduce<{ emphasis: 'normal' | 'small'; textParts: string[] }[]>((acc, curr) => { - const emphasis = curr === '.' || curr === ',' || isFiniteNumber(Number.parseInt(curr)) ? 'normal' : 'small'; - if (acc.length > 0 && acc.at(-1)?.emphasis === emphasis) { - acc.at(-1)?.textParts.push(curr); - } else { - acc.push({ emphasis, textParts: [curr] }); - } - return acc; - }, []) - .map(({ emphasis, textParts }) => ({ - emphasis, - text: textParts.join(''), - })); -} - -/** - * Approximate font size to fit given available space - * @internal - */ -export function getFitValueFontSize( - valueFontSize: number, - width: number, - gapHeight: number, - textParts: TextParts[], - minValueFontSize: number, - hasIcon: boolean, -): number { - const maxWidth = (width - 2 * PADDING) * 0.98; // small buffer to prevent clipping - const widthConstrainedSize = withTextMeasure((textMeasure) => { - const iconMultiplier = hasIcon ? 1 : 0; - const textWidth = textParts.reduce((sum, { text, emphasis }) => { - const fontSize = emphasis === 'small' ? valueFontSize / VALUE_PART_FONT_RATIO : valueFontSize; - return sum + textMeasure(text, VALUE_FONT, fontSize).width; - }, 0); - const ratio = textWidth / valueFontSize; - return (maxWidth - iconMultiplier * PADDING) / (ratio + iconMultiplier / VALUE_PART_FONT_RATIO); - }); - const heightConstrainedSize = valueFontSize + gapHeight; - - return Math.max(Math.min(heightConstrainedSize, widthConstrainedSize), minValueFontSize); -} - -/** @internal */ -export function getMetricTextPartDimensions(datum: MetricDatum, panel: Size, style: MetricStyle, locale: string) { - const sizes = getFontSizes(HEIGHT_BP, panel.height, style); - const hasProgressBar = isMetricWProgress(datum); - const hasTarget = !isNil((datum as MetricWNumber)?.target); - const progressBarDirection = isMetricWProgress(datum) ? datum.progressBarDirection : undefined; - - return { - sizes, - hasProgressBar, - progressBarDirection, - progressBarWidth: - hasProgressBar && progressBarDirection === LayoutDirection.Vertical - ? PROGRESS_BAR_WIDTH + (hasTarget ? PROGRESS_BAR_TARGET_WIDTH : 0) - : 0, - visibility: elementVisibility(datum, panel, sizes, locale, style.valueFontSize === 'fit'), - textParts: getTextParts(datum, style), - }; -} diff --git a/packages/charts/src/chart_types/metric/renderer/dom/text_measurements.tsx b/packages/charts/src/chart_types/metric/renderer/dom/text_measurements.tsx new file mode 100644 index 0000000000..8a7fe4951d --- /dev/null +++ b/packages/charts/src/chart_types/metric/renderer/dom/text_measurements.tsx @@ -0,0 +1,308 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getTextParts, TextParts } from './text_processing'; +import { DEFAULT_FONT_FAMILY } from '../../../../common/default_theme_attributes'; +import { Font } from '../../../../common/text_utils'; +import { TextMeasure, withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; +import { clamp, isNil, LayoutDirection } from '../../../../utils/common'; +import { Size } from '../../../../utils/dimensions'; +import { wrapText } from '../../../../utils/text/wrap'; +import { MetricStyle } from '../../../../utils/themes/theme'; +import { MetricDatum, isMetricWProgress, MetricWNumber } from '../../specs'; + +interface HeightBasedSizes { + iconSize: number; + titleFontSize: number; + subtitleFontSize: number; + extraFontSize: number; + valueFontSize: number; + valuePartFontSize: number; +} + +type BreakPoint = 'xxxs' | 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'; + +type ElementVisibility = { + titleMaxLines: number; + subtitleMaxLines: number; + title: boolean; + subtitle: boolean; + extra: boolean; +}; + +/** @internal */ +export const PADDING = 8; + +const PROGRESS_BAR_WIDTH = 10; // synced with scss variables +const PROGRESS_BAR_TARGET_WIDTH = 4; +const LINE_HEIGHT = 1.2; // aligned with our CSS +const HEIGHT_BP: [number, number, BreakPoint][] = [ + [100, 200, 'xs'], + [200, 300, 's'], + [300, 400, 'm'], + [400, 500, 'l'], + [500, 600, 'xl'], + [600, Infinity, 'xxl'], +]; +const ICON_SIZE: Record = { xxxs: 16, xxs: 16, xs: 16, s: 16, m: 24, l: 24, xl: 32, xxl: 42 }; +const TITLE_FONT_SIZE: Record = { xxxs: 16, xxs: 16, xs: 16, s: 16, m: 24, l: 24, xl: 32, xxl: 42 }; +const SUBTITLE_FONT_SIZE: Record = { + xxxs: 14, + xxs: 14, + xs: 14, + s: 14, + m: 16, + l: 20, + xl: 26, + xxl: 36, +}; +const EXTRA_FONT_SIZE: Record = { xxxs: 14, xxs: 14, xs: 14, s: 14, m: 16, l: 20, xl: 26, xxl: 36 }; +const VALUE_FONT_SIZE: Record = { + xxxs: 16, + xxs: 26, + xs: 36, + s: 42, + m: 56, + l: 72, + xl: 104, + xxl: 170, +}; +const VALUE_FONT_SIZE_VALUES = [ + VALUE_FONT_SIZE.xxxs, + VALUE_FONT_SIZE.xxs, + VALUE_FONT_SIZE.xs, + VALUE_FONT_SIZE.s, + VALUE_FONT_SIZE.m, + VALUE_FONT_SIZE.l, + VALUE_FONT_SIZE.xl, +]; +const VALUE_PART_FONT_RATIO = 1.3; +const TITLE_FONT: Font = { + fontStyle: 'normal', + fontFamily: DEFAULT_FONT_FAMILY, + fontVariant: 'normal', + fontWeight: 'bold', + textColor: 'black', +}; +const VALUE_FONT = TITLE_FONT; +const SUBTITLE_FONT: Font = { + ...TITLE_FONT, + fontWeight: 'normal', +}; + +/** + * Approximate font size to fit given available space + * @internal + */ +export function getFitValueFontSize( + valueFontSize: number, + width: number, + gapHeight: number, + textParts: TextParts[], + minValueFontSize: number, + hasIcon: boolean, +): number { + const maxWidth = (width - 2 * PADDING) * 0.98; // small buffer to prevent clipping + const widthConstrainedSize = withTextMeasure((textMeasure) => { + const iconMultiplier = hasIcon ? 1 : 0; + const textWidth = textParts.reduce((sum, { text, emphasis }) => { + const fontSize = emphasis === 'small' ? Math.floor(valueFontSize / VALUE_PART_FONT_RATIO) : valueFontSize; + return sum + textMeasure(text, VALUE_FONT, fontSize).width; + }, 0); + const ratio = textWidth / valueFontSize; + return (maxWidth - iconMultiplier * PADDING) / (ratio + iconMultiplier / VALUE_PART_FONT_RATIO); + }); + const heightConstrainedSize = valueFontSize + gapHeight; + + return Math.floor(Math.max(Math.min(heightConstrainedSize, widthConstrainedSize), minValueFontSize)); +} + +/** @internal */ +export interface MetricTextDimensions { + heightBasedSizes: HeightBasedSizes; + hasProgressBar: boolean; + progressBarDirection: LayoutDirection | undefined; + progressBarWidth: number; + visibility: ElementVisibility & { + titleLines: string[]; + subtitleLines: string[]; + gapHeight: number; + }; + textParts: TextParts[]; +} + +/** @internal */ +export function getMetricTextPartDimensions( + datum: MetricDatum, + panel: Size, + style: MetricStyle, + locale: string, +): MetricTextDimensions { + const heightBasedSizes = getHeightBasedFontSizes(HEIGHT_BP, panel.height, style); + const hasProgressBar = isMetricWProgress(datum); + const hasTarget = !isNil((datum as MetricWNumber)?.target); + const progressBarDirection = isMetricWProgress(datum) ? datum.progressBarDirection : undefined; + + return { + heightBasedSizes, + hasProgressBar, + progressBarDirection, + progressBarWidth: + hasProgressBar && progressBarDirection === LayoutDirection.Vertical + ? PROGRESS_BAR_WIDTH + (hasTarget ? PROGRESS_BAR_TARGET_WIDTH : 0) + : 0, + visibility: elementVisibility(datum, panel, heightBasedSizes, locale, style.valueFontSize === 'fit'), + textParts: getTextParts(datum, style), + }; +} + +/** @internal */ +function getHeightBasedFontSizes( + ranges: [number, number, BreakPoint][], + value: number, + style: MetricStyle, +): HeightBasedSizes { + const range = ranges.find(([min, max]) => min <= value && value < max); + const size = range ? range[2] : ranges[0]?.[2] ?? 's'; + const valueFontSize = typeof style.valueFontSize === 'number' ? style.valueFontSize : VALUE_FONT_SIZE[size]; + const valuePartFontSize = Math.floor(valueFontSize / VALUE_PART_FONT_RATIO); + + return { + iconSize: ICON_SIZE[size], + titleFontSize: TITLE_FONT_SIZE[size], + subtitleFontSize: SUBTITLE_FONT_SIZE[size], + extraFontSize: EXTRA_FONT_SIZE[size], + valueFontSize, + valuePartFontSize, + }; +} + +/** @internal */ +export function getFittedFontSizes( + fittedValueFontSize: number, +): Pick { + return { + valueFontSize: fittedValueFontSize, + valuePartFontSize: Math.floor(fittedValueFontSize / VALUE_PART_FONT_RATIO), + }; +} +/** @internal */ +export function getFixedFontSizes( + fixedFontSize: number, +): Pick { + return { + valueFontSize: fixedFontSize, + valuePartFontSize: Math.floor(fixedFontSize / VALUE_PART_FONT_RATIO), + }; +} + +/** @internal */ +export function getSnappedFontSizes( + fittedValueFontSize: number, + panelHeight: number, + style: MetricStyle, +): Pick { + const sizes = getHeightBasedFontSizes(HEIGHT_BP, panelHeight, style); + const minFontSize = Math.min(fittedValueFontSize, sizes.valueFontSize); + const fontSize = clamp( + VALUE_FONT_SIZE_VALUES.findLast((value) => value <= minFontSize) ?? minFontSize, + VALUE_FONT_SIZE.xxxs, + VALUE_FONT_SIZE.xxl, + ); + return { + valueFontSize: fontSize, + valuePartFontSize: Math.floor(fontSize / VALUE_PART_FONT_RATIO), + }; +} + +/** @internal */ +export function elementVisibility( + datum: MetricDatum, + panel: Size, + sizes: HeightBasedSizes, + locale: string, + fit: boolean, +): ElementVisibility & { + titleLines: string[]; + subtitleLines: string[]; + gapHeight: number; +} { + const maxTitlesWidth = 0.95 * panel.width - (datum.icon ? 24 : 0) - 2 * PADDING; + const titleHeight = (maxLines: number, textMeasure: TextMeasure) => { + return datum.title + ? PADDING + + wrapText(datum.title, TITLE_FONT, sizes.titleFontSize, maxTitlesWidth, maxLines, textMeasure, locale).length * + sizes.titleFontSize * + LINE_HEIGHT + : 0; + }; + + const subtitleHeight = (maxLines: number, textMeasure: TextMeasure) => { + return datum.subtitle + ? PADDING + + wrapText(datum.subtitle, SUBTITLE_FONT, sizes.subtitleFontSize, maxTitlesWidth, maxLines, textMeasure, locale) + .length * + sizes.subtitleFontSize * + LINE_HEIGHT + : 0; + }; + + const extraHeight = sizes.extraFontSize * LINE_HEIGHT; + const valueHeight = sizes.valueFontSize * LINE_HEIGHT; + + const responsiveBreakPoints: Array = [ + { titleMaxLines: 3, subtitleMaxLines: 2, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, + { titleMaxLines: 3, subtitleMaxLines: 1, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, + { titleMaxLines: 2, subtitleMaxLines: 1, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, + { titleMaxLines: 1, subtitleMaxLines: 1, title: !!datum.title, subtitle: !!datum.subtitle, extra: !!datum.extra }, + { titleMaxLines: 1, subtitleMaxLines: 0, title: !!datum.title, subtitle: false, extra: !!datum.extra }, + { titleMaxLines: 1, subtitleMaxLines: 0, title: !!datum.title, subtitle: false, extra: false }, + { titleMaxLines: 1, subtitleMaxLines: 0, title: !!datum.title, subtitle: false, extra: false }, + ]; + + const getCombinedHeight = ( + { titleMaxLines, subtitleMaxLines, title, subtitle, extra }: ElementVisibility, + measure: TextMeasure, + ) => + (title && titleMaxLines > 0 ? titleHeight(titleMaxLines, measure) : 0) + + (subtitle && subtitleMaxLines > 0 ? subtitleHeight(subtitleMaxLines, measure) : 0) + + (extra ? extraHeight : 0) + + valueHeight + + PADDING; + + const isVisible = (ev: ElementVisibility, measure: TextMeasure) => getCombinedHeight(ev, measure) < panel.height; + + return withTextMeasure((textMeasure) => { + const visibilityBreakpoint = fit + ? responsiveBreakPoints.at(0)! + : responsiveBreakPoints.find((breakpoint) => isVisible(breakpoint, textMeasure)) ?? responsiveBreakPoints.at(-1)!; + + return { + ...visibilityBreakpoint, + gapHeight: Math.max(0, panel.height - getCombinedHeight(visibilityBreakpoint, textMeasure)), + titleLines: wrapText( + datum.title ?? '', + TITLE_FONT, + sizes.titleFontSize, + maxTitlesWidth, + visibilityBreakpoint.titleMaxLines, + textMeasure, + locale, + ), + subtitleLines: wrapText( + datum.subtitle ?? '', + SUBTITLE_FONT, + sizes.subtitleFontSize, + maxTitlesWidth, + visibilityBreakpoint.subtitleMaxLines, + textMeasure, + locale, + ), + }; + }); +} diff --git a/packages/charts/src/chart_types/metric/renderer/dom/text_processing.tsx b/packages/charts/src/chart_types/metric/renderer/dom/text_processing.tsx new file mode 100644 index 0000000000..25344baa4e --- /dev/null +++ b/packages/charts/src/chart_types/metric/renderer/dom/text_processing.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isFiniteNumber } from '../../../../utils/common'; +import { MetricStyle } from '../../../../utils/themes/theme'; +import { MetricDatum, isMetricWNumber, isMetricWNumberArrayValues } from '../../specs'; + +/** @internal */ +export interface TextParts { + emphasis: 'small' | 'normal'; + text: string; +} + +/** @internal */ + +export function getTextParts(datum: MetricDatum, style: MetricStyle): TextParts[] { + const values = Array.isArray(datum.value) ? datum.value : [datum.value]; + const valueFormatter = + isMetricWNumber(datum) || isMetricWNumberArrayValues(datum) ? datum.valueFormatter : (v: number) => `${v}`; + const textParts = values.reduce((acc, value, i, { length }) => { + const parts: TextParts[] = + typeof value === 'number' + ? isFiniteNumber(value) + ? splitNumericSuffixPrefix(valueFormatter(value)) + : [{ emphasis: 'normal', text: style.nonFiniteText }] + : [{ emphasis: 'normal', text: value }]; + + if (i < length - 1) { + parts.push({ emphasis: 'normal', text: ', ' }); + } + return [...acc, ...parts]; + }, []); + + if (!Array.isArray(datum.value)) return textParts; + + return [{ emphasis: 'normal', text: '[' }, ...textParts, { emphasis: 'normal', text: ']' }]; +} + +function splitNumericSuffixPrefix(text: string): TextParts[] { + return text + .split('') + .reduce<{ emphasis: 'normal' | 'small'; textParts: string[] }[]>((acc, curr) => { + const emphasis = curr === '.' || curr === ',' || isFiniteNumber(Number.parseInt(curr)) ? 'normal' : 'small'; + if (acc.length > 0 && acc.at(-1)?.emphasis === emphasis) { + acc.at(-1)?.textParts.push(curr); + } else { + acc.push({ emphasis, textParts: [curr] }); + } + return acc; + }, []) + .map(({ emphasis, textParts }) => ({ + emphasis, + text: textParts.join(''), + })); +}