Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
34554e0
Add abstract base chart class
djbarnwal Apr 10, 2025
4d38755
Move to index file
djbarnwal Apr 11, 2025
33a53de
Merge branch 'main' into feat/donut-chart
djbarnwal Apr 11, 2025
e26dfef
Use base chart reference
djbarnwal Apr 12, 2025
ddc032a
Reorganize files and params
djbarnwal Apr 12, 2025
db0186f
Use updated references, add different chart types
djbarnwal Apr 13, 2025
e322936
Merge branch 'main' into feat/donut-chart
djbarnwal Apr 13, 2025
1aca55d
Consolidate chart types
djbarnwal Apr 14, 2025
72d4658
Merge main
djbarnwal Apr 14, 2025
800f46d
Move data query inside chart component
djbarnwal Apr 14, 2025
b0cce25
Add pie chart component
djbarnwal Apr 14, 2025
562fb97
Reorganize types and title
djbarnwal Apr 15, 2025
98f9f51
Merge branch 'main' into feat/donut-chart
djbarnwal Apr 15, 2025
01db521
Standardize chart input params
djbarnwal Apr 15, 2025
0f98de0
By default hide nulls
djbarnwal Apr 15, 2025
5fafd67
Add icon for donut
djbarnwal Apr 15, 2025
6403a42
Support inner radius
djbarnwal Apr 16, 2025
06718c1
add heatmap chart
djbarnwal Apr 16, 2025
5ce20c4
Consolidate chart metadata
djbarnwal Apr 16, 2025
c9f806d
lint fix
djbarnwal Apr 16, 2025
f277261
Fix import
djbarnwal Apr 16, 2025
a12305a
Remove limit from heatmap
djbarnwal Apr 16, 2025
edf5581
Add initial chart switching logic
djbarnwal Apr 16, 2025
7a2aaeb
add color field for cartesian charts
djbarnwal Apr 16, 2025
2591383
Hide time dimension for donut
djbarnwal Apr 16, 2025
bc25adc
Merge branch 'main' into feat/donut-chart
djbarnwal Apr 16, 2025
cb972b1
Move config as part of chart spec
djbarnwal Apr 17, 2025
aea0e78
Update color for heatmap
djbarnwal Apr 17, 2025
9b7ba42
Default legend right for pie charts
djbarnwal Apr 18, 2025
e88d348
Preserve matching properties when switching charts
djbarnwal Apr 18, 2025
00af025
Optimistically update chart type
djbarnwal Apr 18, 2025
3676efd
Remove logs, debug statements
djbarnwal Apr 21, 2025
1dfbb3e
clean up
djbarnwal Apr 21, 2025
0ba76ec
default heatmap legend at bottom
djbarnwal Apr 22, 2025
f78f5cb
Merge branch 'main' into feat/donut-chart
djbarnwal Apr 23, 2025
35214de
refactor to use query options paradigm
djbarnwal Apr 23, 2025
ca1eb5f
Merge branch 'main' into feat/donut-chart
djbarnwal Apr 24, 2025
cf9b4e0
lint fix
djbarnwal Apr 24, 2025
34251c1
Consolidate builder methods
djbarnwal Apr 24, 2025
5628de7
Add label angle selector
djbarnwal Apr 24, 2025
883ec99
Address heatmap container overflow
djbarnwal Apr 24, 2025
6b44dad
Add E2E test for switching chart types
djbarnwal Apr 24, 2025
87bdd75
Better query handling for temporal fields
djbarnwal Apr 24, 2025
46d94b6
Fix local timezone offset for chart data
djbarnwal Apr 24, 2025
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: 26 additions & 0 deletions web-common/src/components/icons/Donut.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script>
export let size = "18px";
export let primaryColor = "#3524C7";
export let secondaryColor = "#9CABFF";
</script>

<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 22C17.5229 22 22 17.5228 22 12C22 6.47715 17.5229 2 12 2C6.47716 2 2.00001 6.47715 2.00001 12C2.00001 17.5228 6.47716 22 12 22ZM12 17C14.7614 17 17 14.7614 17 12C17 9.23858 14.7614 7 12 7C9.23858 7 7.00001 9.23858 7.00001 12C7.00001 14.7614 9.23858 17 12 17Z"
fill={primaryColor}
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M17.5557 3.68531C15.9112 2.58649 13.9778 2 12 2V7C14.7614 7 17 9.23858 17 12C17 13.3807 16.4404 14.6307 15.5355 15.5355L19.0711 19.0711C20.4696 17.6725 21.422 15.8907 21.8079 13.9509C22.1937 12.0111 21.9957 10.0004 21.2388 8.17317C20.4819 6.34591 19.2002 4.78412 17.5557 3.68531Z"
fill={secondaryColor}
/>
</svg>
29 changes: 29 additions & 0 deletions web-common/src/components/icons/Heatmap.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script lang="ts">
export let size = "24";
export let color = "currentColor";
</script>

<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="2" y="2" width="4" height="4" fill={color} opacity="0.2" />
<rect x="7" y="2" width="4" height="4" fill={color} opacity="0.4" />
<rect x="12" y="2" width="4" height="4" fill={color} opacity="0.6" />
<rect x="17" y="2" width="4" height="4" fill={color} opacity="0.8" />
<rect x="2" y="7" width="4" height="4" fill={color} opacity="0.3" />
<rect x="7" y="7" width="4" height="4" fill={color} opacity="0.5" />
<rect x="12" y="7" width="4" height="4" fill={color} opacity="0.7" />
<rect x="17" y="7" width="4" height="4" fill={color} opacity="0.9" />
<rect x="2" y="12" width="4" height="4" fill={color} opacity="0.4" />
<rect x="7" y="12" width="4" height="4" fill={color} opacity="0.6" />
<rect x="12" y="12" width="4" height="4" fill={color} opacity="0.8" />
<rect x="17" y="12" width="4" height="4" fill={color} />
<rect x="2" y="17" width="4" height="4" fill={color} opacity="0.5" />
<rect x="7" y="17" width="4" height="4" fill={color} opacity="0.7" />
<rect x="12" y="17" width="4" height="4" fill={color} opacity="0.9" />
<rect x="17" y="17" width="4" height="4" fill={color} />
</svg>
2 changes: 1 addition & 1 deletion web-common/src/components/vega/VegaLiteRenderer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
bind:contentRect
class:bg-white={canvasDashboard}
class:px-2={canvasDashboard}
class="overflow-hidden size-full flex flex-col items-center justify-center"
class="overflow-y-auto overflow-x-hidden size-full flex flex-col items-center"
>
{#if error}
<div
Expand Down
3 changes: 3 additions & 0 deletions web-common/src/components/vega/vega-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ export const getRillTheme: (isCanvasDashboard: boolean) => Config = (
},
range: {
category: COMPARIONS_COLORS,
heatmap: {
scheme: "tealblues", // TODO: Generate this from theme
},
},
numberFormat: "s",
tooltipFormat: {
Expand Down
10 changes: 5 additions & 5 deletions web-common/src/features/canvas/AddComponentDropdown.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script lang="ts">
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu";
import { chartMetadata } from "@rilldata/web-common/features/canvas/components/charts/util";
import { Plus, PlusCircle } from "lucide-svelte";
import type { ComponentType, SvelteComponent } from "svelte";
import { CHART_TYPES } from "./components/charts";
import type { ChartType } from "./components/charts/types";
import type { CanvasComponentType } from "./components/types";
import BigNumberIcon from "./icons/BigNumberIcon.svelte";
Expand All @@ -18,11 +18,11 @@

// Function to get a random chart type
function getRandomChartType(): ChartType {
const chartTypes = chartMetadata
.map((chart) => chart.type)
.filter((t) => t !== "stacked_bar_normalized");
const chartTypes = CHART_TYPES.filter(
(t) => t !== "stacked_bar_normalized",
);
const randomIndex = Math.floor(Math.random() * chartTypes.length);
return chartTypes[randomIndex];
return chartTypes[randomIndex] as ChartType;
}

// Create menu items with a function to get random chart type when clicked
Expand Down
5 changes: 4 additions & 1 deletion web-common/src/features/canvas/ItemWrapper.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
export let type: string | undefined = undefined;

$: expandable =
type === "kpi_grid" || type === "markdown" || type === "leaderboard";
type === "kpi_grid" ||
type === "markdown" ||
type === "leaderboard" ||
type === "heatmap";
$: minHeight = getInitialHeight(type) + "px";
</script>

Expand Down
16 changes: 8 additions & 8 deletions web-common/src/features/canvas/components/BaseCanvasComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import type {
V1Resource,
V1TimeRange,
} from "@rilldata/web-common/runtime-client";
import type { ComponentType, SvelteComponent } from "svelte";
import { derived, get, writable, type Writable } from "svelte/store";
import { CanvasComponentState } from "../stores/canvas-component";
import type { CanvasEntity, ComponentPath } from "../stores/canvas-entity";
import type {
ComparisonTimeRangeState,
TimeRangeState,
} from "../../dashboards/time-controls/time-control-store";
import { mergeFilters } from "../../dashboards/pivot/pivot-merge-filters";
import {
buildValidMetricsViewFilter,
createAndExpression,
} from "../../dashboards/stores/filter-utils";
import { mergeFilters } from "../../dashboards/pivot/pivot-merge-filters";
import type { ComponentType, SvelteComponent } from "svelte";
import type {
ComparisonTimeRangeState,
TimeRangeState,
} from "../../dashboards/time-controls/time-control-store";
import { CanvasComponentState } from "../stores/canvas-component";
import type { CanvasEntity, ComponentPath } from "../stores/canvas-entity";

export abstract class BaseCanvasComponent<T = ComponentSpec> {
id: string;
Expand Down
202 changes: 202 additions & 0 deletions web-common/src/features/canvas/components/charts/BaseChart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { BaseCanvasComponent } from "@rilldata/web-common/features/canvas/components/BaseCanvasComponent";
import { CHART_CONFIG } from "@rilldata/web-common/features/canvas/components/charts";
import {
commonOptions,
createComponent,
getFilterOptions,
} from "@rilldata/web-common/features/canvas/components/util";
import type {
AllKeys,
ComponentInputParam,
InputParams,
} from "@rilldata/web-common/features/canvas/inspector/types";
import type { CanvasStore } from "@rilldata/web-common/features/canvas/state-managers/state-managers";
import type { TimeAndFilterStore } from "@rilldata/web-common/features/canvas/stores/types";
import type {
V1MetricsViewSpec,
V1Resource,
} from "@rilldata/web-common/runtime-client";
import { get, writable, type Readable, type Writable } from "svelte/store";
import type { CanvasEntity, ComponentPath } from "../../stores/canvas-entity";
import type {
ComponentCommonProperties,
ComponentFilterProperties,
} from "../types";
import Chart from "./Chart.svelte";
import type {
ChartDataQuery,
ChartFieldsMap,
ChartType,
CommonChartProperties,
FieldConfig,
} from "./types";

// Base interface for all chart configurations
export type BaseChartConfig = ComponentFilterProperties &
ComponentCommonProperties &
CommonChartProperties;

export abstract class BaseChart<
TConfig extends BaseChartConfig,
> extends BaseCanvasComponent<TConfig> {
minSize = { width: 4, height: 4 };
defaultSize = { width: 6, height: 4 };
resetParams = [];
type: ChartType;
chartType: Writable<ChartType>;
component = Chart;

constructor(resource: V1Resource, parent: CanvasEntity, path: ComponentPath) {
const baseSpec: BaseChartConfig = {
metrics_view: "",
title: "",
description: "",
};
super(resource, parent, path, baseSpec as TConfig);

this.type = resource.component?.state?.validSpec?.renderer as ChartType;
this.chartType = writable(this.type);
}

isValid(spec: TConfig): boolean {
return typeof spec.metrics_view === "string";
}

inputParams(): InputParams<TConfig> {
return {
options: {
metrics_view: { type: "metrics", label: "Metrics view" },
tooltip: { type: "tooltip", label: "Tooltip", showInUI: false },
vl_config: { type: "config", showInUI: false },
...this.getChartSpecificOptions(),
...commonOptions,
},
filter: getFilterOptions(false),
};
}

abstract getChartSpecificOptions(): Record<
AllKeys<TConfig>,
ComponentInputParam
>;

abstract createChartDataQuery(
ctx: CanvasStore,
timeAndFilterStore: Readable<TimeAndFilterStore>,
): ChartDataQuery;

abstract chartTitle(fields: ChartFieldsMap): string;

protected getDefaultFieldConfig(): Partial<FieldConfig> {
return {
showAxisTitle: true,
zeroBasedOrigin: true,
showNull: false,
};
}

updateChartType(
key: ChartType,
metricsViewSpec: V1MetricsViewSpec | undefined,
) {
if (!this.parent.fileArtifact) return;

const currentSpec = get(this.specStore);
const parentPath = this.pathInYAML.slice(0, -1);

const parseDocumentStore = this.parent.parsedContent;
const parsedDocument = get(parseDocumentStore);
const { updateEditorContent } = this.parent.fileArtifact;

const newSpecForKey = CHART_CONFIG[key].component.newComponentSpec(
currentSpec.metrics_view,
metricsViewSpec,
);

const commonProps = this.extractCommonProperties(
currentSpec,
this.type,
key,
);
const mergedSpec = {
...newSpecForKey,
...commonProps,
};

const newResource = this.parent.createOptimisticResource({
type: key,
row: this.pathInYAML[1],
column: this.pathInYAML[3],
metricsViewName: currentSpec.metrics_view,
metricsViewSpec,
spec: mergedSpec,
});

const newComponent = createComponent(
newResource,
this.parent,
this.pathInYAML,
);

this.parent.components.set(newComponent.id, newComponent);
this.parent.selectedComponent.set(newComponent.id);
this.parent._rows.refresh();

// Preserve the width from the current chart
const width = parsedDocument.getIn([...parentPath, "width"]);

parsedDocument.setIn(parentPath, { [key]: mergedSpec, width });

updateEditorContent(parsedDocument.toString(), false, true);

this.chartType.set(key);
}

private extractCommonProperties(
spec: TConfig,
sourceType: ChartType,
targetType: ChartType,
): Partial<BaseChartConfig> {
const {
metrics_view,
title,
description,
vl_config,
time_filters,
dimension_filters,
} = spec;

const sourceChartParams =
CHART_CONFIG[sourceType].component.chartInputParams || {};
const targetChartParams =
CHART_CONFIG[targetType].component.chartInputParams || {};

// Check for common keys and type match first
const commonProps = Object.keys(sourceChartParams).filter((key) => {
const isKeyAndTypeMatch =
targetChartParams?.[key]?.type === sourceChartParams[key]?.type;
const isFieldTypeMatch =
targetChartParams?.[key]?.meta?.chartFieldInput?.type ===
sourceChartParams[key]?.meta?.chartFieldInput?.type;
return isKeyAndTypeMatch && isFieldTypeMatch;
});

const commonPropsObject = commonProps.reduce(
(acc, key) => {
acc[key] = spec[key];
return acc;
},
{} as Record<string, unknown>,
);

return {
metrics_view,
title,
description,
vl_config,
time_filters,
dimension_filters,
...commonPropsObject,
};
}
}
Loading