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
1,420 changes: 717 additions & 703 deletions proto/gen/rill/runtime/v1/resources.pb.go

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions proto/gen/rill/runtime/v1/resources.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions proto/gen/rill/runtime/v1/runtime.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5356,6 +5356,11 @@ definitions:
type: boolean
pivotShowTotalsRow:
type: boolean
pivotFormatting:
type: string
description: |-
Per-measure pivot conditional formatting, serialized in the URL param
format (frontend-only; persisted in URL state).
chartDynamicYAxis:
type: boolean
title: Chart display settings (frontend-only; persisted in URL state)
Expand Down
3 changes: 3 additions & 0 deletions proto/rill/runtime/v1/resources.proto
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,9 @@ message ExplorePreset {
optional int32 pivot_row_limit = 33;
optional bool pivot_show_totals_column = 37;
optional bool pivot_show_totals_row = 38;
// Per-measure pivot conditional formatting, serialized in the URL param
// format (frontend-only; persisted in URL state).
optional string pivot_formatting = 39;

// Chart display settings (frontend-only; persisted in URL state)
optional bool chart_dynamic_y_axis = 35;
Expand Down
28 changes: 28 additions & 0 deletions proto/rill/ui/v1/dashboard.proto
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ message DashboardState {
repeated PivotElement pivot_row_all_dimensions = 35;
// List of all dimensions selected for columns
repeated PivotElement pivot_column_all_dimensions = 36;

// Per-measure conditional formatting (heatmap / data bar) for pivot cells.
repeated PivotConditionalFormat pivot_conditional_formatting = 44;
}

message DashboardTimeRange {
Expand All @@ -180,3 +183,28 @@ message PivotElement {
string pivot_dimension = 2;
}
}

// Conditional formatting applied to a measure's cells in a pivot table.
message PivotConditionalFormat {
string measure = 1;
// Formatting style: "heatmap", "data_bar" or "rules".
string mode = 2;
// Color scheme key (e.g. "theme-sequential", "greens", "redYellowGreen").
// Only for "heatmap" and "data_bar" modes.
string scheme = 3;
// Flip the gradient direction (heatmap only).
bool reverse = 4;
// Ordered threshold rules; first match wins. Only for "rules" mode.
repeated PivotFormatRule rules = 5;
}

// A single threshold rule for "rules" mode conditional formatting.
message PivotFormatRule {
// Comparison operator: "gt", "gte", "lt", "lte", "eq" or "between".
string operator = 1;
double value = 2;
// Upper bound (inclusive), only for "between".
optional double value2 = 3;
// Semantic color key (e.g. "positive") or hex string.
string color = 4;
}
7 changes: 7 additions & 0 deletions web-common/src/components/table/tanstack-table-column-meta.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { RowData } from "tanstack-table-8-svelte-5";
import type { ComponentType, SvelteComponent } from "svelte";
import type { PivotMeasureFormatting } from "@rilldata/web-common/features/dashboards/pivot/types";

declare module "tanstack-table-8-svelte-5" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -12,5 +13,11 @@ declare module "tanstack-table-8-svelte-5" {
/** Description shown in the header name tooltip */
description?: string;
tooltipFormatter?: (value: unknown) => string | null | undefined;
/** Conditional formatting config for a main measure column (heatmap/data bar) */
conditionalFormat?: PivotMeasureFormatting;
/** Base measure name, used to look up the per-measure color domain */
measureName?: string;
/** True for the row-totals column leaves, which are excluded from formatting */
isRowTotal?: boolean;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<script lang="ts">
import type { PivotCanvasComponent } from "@rilldata/web-common/features/canvas/components/pivot";
import {
conditionalFormatSpecToMeasureFormatting,
type PivotCanvasComponent,
} from "@rilldata/web-common/features/canvas/components/pivot";
import ComponentHeader from "../../ComponentHeader.svelte";
import CanvasPivotRenderer from "./CanvasPivotRenderer.svelte";
import { validateTableSchema } from "./selector";
Expand Down Expand Up @@ -40,6 +43,11 @@
$: schema = validateTableSchema(metricsViewSpec, tableSpec);
$: widthScopeKey = `canvas:${component.parent.name}:${component.id}`;

// Seed the shared pivot state with per-measure formatting from the YAML spec.
$: measureFormatting = conditionalFormatSpecToMeasureFormatting(
tableSpec.conditional_format,
);

$: if ("columns" in tableSpec && schema.isValid) {
const columns = tableSpec?.columns || [];
pivotState.update((state) => ({
Expand All @@ -52,6 +60,7 @@
columns: tableFieldMapper(columns, metricsViewSpec),
showTotalsColumn: tableSpec.hide_totals_col !== true,
showTotalsRow: tableSpec.hide_totals_row !== true,
measureFormatting,
}));
} else if (!("columns" in tableSpec) && schema.isValid) {
const measures = tableSpec.measures || [];
Expand All @@ -71,6 +80,7 @@
rows: tableFieldMapper(rowDimensions, metricsViewSpec),
showTotalsColumn: tableSpec.hide_totals_col !== true,
showTotalsRow: tableSpec.hide_totals_row !== true,
measureFormatting,
}));
}
</script>
Expand Down
87 changes: 85 additions & 2 deletions web-common/src/features/canvas/components/pivot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
import type { InputParams } from "@rilldata/web-common/features/canvas/inspector/types";
import type {
PivotDataStoreConfig,
PivotFormatRule,
PivotMeasureFormatting,
PivotState,
} from "@rilldata/web-common/features/dashboards/pivot/types";
import type { ExploreState } from "@rilldata/web-common/features/dashboards/stores/explore-state";
Expand All @@ -25,6 +27,69 @@ import type {
import CanvasPivotDisplay from "./CanvasPivotDisplay.svelte";
import { createPivotConfig, usePivotForCanvas } from "./util";

// Per-measure conditional formatting persisted in the canvas YAML. A list (not
// a map) keeps the YAML readable and mirrors the proto representation.
export interface PivotConditionalFormatSpec {
measure: string;
mode: "heatmap" | "data_bar" | "rules";
// Color scheme; only for "heatmap" and "data_bar" modes.
scheme?: string;
reverse?: boolean;
// Ordered threshold rules (first match wins); only for "rules" mode.
rules?: {
operator: PivotFormatRule["operator"];
value: number;
value2?: number;
color: string;
}[];
}

const DEFAULT_FORMAT_SCHEME = "theme-sequential";

/**
* Map the YAML conditional_format list to the per-measure formatting config
* consumed by the pivot renderer. Malformed entries are skipped.
*/
export function conditionalFormatSpecToMeasureFormatting(
specs: PivotConditionalFormatSpec[] | undefined,
): Record<string, PivotMeasureFormatting> {
const measureFormatting: Record<string, PivotMeasureFormatting> = {};
for (const spec of specs ?? []) {
if (spec.mode === "heatmap" || spec.mode === "data_bar") {
measureFormatting[spec.measure] = {
mode: spec.mode,
scheme: spec.scheme ?? DEFAULT_FORMAT_SCHEME,
reverse: spec.reverse,
};
} else if (spec.mode === "rules" && spec.rules?.length) {
measureFormatting[spec.measure] = {
mode: "rules",
rules: spec.rules,
};
}
}
return measureFormatting;
}

/**
* Inverse of conditionalFormatSpecToMeasureFormatting, used when writing the
* inspector state back to the YAML.
*/
export function measureFormattingToConditionalFormatSpec(
measureFormatting: Record<string, PivotMeasureFormatting>,
): PivotConditionalFormatSpec[] {
return Object.entries(measureFormatting).map(([measure, fmt]) =>
fmt.mode === "rules"
? { measure, mode: fmt.mode, rules: fmt.rules }
: {
measure,
mode: fmt.mode,
scheme: fmt.scheme,
...(fmt.reverse ? { reverse: true } : {}),
},
);
}

export interface PivotSpec
extends ComponentCommonProperties,
ComponentFilterProperties {
Expand All @@ -34,6 +99,7 @@ export interface PivotSpec
col_dimensions?: string[];
hide_totals_row?: boolean;
hide_totals_col?: boolean;
conditional_format?: PivotConditionalFormatSpec[];
}

export interface TableSpec
Expand All @@ -43,6 +109,7 @@ export interface TableSpec
columns: string[];
hide_totals_row?: boolean;
hide_totals_col?: boolean;
conditional_format?: PivotConditionalFormatSpec[];
}

export { default as Pivot } from "./CanvasPivotDisplay.svelte";
Expand Down Expand Up @@ -141,6 +208,22 @@ export class PivotCanvasComponent extends BaseCanvasComponent<
activePage: DashboardState_ActivePage.PIVOT,
};
}

/** Update a single measure's conditional formatting in the YAML; pass null to clear it. */
setMeasureFormatting(measureName: string, fmt: PivotMeasureFormatting | null) {
const measureFormatting = conditionalFormatSpecToMeasureFormatting(
get(this.specStore).conditional_format,
);
if (fmt) {
measureFormatting[measureName] = fmt;
} else {
delete measureFormatting[measureName];
}
this.updateProperty(
"conditional_format",
measureFormattingToConditionalFormatSpec(measureFormatting),
);
}
inputParams(type: "pivot" | "table"): InputParams<PivotSpec | TableSpec> {
const spec = get(this.specStore);

Expand All @@ -160,7 +243,7 @@ export class PivotCanvasComponent extends BaseCanvasComponent<
options: {
metrics_view: { type: "metrics", label: "Metrics view" },
measures: {
type: "multi_fields",
type: "multi_fields_format",
meta: { allowedTypes: ["measure"] },
label: "Measures",
},
Expand Down Expand Up @@ -206,7 +289,7 @@ export class PivotCanvasComponent extends BaseCanvasComponent<
options: {
metrics_view: { type: "metrics", label: "Metrics view" },
columns: {
type: "multi_fields",
type: "multi_fields_format",
label: "Columns",
meta: { allowedTypes: ["time", "dimension", "measure"] },
},
Expand Down
Loading