Compare commits

...

5 Commits

Author SHA1 Message Date
Aleksandr Lesnenko
c26a5f0d48 render real data 2024-08-28 00:40:29 -04:00
Aleksandr Lesnenko
d59bdfafe0 register heatmap echarts components 2024-08-28 00:39:11 -04:00
Aleksandr Lesnenko
55b35830c0 add hardcoded echarts heatmap chart 2024-08-27 23:54:23 -04:00
Aleksandr Lesnenko
ea42530ebe add heatmap chart definition 2024-08-27 23:53:57 -04:00
Aleksandr Lesnenko
56908e1c8c echarts viz ergonomic tweaks 2024-08-27 23:52:18 -04:00
11 changed files with 130 additions and 21 deletions

View File

@@ -1,5 +1,7 @@
import styled from "@emotion/styled";
import { ResponsiveEChartsRendererExplicitSize } from "./ResponsiveEChartsRenderer";
export const ResponsiveEChartsRendererStyled = styled.div`
position: absolute;
top: 0;
@@ -7,3 +9,9 @@ export const ResponsiveEChartsRendererStyled = styled.div`
bottom: 0;
right: 0;
`;
export const ResponsiveEChartsRenderer = styled(
ResponsiveEChartsRendererExplicitSize,
)`
height: 100%;
`;

View File

@@ -7,7 +7,7 @@ import { EChartsRenderer } from "metabase/visualizations/components/EChartsRende
import { ResponsiveEChartsRendererStyled } from "metabase/visualizations/components/EChartsRenderer/ResponsiveEChartsRenderer.styled";
export interface ResponsiveEChartsRendererProps extends EChartsRendererProps {
onResize: (width: number, height: number) => void;
onResize?: (width: number, height: number) => void;
}
const _ResponsiveEChartsRenderer = forwardRef<
@@ -24,7 +24,7 @@ const _ResponsiveEChartsRenderer = forwardRef<
) {
useEffect(() => {
if (isNumber(width) && isNumber(height)) {
onResize(width, height);
onResize?.(width, height);
}
}, [width, height, onResize]);
@@ -44,7 +44,7 @@ const _ResponsiveEChartsRenderer = forwardRef<
);
});
export const ResponsiveEChartsRenderer =
export const ResponsiveEChartsRendererExplicitSize =
ExplicitSize<ResponsiveEChartsRendererProps>({
wrapped: true,
refreshMode: "debounceLeading",

View File

@@ -1,2 +1,3 @@
export * from "./EChartsRenderer";
export * from "./ResponsiveEChartsRenderer";
export type { ResponsiveEChartsRendererProps } from "./ResponsiveEChartsRenderer";
export { ResponsiveEChartsRenderer } from "./ResponsiveEChartsRenderer.styled";

View File

@@ -1,12 +1,14 @@
import {
BarChart,
CustomChart,
HeatmapChart,
LineChart,
ScatterChart,
SunburstChart,
} from "echarts/charts";
import {
BrushComponent,
CalendarComponent,
DataZoomComponent,
DatasetComponent,
GraphicComponent,
@@ -14,6 +16,7 @@ import {
MarkLineComponent,
ToolboxComponent,
TooltipComponent,
VisualMapComponent,
} from "echarts/components";
import { use } from "echarts/core";
import { LabelLayout } from "echarts/features";
@@ -26,6 +29,7 @@ export const registerEChartsModules = () => {
ScatterChart,
CustomChart,
SunburstChart,
HeatmapChart,
GraphicComponent,
GridComponent,
BarChart,
@@ -37,5 +41,7 @@ export const registerEChartsModules = () => {
DatasetComponent,
LabelLayout,
TooltipComponent,
CalendarComponent,
VisualMapComponent,
]);
};

View File

@@ -11,6 +11,7 @@ import { DashCardPlaceholder } from "./visualizations/DashCardPlaceholder";
import { Funnel } from "./visualizations/Funnel";
import Gauge from "./visualizations/Gauge";
import { Heading } from "./visualizations/Heading";
import { Heatmap } from "./visualizations/Heatmap";
import { LineChart } from "./visualizations/LineChart";
import { LinkViz } from "./visualizations/LinkViz";
import { Map } from "./visualizations/Map";
@@ -44,6 +45,7 @@ export default function () {
registerVisualization(Funnel);
registerVisualization(ObjectDetail);
registerVisualization(PivotTable);
registerVisualization(Heatmap);
registerVisualization(ActionViz);
registerVisualization(DashCardPlaceholder);

View File

@@ -1,6 +1,5 @@
import styled from "@emotion/styled";
import { ResponsiveEChartsRenderer } from "metabase/visualizations/components/EChartsRenderer";
import { LegendLayout } from "metabase/visualizations/components/legend/LegendLayout";
import { getChartPadding } from "./padding";
@@ -21,8 +20,3 @@ export const CartesianChartRoot = styled.div<CartesianChartRootProps>`
export const CartesianChartLegendLayout = styled(LegendLayout)`
flex: 1 1 auto;
`;
// created in ExplicitSize
export const CartesianChartRenderer = styled(ResponsiveEChartsRenderer)`
height: 100%;
`;

View File

@@ -2,6 +2,7 @@ import type { EChartsType } from "echarts/core";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ChartRenderingErrorBoundary } from "metabase/visualizations/components/ChartRenderingErrorBoundary";
import { ResponsiveEChartsRenderer } from "metabase/visualizations/components/EChartsRenderer";
import LegendCaption from "metabase/visualizations/components/legend/LegendCaption";
import { getLegendItems } from "metabase/visualizations/echarts/cartesian/model/legend";
import {
@@ -11,7 +12,6 @@ import {
import type { VisualizationProps } from "metabase/visualizations/types";
import {
CartesianChartLegendLayout,
CartesianChartRenderer,
CartesianChartRoot,
} from "metabase/visualizations/visualizations/CartesianChart/CartesianChart.styled";
import { useChartEvents } from "metabase/visualizations/visualizations/CartesianChart/use-chart-events";
@@ -130,7 +130,7 @@ function _CartesianChart(props: VisualizationProps) {
width={outerWidth}
height={outerHeight}
>
<CartesianChartRenderer
<ResponsiveEChartsRenderer
ref={containerRef}
option={option}
eventHandlers={eventHandlers}

View File

@@ -0,0 +1,104 @@
import { t } from "ttag";
import _ from "underscore";
import { findWithIndex } from "metabase/lib/arrays";
import { ResponsiveEChartsRenderer } from "metabase/visualizations/components/EChartsRenderer";
import { columnSettings } from "metabase/visualizations/lib/settings/column";
import {
dimensionSetting,
metricSetting,
} from "metabase/visualizations/lib/settings/utils";
import type { VisualizationProps } from "metabase/visualizations/types";
import type { RowValue } from "metabase-types/api";
// Defines supported visualization settings
const SETTINGS_DEFINITIONS = {
// Column formatting settings
...columnSettings({ hidden: true }),
// Heatmap dimension column
...dimensionSetting("heatmap.dimension", {
section: t`Data`,
title: t`Dimension`,
showColumnSetting: true,
}),
// Heatmap metric column
...metricSetting("heatmap.metric", {
section: t`Data`,
title: t`Measure`,
showColumnSetting: true,
}),
};
export const Heatmap = ({ rawSeries, settings }: VisualizationProps) => {
// Assuming Heatmaps can use one card only, getting the dataset
const [{ data }] = rawSeries;
// Find dimension and metric columns
const dimension = findWithIndex(
data.cols,
col => col.name === settings["heatmap.dimension"],
);
const metric = findWithIndex(
data.cols,
col => col.name === settings["heatmap.metric"],
);
// Create a dataset in the format expected by ECharts
const echartsData = data.rows.map(row => {
// Get dimension and metric values from a row
const dimensionValue = row[dimension.index];
const metricValue = row[metric.index];
return [dimensionValue, metricValue];
});
// Finding latest year that exists in the dataset to select it in the calendar.
// Ideally, users should be able to select it manually.
const datasetLatestYear = echartsData.reduce(
(latestYear: RowValue, row: RowValue[]) => {
const currentDate = row[0];
const currentDateYear = new Date(String(currentDate)).getFullYear();
if (typeof latestYear !== "number") {
return currentDateYear;
}
return Math.max(latestYear, currentDateYear);
},
null,
);
// Define ECharts option object that describes how to render the chart
const option = {
visualMap: {
type: "piecewise",
orient: "horizontal",
left: "center",
top: 65,
},
calendar: {
top: 120,
left: 80,
right: 80,
bottom: 120,
cellSize: ["auto", 20],
range: datasetLatestYear,
itemStyle: {
borderWidth: 0.5,
},
},
series: {
type: "heatmap",
coordinateSystem: "calendar",
data: echartsData,
},
};
return <ResponsiveEChartsRenderer option={option} />;
};
Object.assign(Heatmap, {
uiName: t`Heatmap`,
identifier: "Heatmap",
iconName: "grid",
noun: t`Heatmap`,
settings: SETTINGS_DEFINITIONS,
});

View File

@@ -0,0 +1 @@
export * from "./Heatmap";

View File

@@ -1,7 +0,0 @@
import styled from "@emotion/styled";
import { ResponsiveEChartsRenderer } from "metabase/visualizations/components/EChartsRenderer";
export const ChartRenderer = styled(ResponsiveEChartsRenderer)`
height: 100%;
`;

View File

@@ -2,6 +2,7 @@ import type { EChartsType } from "echarts/core";
import { useCallback, useMemo, useRef, useState } from "react";
import ChartWithLegend from "metabase/visualizations/components/ChartWithLegend";
import { ResponsiveEChartsRenderer } from "metabase/visualizations/components/EChartsRenderer";
import { getPieChartFormatters } from "metabase/visualizations/echarts/pie/format";
import { getPieChartModel } from "metabase/visualizations/echarts/pie/model";
import { getPieChartOption } from "metabase/visualizations/echarts/pie/option";
@@ -13,7 +14,6 @@ import {
import { useBrowserRenderingContext } from "metabase/visualizations/hooks/use-browser-rendering-context";
import type { VisualizationProps } from "metabase/visualizations/types";
import { ChartRenderer } from "./PieChart.styled";
import { PIE_CHART_DEFINITION } from "./chart-definition";
import { useChartEvents } from "./use-chart-events";
@@ -129,7 +129,7 @@ export function PieChart(props: VisualizationProps) {
hovered={props.hovered}
isDashboard={isDashboard}
>
<ChartRenderer
<ResponsiveEChartsRenderer
ref={containerRef}
option={option}
onInit={handleInit}