From 4308d5d78d56449a2152aea68150601a2253bda5 Mon Sep 17 00:00:00 2001 From: Dmitrii Tunikov Date: Wed, 29 Oct 2025 10:10:29 +0100 Subject: [PATCH 1/2] feat: add customizable chart colors via props This change allows users to customize chart colors (markColor, markColorFade, etc.) by passing a `chartColors` prop to the EmbeddingAtlas component. This provides flexibility for theming and branding while maintaining backward compatibility with default colors. Changes: - Add ChartColors interface and export it from api.ts - Add chartColors prop to EmbeddingAtlasProps - Add getChartColors() helper function to merge custom colors with defaults - Update ChartContext to include customChartColors field - Update all chart components (CountPlot, Histogram, BoxPlot, etc.) to use getChartColors() - Pass chartColors through EmbeddingAtlas.svelte to chart context Example usage: ```typescript new EmbeddingAtlas(container, { coordinator, data: { table: "data", id: "id" }, chartColors: { light: { markColor: "#ff6b6b", markColorFade: "#ffe0e0" }, dark: { markColor: "#ff6b6b", markColorFade: "#4d2020" } } }); ``` --- packages/viewer/src/EmbeddingAtlas.svelte | 2 + packages/viewer/src/api.ts | 9 ++++ .../viewer/src/charts/basic/BoxPlot.svelte | 6 +-- .../viewer/src/charts/basic/CountPlot.svelte | 6 +-- .../src/charts/basic/CountPlotList.svelte | 5 ++- .../viewer/src/charts/basic/Histogram.svelte | 6 +-- .../src/charts/basic/HistogramStack.svelte | 6 +-- packages/viewer/src/charts/chart.ts | 7 +++ packages/viewer/src/charts/common/colors.ts | 44 ++++++++++++++++++- 9 files changed, 76 insertions(+), 15 deletions(-) diff --git a/packages/viewer/src/EmbeddingAtlas.svelte b/packages/viewer/src/EmbeddingAtlas.svelte index bf586e99..487abf2b 100644 --- a/packages/viewer/src/EmbeddingAtlas.svelte +++ b/packages/viewer/src/EmbeddingAtlas.svelte @@ -38,6 +38,7 @@ embeddingViewConfig = null, embeddingViewLabels = null, colorScheme: colorSchemeProp, + chartColors: customChartColors, tableCellRenderers, onExportApplication, onExportSelection, @@ -279,6 +280,7 @@ id: data.id, columns: [], colorScheme: colorScheme, + customChartColors: customChartColors, columnStyles: resolvedColumnStyles, cache: new ChartContextCache(), persistentCache: cache ?? { get: async () => null, set: async (key, value) => {} }, diff --git a/packages/viewer/src/api.ts b/packages/viewer/src/api.ts index b1ea7848..cfcf6793 100644 --- a/packages/viewer/src/api.ts +++ b/packages/viewer/src/api.ts @@ -10,6 +10,9 @@ import { createClassComponent } from "svelte/legacy"; import Component from "./EmbeddingAtlas.svelte"; import cssCode from "./app.css?inline"; +import type { ChartColors } from "./charts/common/colors.js"; + +export type { ChartColors }; export interface EmbeddingAtlasProps { /** The Mosaic coordinator. */ @@ -41,6 +44,12 @@ export interface EmbeddingAtlasProps { /** The color scheme. */ colorScheme?: "light" | "dark" | null; + /** Custom chart colors. You can override specific colors for light and dark themes. */ + chartColors?: { + light?: Partial; + dark?: Partial; + } | null; + /** The initial viewer state. */ initialState?: EmbeddingAtlasState | null; diff --git a/packages/viewer/src/charts/basic/BoxPlot.svelte b/packages/viewer/src/charts/basic/BoxPlot.svelte index a5fce92a..253ce472 100644 --- a/packages/viewer/src/charts/basic/BoxPlot.svelte +++ b/packages/viewer/src/charts/basic/BoxPlot.svelte @@ -10,7 +10,7 @@ import ScaleTypePicker from "../controls/ScaleTypePicker.svelte"; import type { ChartViewProps } from "../chart.js"; - import { chartColors } from "../common/colors.js"; + import { getChartColors } from "../common/colors.js"; import { distributionAggregate, distributionStats, @@ -51,11 +51,11 @@ onStateChange, }: ChartViewProps = $props(); - let { colorScheme } = context; + let { colorScheme, customChartColors } = context; let { brush } = $derived(chartState); - let colors = $derived(chartColors[$colorScheme]); + let colors = $derived(getChartColors($colorScheme, customChartColors)); let chartData: ChartData | null = $state.raw(null); diff --git a/packages/viewer/src/charts/basic/CountPlot.svelte b/packages/viewer/src/charts/basic/CountPlot.svelte index fc1f72a3..bbdc1b62 100644 --- a/packages/viewer/src/charts/basic/CountPlot.svelte +++ b/packages/viewer/src/charts/basic/CountPlot.svelte @@ -9,7 +9,7 @@ import Container from "../common/Container.svelte"; import type { ChartViewProps } from "../chart.js"; - import { chartColors } from "../common/colors.js"; + import { getChartColors } from "../common/colors.js"; import { distributionAggregate, distributionStats, @@ -33,7 +33,7 @@ onStateChange, onSpecChange, }: ChartViewProps = $props(); - let { coordinator, colorScheme } = context; + let { coordinator, colorScheme, customChartColors } = context; let { selection } = $derived(chartState); let { expanded, percentage } = $derived(spec); @@ -59,7 +59,7 @@ // Adjust scale so the minimum width for non-zero count is 1px. let xScaleAdjusted = $derived((v: number) => (v != 0 ? Math.max(1, xScale(v)) : 0)); - let colors = $derived(chartColors[$colorScheme]); + let colors = $derived(getChartColors($colorScheme, customChartColors)); function initializeClient(coordinator: Coordinator, table: string, field: string, filter: Selection) { let stats: any | null = $state.raw(null); diff --git a/packages/viewer/src/charts/basic/CountPlotList.svelte b/packages/viewer/src/charts/basic/CountPlotList.svelte index 7adb99e8..3e1263d0 100644 --- a/packages/viewer/src/charts/basic/CountPlotList.svelte +++ b/packages/viewer/src/charts/basic/CountPlotList.svelte @@ -44,7 +44,7 @@ import Container from "../common/Container.svelte"; import type { ChartViewProps } from "../chart.js"; - import { chartColors } from "../common/colors.js"; + import { getChartColors } from "../common/colors.js"; const MAX_BARS = 10; const MAX_BARS_EXPANDED = 100; @@ -71,6 +71,7 @@ }: ChartViewProps = $props(); let colorScheme = context.colorScheme; + let customChartColors = context.customChartColors; let { selection } = $derived(chartState); interface Bin { @@ -93,7 +94,7 @@ // Adjust scale so the minimum width for non-zero count is 1px. let xScaleAdjusted = $derived((v: number) => (v != 0 ? Math.max(1, xScale(v)) : 0)); - let colors = $derived(chartColors[$colorScheme]); + let colors = $derived(getChartColors($colorScheme, customChartColors)); function initializeClient(coordinator: Coordinator, table: string, field: string, filter: Selection, limit: number) { let stats: any | null = $state.raw(null); diff --git a/packages/viewer/src/charts/basic/Histogram.svelte b/packages/viewer/src/charts/basic/Histogram.svelte index aad4ade0..6aa74553 100644 --- a/packages/viewer/src/charts/basic/Histogram.svelte +++ b/packages/viewer/src/charts/basic/Histogram.svelte @@ -11,7 +11,7 @@ import ScaleTypePicker from "../controls/ScaleTypePicker.svelte"; import type { ChartViewProps } from "../chart.js"; - import { chartColors } from "../common/colors.js"; + import { getChartColors } from "../common/colors.js"; import { distributionAggregate, distributionStats, @@ -47,11 +47,11 @@ onStateChange, }: ChartViewProps = $props(); - let { coordinator, colorScheme } = context; + let { coordinator, colorScheme, customChartColors } = context; let { brush } = $derived(chartState); - let colors = $derived(chartColors[$colorScheme]); + let colors = $derived(getChartColors($colorScheme, customChartColors)); let chartData: ChartData | null = $state.raw(null); let defaultScaleType = $state(null); diff --git a/packages/viewer/src/charts/basic/HistogramStack.svelte b/packages/viewer/src/charts/basic/HistogramStack.svelte index 3cd02b0b..87e218a0 100644 --- a/packages/viewer/src/charts/basic/HistogramStack.svelte +++ b/packages/viewer/src/charts/basic/HistogramStack.svelte @@ -12,7 +12,7 @@ import ScaleTypePicker from "../controls/ScaleTypePicker.svelte"; import type { ChartViewProps } from "../chart.js"; - import { chartColors } from "../common/colors.js"; + import { getChartColors } from "../common/colors.js"; import { distributionAggregate, distributionStats, @@ -49,11 +49,11 @@ onStateChange, }: ChartViewProps = $props(); - let { colorScheme } = context; + let { colorScheme, customChartColors } = context; let { brush } = $derived(chartState); - let colors = $derived(chartColors[$colorScheme]); + let colors = $derived(getChartColors($colorScheme, customChartColors)); let chartData = $state.raw(null); let defaultScaleType = $state(null); diff --git a/packages/viewer/src/charts/chart.ts b/packages/viewer/src/charts/chart.ts index 120d9752..88847176 100644 --- a/packages/viewer/src/charts/chart.ts +++ b/packages/viewer/src/charts/chart.ts @@ -6,6 +6,7 @@ import type { Coordinator, Selection } from "@uwdata/mosaic-core"; import type { Readable, Writable } from "svelte/store"; import type { ColumnDesc } from "../utils/database.js"; +import type { ChartColors } from "./common/colors.js"; export class ChartContextCache { private contents: Map; @@ -53,6 +54,12 @@ export interface ChartContext { /** The current color scheme. */ colorScheme: Readable<"light" | "dark">; + /** Custom chart colors (optional overrides). */ + customChartColors?: { + light?: Partial; + dark?: Partial; + } | null; + /** The column styles. */ columnStyles: Readable; diff --git a/packages/viewer/src/charts/common/colors.ts b/packages/viewer/src/charts/common/colors.ts index 669be603..4aea47f7 100644 --- a/packages/viewer/src/charts/common/colors.ts +++ b/packages/viewer/src/charts/common/colors.ts @@ -13,7 +13,23 @@ function adjustForGray(hex: string, lightnessShift: number = 0): string { return c.rgb().formatHex8(); } -export const chartColors = { +export interface ChartColors { + scheme: "light" | "dark"; + continuousColorScheme: string; + continuousColorSchemeAtZero: string; + markColor: string; + markColorFade: string; + markColorGray: string; + markColorGrayFade: string; + gridColor: string; + labelColor: string; + titleColor: string; + brushBorder: string; + brushBorderBack: string; + brushFill: string; +} + +export const chartColors: Record<"light" | "dark", ChartColors> = { light: { scheme: "light", continuousColorScheme: "YlGnBu", @@ -53,3 +69,29 @@ export function defaultOrdinalColors(count: number): string[] { } return result; } + +/** + * Get chart colors with optional custom overrides. + * @param scheme - The color scheme ("light" or "dark") + * @param customColors - Optional custom color overrides + * @returns The merged chart colors + */ +export function getChartColors( + scheme: "light" | "dark", + customColors?: { + light?: Partial; + dark?: Partial; + } | null, +): ChartColors { + const baseColors = chartColors[scheme]; + const overrides = customColors?.[scheme]; + + if (!overrides) { + return baseColors; + } + + return { + ...baseColors, + ...overrides, + }; +} From 1268ef1516936ba8ee575d79969dfd8736ca790b Mon Sep 17 00:00:00 2001 From: Dmitrii Tunikov Date: Wed, 29 Oct 2025 11:00:47 +0100 Subject: [PATCH 2/2] feat: add support for embedding view and table theme customization Extends the customizable color system to include embedding view and table components. Changes: - Add `theme` field to EmbeddingViewConfig for customizing embedding view colors and fonts - Export `Theme` type from table package for public API - Add `tableTheme` prop to EmbeddingAtlasProps for customizing table colors and fonts - Add `tableTheme` field to ChartContext and wire it through to Table component - Update Table.svelte to merge custom theme with default theme This complements the chart colors customization added in the previous commit, providing a comprehensive color customization API for all visualization components. Example usage: ```typescript new EmbeddingAtlas(container, { coordinator, data: { table: "data", id: "id", projection: { x: "x", y: "y" } }, // Chart colors chartColors: { light: { markColor: "#ff6b6b" }, dark: { markColor: "#ff6b6b" } }, // Embedding view theme (via config) embeddingViewConfig: { theme: { clusterLabelColor: "#ff0000", statusBarBackgroundColor: "rgba(255, 255, 255, 0.95)" } }, // Table theme tableTheme: { light: { primaryBackgroundColor: "#f5f5f5" }, dark: { primaryBackgroundColor: "#1a1a1a" } } }); ``` --- .../src/lib/embedding_view/embedding_view_config.ts | 5 +++++ packages/table/src/lib/index.ts | 2 +- packages/viewer/src/EmbeddingAtlas.svelte | 2 ++ packages/viewer/src/api.ts | 7 +++++-- packages/viewer/src/charts/chart.ts | 4 ++++ packages/viewer/src/charts/table/Table.svelte | 6 +++++- 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/component/src/lib/embedding_view/embedding_view_config.ts b/packages/component/src/lib/embedding_view/embedding_view_config.ts index 492454ec..7cf61e46 100644 --- a/packages/component/src/lib/embedding_view/embedding_view_config.ts +++ b/packages/component/src/lib/embedding_view/embedding_view_config.ts @@ -1,9 +1,14 @@ // Copyright (c) 2025 Apple Inc. Licensed under MIT License. +import type { ThemeConfig } from "./theme.js"; + export interface EmbeddingViewConfig { /** Color scheme. */ colorScheme?: "light" | "dark" | null; + /** Theme configuration for customizing colors, fonts, and other visual properties. */ + theme?: ThemeConfig | null; + /** View mode. */ mode?: "points" | "density" | null; diff --git a/packages/table/src/lib/index.ts b/packages/table/src/lib/index.ts index 730a4fa3..5b8c0f1b 100644 --- a/packages/table/src/lib/index.ts +++ b/packages/table/src/lib/index.ts @@ -147,4 +147,4 @@ export class Table { } } -export type { CustomCell, AdditionalHeaderContent as CustomHeader }; +export type { CustomCell, AdditionalHeaderContent as CustomHeader, Theme }; diff --git a/packages/viewer/src/EmbeddingAtlas.svelte b/packages/viewer/src/EmbeddingAtlas.svelte index 487abf2b..8f693936 100644 --- a/packages/viewer/src/EmbeddingAtlas.svelte +++ b/packages/viewer/src/EmbeddingAtlas.svelte @@ -40,6 +40,7 @@ colorScheme: colorSchemeProp, chartColors: customChartColors, tableCellRenderers, + tableTheme, onExportApplication, onExportSelection, onStateChange, @@ -291,6 +292,7 @@ embeddingViewConfig: embeddingViewConfig, embeddingViewLabels: embeddingViewLabels, tableCellRenderers: tableCellRenderers, + tableTheme: tableTheme, }; let charts = $state.raw>({}); diff --git a/packages/viewer/src/api.ts b/packages/viewer/src/api.ts index cfcf6793..02aaac3f 100644 --- a/packages/viewer/src/api.ts +++ b/packages/viewer/src/api.ts @@ -3,7 +3,7 @@ // The component API for embedding viewer. import type { EmbeddingViewConfig, Label } from "@embedding-atlas/component"; -import type { CustomCell } from "@embedding-atlas/table"; +import type { CustomCell, Theme as TableTheme } from "@embedding-atlas/table"; import type { Coordinator } from "@uwdata/mosaic-core"; import { createClassComponent } from "svelte/legacy"; @@ -12,7 +12,7 @@ import Component from "./EmbeddingAtlas.svelte"; import cssCode from "./app.css?inline"; import type { ChartColors } from "./charts/common/colors.js"; -export type { ChartColors }; +export type { ChartColors, TableTheme }; export interface EmbeddingAtlasProps { /** The Mosaic coordinator. */ @@ -67,6 +67,9 @@ export interface EmbeddingAtlasProps { /** Custom cell renderers for the table view. */ tableCellRenderers?: Record; + /** Custom theme for the table view. Allows customization of colors, fonts, and other visual properties. */ + tableTheme?: TableTheme | null; + /** A callback to export the currently selected points. */ onExportSelection?: | ((predicate: string | null, format: "json" | "jsonl" | "csv" | "parquet") => Promise) diff --git a/packages/viewer/src/charts/chart.ts b/packages/viewer/src/charts/chart.ts index 88847176..957b5364 100644 --- a/packages/viewer/src/charts/chart.ts +++ b/packages/viewer/src/charts/chart.ts @@ -2,6 +2,7 @@ import type { EmbeddingViewConfig, Label } from "@embedding-atlas/component"; import type { CustomCell } from "@embedding-atlas/table"; +import type { Theme as TableTheme } from "@embedding-atlas/table"; import type { Coordinator, Selection } from "@uwdata/mosaic-core"; import type { Readable, Writable } from "svelte/store"; @@ -100,6 +101,9 @@ export interface ChartContext { /** Custom cell renderers for the table view. */ tableCellRenderers?: Record; + + /** Custom theme for the table view. */ + tableTheme?: TableTheme | null; } /** Props passed into a chart view. */ diff --git a/packages/viewer/src/charts/table/Table.svelte b/packages/viewer/src/charts/table/Table.svelte index ea18f815..7743dd12 100644 --- a/packages/viewer/src/charts/table/Table.svelte +++ b/packages/viewer/src/charts/table/Table.svelte @@ -47,6 +47,10 @@ let resolvedCustomCellRenderers = $derived( resolveCustomCellRenderers(context.columns, $columnStyles, context.tableCellRenderers), ); + + let resolvedTableTheme = $derived( + context.tableTheme ? { ...tableTheme, ...context.tableTheme } : tableTheme, + );