Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 17 additions & 9 deletions web-common/src/features/components/charts/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,23 @@ export function createPositionEncoding(
...(field.max !== undefined && { domainMax: field.max }),
},
}),
axis: {
...(field.labelAngle !== undefined && { labelAngle: field.labelAngle }),
...(field.axisOrient && { orient: field.axisOrient }),
...(field.type === "quantitative" && {
formatType: sanitizeFieldName(field.field),
}),
...(metaData && "format" in metaData && { format: metaData.format }),
...(!field.showAxisTitle && { title: null }),
},
axis:
field.axisOrient === "none"
? null
: {
...(field.labelAngle !== undefined && {
labelAngle: field.labelAngle,
}),
...(field.axisOrient && { orient: field.axisOrient }),
...(field.type === "quantitative" && {
formatType: sanitizeFieldName(field.field),
}),
...(metaData &&
"format" in metaData && {
format: metaData.format,
}),
...(!field.showAxisTitle && { title: null }),
},
};
}

Expand Down
2 changes: 1 addition & 1 deletion web-common/src/features/components/charts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ interface BaseFieldConfig {
field: string;
type: "quantitative" | "ordinal" | "nominal" | "temporal" | "value";
showAxisTitle?: boolean; // Default is false
axisOrient?: "top" | "bottom" | "left" | "right";
axisOrient?: "top" | "bottom" | "left" | "right" | "none";
fields?: string[]; // To support multi metric chart variants
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
import { onDestroy } from "svelte";
import TDDHeader from "./TDDHeader.svelte";
import TDDTable from "./TDDTable.svelte";
import { useTimeDimensionDataStore } from "./time-dimension-data-store";
import {
tableInteractionStore,
useTimeDimensionDataStore,
} from "./time-dimension-data-store";
import { hoverIndex } from "@rilldata/web-common/features/dashboards/time-series/measure-chart/hover-index";
chartHoverStore,
hoverIndex,
} from "@rilldata/web-common/features/dashboards/time-series/measure-chart/hover-index";
import type { TDDComparison, TableData } from "./types";

export let exploreName: string;
Expand Down Expand Up @@ -104,7 +104,7 @@
function highlightCell(x: number | undefined, y: number | undefined) {
if (x === undefined || y === undefined) {
hoverIndex.clear("table");
tableInteractionStore.set({
chartHoverStore.set({
dimensionValue: undefined,
time: undefined,
});
Expand All @@ -118,7 +118,7 @@
}

hoverIndex.set(x, "table");
tableInteractionStore.set({
chartHoverStore.set({
dimensionValue,
time: time,
});
Expand Down Expand Up @@ -186,7 +186,7 @@

onDestroy(() => {
hoverIndex.clear("table");
tableInteractionStore.set({
chartHoverStore.set({
dimensionValue: undefined,
time: undefined,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,42 @@
import StackedBar from "@rilldata/web-common/components/icons/StackedBar.svelte";
import { metricsExplorerStore } from "@rilldata/web-common/features/dashboards/stores/dashboard-stores";
import { TDDChart } from "@rilldata/web-common/features/dashboards/time-dimension-details/types";
import type { ComponentType, SvelteComponent } from "svelte";

export let exploreName: string;
export let chartType: TDDChart;
export let hasComparison: boolean;
export let onChartTypeChange: ((type: TDDChart) => void) | undefined =
undefined;

const comparisonCharts = [TDDChart.STACKED_AREA, TDDChart.STACKED_BAR];

const chartTypeTabs = [
let buttonEls: HTMLElement[] = [];
let indicatorLeft = 0;
let indicatorWidth = 0;
let ready = false;

$: activeIndex = chartTypeTabs.findIndex((t) => t.id === chartType);

$: if (buttonEls[activeIndex]) {
const el = buttonEls[activeIndex];
indicatorLeft = el.offsetLeft;
indicatorWidth = el.offsetWidth;
ready = true;
}

const comparisonChartFallbacks: Record<TDDChart, TDDChart> = {
[TDDChart.STACKED_AREA]: TDDChart.DEFAULT,
[TDDChart.STACKED_BAR]: TDDChart.GROUPED_BAR,
[TDDChart.DEFAULT]: TDDChart.DEFAULT,
[TDDChart.GROUPED_BAR]: TDDChart.GROUPED_BAR,
};

const chartTypeTabs: {
label: string;
id: TDDChart;
Icon: ComponentType<SvelteComponent>;
}[] = [
{
label: "Line",
id: TDDChart.DEFAULT,
Expand All @@ -38,34 +66,55 @@

function handleChartTypeChange(type: TDDChart, isDisabled: boolean) {
if (isDisabled) return;
metricsExplorerStore.setTDDChartType(exploreName, type);
if (onChartTypeChange) {
onChartTypeChange(type);
} else {
metricsExplorerStore.setTDDChartType(exploreName, type);
}
}

// switch to default if current selected chart is not available
// switch to non-comparison fallback if current selected chart is not available
$: if (!hasComparison && comparisonCharts.includes(chartType)) {
metricsExplorerStore.setTDDChartType(exploreName, TDDChart.DEFAULT);
const fallback = comparisonChartFallbacks[chartType];
if (onChartTypeChange) {
onChartTypeChange(fallback);
} else {
metricsExplorerStore.setTDDChartType(exploreName, fallback);
}
}
</script>

<div class="chart-type-selector">
{#each chartTypeTabs as { label, id, Icon } (label)}
{#if ready}
<div
class="indicator"
style:left="{indicatorLeft}px"
style:width="{indicatorWidth}px"
></div>
{/if}
{#each chartTypeTabs as { label, id, Icon }, i (id)}
{@const active = chartType === id}
{@const disabled = !hasComparison && comparisonCharts.includes(id)}
<div class:bg-theme-100={active} class="chart-icon-wrapper">
<div bind:this={buttonEls[i]} class="chart-icon-wrapper" class:disabled>
<IconButton
{disabled}
disableHover
tooltipLocation="top"
onclick={() => handleChartTypeChange(id, disabled)}
ariaPressed={active}
>
<Icon
primaryColor={disabled
? "var(--color-gray-400)"
: "var(--color-theme-700)"}
secondaryColor={disabled
? "var(--color-gray-300)"
: "var(--color-theme-300)"}
size="20px"
: active
? "var(--color-theme-600)"
: "var(--color-gray-500)"}
secondaryColor={disabled
? "var(--color-gray-200)"
: active
? "var(--color-theme-300)"
: "var(--color-gray-300)"}
size="18px"
/>
<svelte:fragment slot="tooltip-content">
{disabled ? `Add comparison values to use ${label} chart` : label}
Expand All @@ -77,14 +126,27 @@

<style lang="postcss">
.chart-type-selector {
@apply flex ml-auto overflow-hidden;
@apply border border-theme-300 divide-x divide-theme-300 rounded-sm;
@apply relative flex items-center gap-x-1;
@apply bg-surface-muted rounded p-0.5;
@apply border border-slate-200;
}

.indicator {
@apply absolute rounded bg-white;
@apply top-0.5 bottom-0.5;
box-shadow:
0 1px 2px rgb(0 0 0 / 0.08),
0 0 0 1px rgb(0 0 0 / 0.04);
transition:
left 150ms cubic-bezier(0.4, 0, 0.2, 1),
width 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.chart-icon-wrapper {
@apply p-1;
@apply relative z-10;
}

.chart-icon-wrapper:hover {
@apply bg-theme-100;
.chart-icon-wrapper.disabled {
@apply opacity-80;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import type { ChartProvider } from "@rilldata/web-common/features/components/charts/types";
import { THEME_STORE_CONTEXT_KEY } from "@rilldata/web-common/features/dashboards/ThemeProvider.svelte";
import type { TimeAndFilterStore } from "@rilldata/web-common/features/dashboards/time-controls/time-control-store";
import { tableInteractionStore } from "@rilldata/web-common/features/dashboards/time-dimension-details/time-dimension-data-store";
import { chartHoverStore } from "@rilldata/web-common/features/dashboards/time-series/measure-chart/hover-index";
import type { DimensionSeriesData } from "@rilldata/web-common/features/dashboards/time-series/measure-chart/types";
import { MetricsViewSelectors } from "@rilldata/web-common/features/metrics-views/metrics-view-selectors";
import type { Theme } from "@rilldata/web-common/features/themes/theme";
Expand Down Expand Up @@ -44,6 +44,7 @@
export let dimensionValues: (string | null)[] = [];
export let dimensionData: DimensionSeriesData[] = [];
export let showComparison: boolean = false;
export let showTimeDimensionDetail: boolean = true;
export let chartType: TDDChart;
export let isScrubbing: boolean;
export let onChartHover: (
Expand Down Expand Up @@ -75,6 +76,7 @@
comparisonDimension,
dimensionValues,
dimensionData,
showTimeDimensionDetail,
);

$: componentChartType = TDD_TO_COMPONENT_CHART_TYPE[chartType];
Expand Down Expand Up @@ -147,9 +149,9 @@
isThemeModeDark: themeMode === "dark",
});

// Bidirectional highlighting: table hover → chart highlight
$: hoveredTime = $tableInteractionStore.time;
$: hoveredDimensionValue = $tableInteractionStore.dimensionValue;
// Bidirectional highlighting: table/chart hover → chart highlight
$: hoveredTime = $chartHoverStore.time;
$: hoveredDimensionValue = $chartHoverStore.dimensionValue;

$: {
if (chartView) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ export function createTDDCartesianSpec(
comparisonDimension?: string,
selectedValues?: (string | null)[],
dimensionData?: DimensionSeriesData[],
showTimeDimensionDetail = true,
): CartesianChartSpec {
const spec: CartesianChartSpec = {
metrics_view: metricsViewName,
x: {
field: timeDimension,
type: "temporal",
axisOrient: "top",
axisOrient: showTimeDimensionDetail ? "top" : "none",
},
y: {
field: measureName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { derived, writable, type Readable } from "svelte/store";
import { memoizeMetricsStore } from "../state-managers/memoize-metrics-store";
import type {
HeaderData,
HighlightedCell,
TDDCellData,
TDDComparison,
TableData,
Expand Down Expand Up @@ -466,13 +465,4 @@ export const useTimeDimensionDataStore =
createTimeDimensionDataStore(ctx),
);

/**
* Stores for handling interactions between chart and table
* Two separate stores created to avoid looped updates and renders
*/
export const tableInteractionStore = writable<HighlightedCell>({
dimensionValue: undefined,
time: undefined,
});

export const lastKnownPosition = writable<TablePosition>(undefined);
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,7 @@
<BackToExplore />
<div class="flex items-center mr-4 gap-x-1">
<ChartTypeSelector
hasComparison={Boolean(
showComparison || includedValuesForDimension.length,
)}
hasComparison={Boolean(includedValuesForDimension.length)}
{exploreName}
chartType={tddChartType}
/>
Expand Down Expand Up @@ -343,6 +341,11 @@
onDynamicYAxisScaleChange={(v) =>
metricsExplorerStore.setDynamicYAxisScale(exploreName, v)}
/>
<ChartTypeSelector
hasComparison={Boolean(includedValuesForDimension.length)}
{exploreName}
chartType={tddChartType}
/>

{#if !hideStartPivotButton}
<div class="grow"></div>
Expand Down Expand Up @@ -403,9 +406,7 @@
{measure}
{scrubController}
{connectNulls}
tddChartType={showTimeDimensionDetail
? (tddChartType ?? TDDChart.DEFAULT)
: TDDChart.DEFAULT}
tddChartType={tddChartType ?? TDDChart.DEFAULT}
metricsViewName={chartMetricsViewName}
where={chartWhere}
{timeDimension}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { createAnnotationsQuery } from "../annotations-selectors";
import { adjustTimeInterval, localToTimeZoneOffset } from "../utils";
import { hoverIndex } from "./hover-index";
import { chartHoverStore } from "./hover-index";
import { createVisibilityObserver } from "./interactions";
import MeasureChartBody from "./MeasureChartBody.svelte";
import { ScrubController } from "./ScrubController";
Expand Down Expand Up @@ -232,7 +233,7 @@

// TDD handlers
function handleTddHover(
_dimension: undefined | string | null,
dimension: undefined | string | null,
ts: Date | undefined,
) {
if (ts && !isNaN(ts.getTime())) {
Expand All @@ -242,8 +243,11 @@
const adjustedTs = localToTimeZoneOffset(ts, timeZone);
const idx = dateToIndex(data, adjustedTs.getTime());
if (idx !== null) hoverIndex.set(idx, "tddChart");
// Propagate to sibling TDD Vega charts in Explore
chartHoverStore.set({ dimensionValue: dimension ?? undefined, time: ts });
} else {
hoverIndex.clear("tddChart");
chartHoverStore.set({ dimensionValue: undefined, time: undefined });
}
}

Expand Down Expand Up @@ -313,6 +317,7 @@
{dimensionValues}
{dimensionData}
{showComparison}
{showTimeDimensionDetail}
isScrubbing={tddIsScrubbing}
onChartHover={handleTddHover}
onChartBrush={handleTddBrush}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { writable } from "svelte/store";
import type { ScaleLinear } from "d3-scale";

export interface ChartHoverState {
dimensionValue: string | undefined | null;
time: Date | undefined;
}

export interface HoverRange {
start: number;
end: number;
Expand Down Expand Up @@ -39,3 +44,9 @@ function createHoverIndex() {
}

export const hoverIndex = createHoverIndex();

/** Shared hover state across TDD Vega charts (explore and TDD view). */
export const chartHoverStore = writable<ChartHoverState>({
dimensionValue: undefined,
time: undefined,
});
Loading