Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5abf00d
Add dimmed color support for bar charts
awahab07 Jan 19, 2026
49dcef6
feat: implement hybrid highlighting for partition charts
awahab07 Jan 20, 2026
89aef6f
fix: pass legendStrategy, flatLegend, and arcSeriesStyle to partition…
awahab07 Jan 20, 2026
611ab4a
feat: add shade color selection controls for testing dimmed colors
awahab07 Jan 20, 2026
97a7e76
Add dimmed highlight style story (Stylings -> Dimmed Highlight Style)…
awahab07 Jan 20, 2026
5b2803d
fix: correct import paths and type guards for partition dimming
awahab07 Jan 21, 2026
36fa035
revert: remove extra series from bar_clicks and sunburst stories
awahab07 Jan 21, 2026
e5a9f9a
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Jan 21, 2026
97cb8ec
Add 'middle' for Metric chart's `valuePosition` configuration.
awahab07 Jan 26, 2026
1331670
Revert "Add 'middle' for Metric chart's `valuePosition` configuration."
awahab07 Jan 26, 2026
82b300f
PR feedback, update default shades.
awahab07 Jan 27, 2026
d9fdb76
Api update, lint and type fixes.
awahab07 Jan 27, 2026
b59f7de
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Jan 27, 2026
bceaf5c
Update jest snapshots.
awahab07 Jan 27, 2026
c7e926b
Merge remote-tracking branch 'upstream/main' into 245829_Charts-Hover…
awahab07 Feb 12, 2026
3901a6f
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Feb 12, 2026
41f551c
PR Feedback:
awahab07 Mar 5, 2026
490a64f
Extract and reuse dimmed color utils.
awahab07 Mar 5, 2026
09983e2
Add e2e snapshots tests for highlight/hover states.
awahab07 Mar 5, 2026
ce3d551
Use `PartitionStyle.dimmed` for partition charts instead of `ArcStyl…
awahab07 Mar 5, 2026
b85f863
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 5, 2026
d197208
PR feedback: Make `dimmed` config required in styles. Fix type infere…
awahab07 Mar 10, 2026
27e942f
Fix "Dimmed highlight style" stories to correctly focus the relevant …
awahab07 Mar 10, 2026
9f6563a
Merge remote-tracking branch 'upstream/main' into 245829_Charts-Hover…
awahab07 Mar 10, 2026
560d586
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 10, 2026
4df72c2
Update story for better e2e snapshot test.
awahab07 Mar 10, 2026
7845e71
Fix types in tests.
awahab07 Mar 10, 2026
715b5c7
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 10, 2026
8820e2d
Remove unused component.
awahab07 Mar 10, 2026
120c4fa
PR feedback:
awahab07 Mar 23, 2026
b2985b2
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 23, 2026
2fd52f8
PR feedback: Use `overrideOpacity` in partition and bar renderers whe…
awahab07 Mar 24, 2026
ffd3056
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 24, 2026
9b9abbf
Fix opacity story and tests.
awahab07 Mar 24, 2026
c6b3da8
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 24, 2026
0fd360d
Add dimming for falme charts.
awahab07 Mar 25, 2026
ff19d78
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 25, 2026
dd56c52
Pull unrelated updated snapshot from upstream.
awahab07 Mar 25, 2026
685ae57
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions e2e/tests/flame_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,24 @@ test.describe('Flame stories', () => {
},
);
});

test('should dim icicle chart (linear renderer) on legend hover - fill dimming', async ({ page }) => {
const action = async () => {
await page.locator('.echLegendItem').first().hover();
};
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9001/?path=/story/flame-alpha--icicle-chart&globals=theme:light&knob-Clip%20text%20%28use%20linear%20renderer%29=true`,
{ action },
);
});

test('should dim icicle chart (linear renderer) on legend hover - opacity dimming', async ({ page }) => {
const action = async () => {
await page.locator('.echLegendItem').first().hover();
};
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9001/?path=/story/flame-alpha--icicle-chart&globals=theme:light&knob-Clip%20text%20%28use%20linear%20renderer%29=true&knob-Use%20opacity-only%20dimming=true`,
{ action },
);
});
});
22 changes: 22 additions & 0 deletions e2e/tests/partition_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,26 @@ test.describe('Axis stories', () => {
{ left: 300, top: 100 },
);
});

eachTheme.describe(({ theme, urlParam }) => {
test(`should dim slices on legend hover - sunburst - ${theme}`, async ({ page }) => {
const action = async () => {
await common.moveMouseRelativeToDOMElement(page)({ left: 5, top: 5 }, '.echLegendItem');
};
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9001/?path=/story/legend--piechart&${urlParam}&knob-Partition Layout=sunburst&knob-flatLegend=true&knob-legendMaxDepth=2`,
{ action },
);
});

test(`should dim slices on legend hover - treemap - ${theme}`, async ({ page }) => {
const action = async () => {
await common.moveMouseRelativeToDOMElement(page)({ left: 5, top: 5 }, '.echLegendItem');
};
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9001/?path=/story/legend--piechart&${urlParam}&knob-Partition Layout=treemap&knob-flatLegend=true&knob-legendMaxDepth=2`,
{ action },
);
});
});
});
56 changes: 55 additions & 1 deletion e2e/tests/stylings_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { test } from '@playwright/test';

import { SeriesType } from '../constants';
import { pwEach } from '../helpers';
import { eachTheme, pwEach } from '../helpers';
import { common } from '../page_objects';

test.describe('Stylings stories', () => {
Expand Down Expand Up @@ -89,4 +89,58 @@ test.describe('Stylings stories', () => {
{ top: 150, right: 150 },
);
});

test.describe('Dimmed highlight style', () => {
eachTheme.describe(({ theme, urlParam }) => {
test(`should dim XY chart series on Bars 2 legend hover - ${theme}`, async ({ page }) => {
const action = async () => {
// Hover on the "Bars 2" legend item
const legendItem = page.locator('[data-ech-series-name="Bars 2"]');
await legendItem.hover();
};
await common.expectChartAtUrlToMatchScreenshot(page)(
// Hide partition chart so XY chart is the first .echChart
`http://localhost:9001/?path=/story/stylings--dimmed-highlight-style&${urlParam}&knob-Show Partition Chart=false`,
{ action },
);
});

test(`should dim partition chart slices on legend hover - ${theme}`, async ({ page }) => {
const action = async () => {
// Hover on the first legend item
const legendItem = page.locator('.echLegendItem').first();
await legendItem.hover();
};
await common.expectChartAtUrlToMatchScreenshot(page)(
// Hide XY chart so partition chart is the only .echChart
`http://localhost:9001/?path=/story/stylings--dimmed-highlight-style&${urlParam}&knob-Show XY Chart=false`,
{ action },
);
});
});

test('should apply opacity-only dimming on bar chart - light theme', async ({ page }) => {
const action = async () => {
// Hover on the "Bars 2" legend item
const legendItem = page.locator('[data-ech-series-name="Bars 2"]');
await legendItem.hover();
};
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9001/?path=/story/stylings--dimmed-highlight-style&globals=theme:light&knob-Show Partition Chart=false&knob-Use opacity-only dimming=true&knob-Alpha (opacity)=0.10`,
{ action },
);
});

test('should apply opacity-only dimming on partition chart - dark theme', async ({ page }) => {
const action = async () => {
// Hover on the first legend item
const legendItem = page.locator('.echLegendItem').first();
await legendItem.hover();
};
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9001/?path=/story/stylings--dimmed-highlight-style&globals=theme:dark&knob-Show XY Chart=false&knob-Use opacity-only dimming=true&knob-Alpha (opacity)=0.15`,
{ action },
);
});
});
});
13 changes: 13 additions & 0 deletions e2e/tests/waffle_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { test } from '@playwright/test';

import { eachTheme } from '../helpers';
import { common } from '../page_objects/common';

test.describe('Waffle', () => {
Expand All @@ -21,4 +22,16 @@ test.describe('Waffle', () => {
'http://localhost:9001/?path=/story/waffle-alpha--test&globals=toggles.showHeader:true;toggles.showChartTitle:false;toggles.showChartDescription:false;theme:light&knob-use alpha=true',
);
});

eachTheme.describe(({ theme, urlParam }) => {
test(`should dim cells on legend hover - ${theme}`, async ({ page }) => {
const action = async () => {
await common.moveMouseRelativeToDOMElement(page)({ left: 5, top: 5 }, '.echLegendItem');
};
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9001/?path=/story/waffle-alpha--simple&${urlParam}`,
{ action },
);
});
});
});
16 changes: 16 additions & 0 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2315,6 +2315,13 @@ export type PartialTheme = RecursivePartial<Theme>;
// @public
export const Partition: <D extends BaseDatum = any>(props: SFProps<PartitionSpec<D>, keyof (typeof buildProps)["overrides"], keyof (typeof buildProps)["defaults"], keyof (typeof buildProps)["optionals"], keyof (typeof buildProps)["requires"]>) => null;

// @public (undocumented)
export type PartitionDimmedStyle = {
opacity: number;
} | {
fill: Color | ColorVariant;
};

// @public
export type PartitionElementEvent = [layers: Array<LayerValue>, seriesIdentifier: SeriesIdentifier];

Expand Down Expand Up @@ -2370,6 +2377,7 @@ export interface PartitionStyle extends FillFontSizeRange {
//
// (undocumented)
circlePadding: Distance;
dimmed: PartitionDimmedStyle;
// Warning: (ae-forgotten-export) The symbol "SizeRatio" needs to be exported by the entry point index.d.ts
emptySizeRatio: SizeRatio;
// (undocumented)
Expand Down Expand Up @@ -2655,6 +2663,14 @@ export interface RectBorderStyle {

// @public (undocumented)
export interface RectStyle {
dimmed: {
opacity: number;
} | {
fill: Color | ColorVariant;
texture: {
opacity: number;
};
};
fill?: Color | ColorVariant;
opacity: number;
texture?: TexturedStyles;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@
*/

import type { AnimationState, ContinuousDomainFocus } from './partition';
import { multiplyColorOpacity } from '../../../../common/color_library_wrappers';
import { Colors } from '../../../../common/colors';
import type { ShapeViewModel } from '../../layout/types/viewmodel_types';
import type { LegendPath } from '../../../../state/actions/legend';
import { getColorFromVariant } from '../../../../utils/common';
import { getDimmedColor } from '../../../../utils/themes/dimmed_colors';
import type { PartitionStyle } from '../../../../utils/themes/partition';
import type { QuadViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
import type { LegendStrategy } from '../../layout/utils/highlighted_geoms';
import { highlightedGeoms } from '../../layout/utils/highlighted_geoms';

const linear = (x: number) => x;
const easeInOut = (alpha: number) => (x: number) => x ** alpha / (x ** alpha + (1 - x) ** alpha);
Expand All @@ -30,8 +37,19 @@ export function renderLinearPartitionCanvas2d(
animation,
}: ShapeViewModel,
{ currentFocusX0, currentFocusX1, prevFocusX0, prevFocusX1 }: ContinuousDomainFocus,
highlightedLegendPath: LegendPath,
legendStrategy: LegendStrategy | undefined,
flatLegend: boolean | undefined,
partitionStyle: PartitionStyle,
animationState: AnimationState,
) {
// Calculate which quads are highlighted for legend dimming
const highlightedQuadSet = new Set<QuadViewModel>();
if (highlightedLegendPath.length > 0) {
const highlighted = highlightedGeoms(legendStrategy, flatLegend, quadViewModel, highlightedLegendPath);
highlighted.forEach((quad) => highlightedQuadSet.add(quad));
}

if (animation?.duration) {
window.cancelAnimationFrame(animationState.rafId);
render(0);
Expand Down Expand Up @@ -72,7 +90,8 @@ export function renderLinearPartitionCanvas2d(
ctx.translate(diskCenter.x, diskCenter.y);
ctx.clearRect(0, 0, width, height);

quadViewModel.forEach(({ fillColor, x0, x1, y0px: y0, y1px: y1, dataName, textColor, depth }) => {
quadViewModel.forEach((quad) => {
const { fillColor, x0, x1, y0px: y0, y1px: y1, dataName, textColor, depth } = quad;
if (y1 - y0 <= padding) return;

const fx0 = Math.max((x0 - focusX0) * scale, 0);
Expand All @@ -87,7 +106,18 @@ export function renderLinearPartitionCanvas2d(
const fWidth = fx1 - fx0;
const fPadding = Math.min(padding, MAX_PADDING_RATIO * fWidth);

ctx.fillStyle = fillColor;
const isDimmed = highlightedQuadSet.size > 0 && !highlightedQuadSet.has(quad);
const baseFillColor = getColorFromVariant(
fillColor,
getDimmedColor(isDimmed, partitionStyle.dimmed, 'fill', fillColor),
);
// Apply opacity when dimmed with opacity config
const dimmedFillColor =
isDimmed && 'opacity' in partitionStyle.dimmed
? multiplyColorOpacity(baseFillColor, partitionStyle.dimmed.opacity)
: baseFillColor;

ctx.fillStyle = dimmedFillColor;
ctx.beginPath();
ctx.rect(fx0 + fPadding, y0 + padding / 2, fWidth - fPadding, y1 - y0 - padding);
ctx.fill();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
* Side Public License, v 1.
*/

import { colorToRgba, RGBATupleToString } from '../../../../common/color_library_wrappers';
import { colorToRgba, multiplyColorOpacity, RGBATupleToString } from '../../../../common/color_library_wrappers';
import type { Color } from '../../../../common/colors';
import { TAU } from '../../../../common/constants';
import type { Pixels } from '../../../../common/geometry';
import { cssFontShorthand, HorizontalAlignment } from '../../../../common/text_utils';
import { renderLayers, withContext } from '../../../../renderers/canvas';
import { MIN_STROKE_WIDTH } from '../../../../renderers/canvas/primitives/line';
import type { LegendPath } from '../../../../state/actions/legend';
import { getColorFromVariant } from '../../../../utils/common';
import { getDimmedColor } from '../../../../utils/themes/dimmed_colors';
import type { PartitionStyle } from '../../../../utils/themes/partition';
import type {
LinkLabelVM,
OutsideLinksViewModel,
Expand All @@ -22,6 +26,8 @@ import type {
ShapeViewModel,
TextRow,
} from '../../layout/types/viewmodel_types';
import type { LegendStrategy } from '../../layout/utils/highlighted_geoms';
import { highlightedGeoms } from '../../layout/utils/highlighted_geoms';
import type { LinkLabelsViewModelSpec } from '../../layout/viewmodel/link_text_layout';
import { isSunburst } from '../../layout/viewmodel/viewmodel';

Expand Down Expand Up @@ -103,7 +109,8 @@ function renderRowSets(ctx: CanvasRenderingContext2D, rowSets: RowSet[], linkLab

function renderTaperedBorder(
ctx: CanvasRenderingContext2D,
{ strokeWidth, strokeStyle, fillColor, x0, x1, y0px, y1px }: QuadViewModel,
{ strokeWidth, strokeStyle, x0, x1, y0px, y1px }: QuadViewModel,
fillColor: Color,
) {
const X0 = x0 - TAU / 4;
const X1 = x1 - TAU / 4;
Expand Down Expand Up @@ -149,22 +156,56 @@ function renderTaperedBorder(
}
}

function renderSectors(ctx: CanvasRenderingContext2D, quadViewModel: QuadViewModel[]) {
function renderSectors(
ctx: CanvasRenderingContext2D,
quadViewModel: QuadViewModel[],
highlightedQuadSet: Set<QuadViewModel>,
partitionStyle: PartitionStyle,
) {
withContext(ctx, () => {
ctx.scale(1, -1); // D3 and Canvas2d use a left-handed coordinate system (+y = down) but the ViewModel uses +y = up, so we must locally invert Y
quadViewModel.forEach((quad: QuadViewModel) => {
if (quad.x0 !== quad.x1) renderTaperedBorder(ctx, quad);
if (quad.x0 === quad.x1) return;

const isDimmed = highlightedQuadSet.size > 0 && !highlightedQuadSet.has(quad);
const baseFillColor = getColorFromVariant(
quad.fillColor,
getDimmedColor(isDimmed, partitionStyle.dimmed, 'fill', quad.fillColor),
);
// Apply opacity when dimmed with opacity config
const fillColor =
isDimmed && 'opacity' in partitionStyle.dimmed
? multiplyColorOpacity(baseFillColor, partitionStyle.dimmed.opacity)
: baseFillColor;
renderTaperedBorder(ctx, quad, fillColor);
});
});
}

function renderRectangles(ctx: CanvasRenderingContext2D, quadViewModel: QuadViewModel[]) {
function renderRectangles(
ctx: CanvasRenderingContext2D,
quadViewModel: QuadViewModel[],
highlightedQuadSet: Set<QuadViewModel>,
partitionStyle: PartitionStyle,
) {
withContext(ctx, () => {
ctx.scale(1, -1); // D3 and Canvas2d use a left-handed coordinate system (+y = down) but the ViewModel uses +y = up, so we must locally invert Y
quadViewModel.forEach(({ strokeWidth, fillColor, x0, x1, y0px, y1px }) => {
quadViewModel.forEach((quad) => {
const { strokeWidth, fillColor, x0, x1, y0px, y1px } = quad;
// only draw a shape if it would show up at all
if (x1 - x0 >= 1 && y1px - y0px >= 1) {
ctx.fillStyle = fillColor;
const isDimmed = highlightedQuadSet.size > 0 && !highlightedQuadSet.has(quad);
const baseFillColor = getColorFromVariant(
fillColor,
getDimmedColor(isDimmed, partitionStyle.dimmed, 'fill', fillColor),
);
// Apply opacity when dimmed with opacity config
const dimmedFillColor =
isDimmed && 'opacity' in partitionStyle.dimmed
? multiplyColorOpacity(baseFillColor, partitionStyle.dimmed.opacity)
: baseFillColor;

ctx.fillStyle = dimmedFillColor;
ctx.beginPath();
ctx.moveTo(x0, y0px);
ctx.lineTo(x0, y1px);
Expand Down Expand Up @@ -277,6 +318,10 @@ export function renderPartitionCanvas2d(
panel,
chartDimensions,
}: ShapeViewModel,
highlightedLegendPath: LegendPath,
legendStrategy: LegendStrategy | undefined,
flatLegend: boolean | undefined,
partitionStyle: PartitionStyle,
) {
const { sectorLineWidth, sectorLineStroke, linkLabel } = style;

Expand Down Expand Up @@ -322,13 +367,24 @@ export function renderPartitionCanvas2d(
ctx.strokeStyle = sectorLineStroke;
ctx.lineWidth = sectorLineWidth;

// Calculate which quads are highlighted for legend dimming
const highlightedQuadSet = new Set<QuadViewModel>();
if (highlightedLegendPath.length > 0) {
// Use highlightedGeoms to determine which quads match the legend path
const highlighted = highlightedGeoms(legendStrategy, flatLegend, quadViewModel, highlightedLegendPath);
highlighted.forEach((quad) => highlightedQuadSet.add(quad));
}

// painter's algorithm, like that of SVG: the sequence determines what overdraws what; first element of the array is drawn first
// (of course, with SVG, it's for ambiguous situations only, eg. when 3D transforms with different Z values aren't used, but
// unlike SVG and esp. WebGL, Canvas2d doesn't support the 3rd dimension well, see ctx.transform / ctx.setTransform).
// The layers are callbacks, because of the need to not bake in the `ctx`, it feels more composable and uncoupled this way.
renderLayers(ctx, [
// bottom layer: sectors (pie slices, ring sectors etc.)
() => (isSunburst(layout) ? renderSectors(ctx, quadViewModel) : renderRectangles(ctx, quadViewModel)),
() =>
isSunburst(layout)
? renderSectors(ctx, quadViewModel, highlightedQuadSet, partitionStyle)
: renderRectangles(ctx, quadViewModel, highlightedQuadSet, partitionStyle),

// all the fill-based, potentially multirow text, whether inside or outside the sector
() => renderRowSets(ctx, rowSets, linkLineColor),
Expand Down
Loading