Skip to content

Commit

Permalink
feat: add Heatmap viz
Browse files Browse the repository at this point in the history
  • Loading branch information
plagoa authored and MEsteves22 committed Apr 19, 2024
1 parent 0e08b93 commit 99c2a7a
Show file tree
Hide file tree
Showing 15 changed files with 653 additions and 11 deletions.
1 change: 1 addition & 0 deletions .storybook/test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const excludeStories = [
"Visualizations/Confusion Matrix",
"Visualizations/Scatter Plot",
"Visualizations/Treemap",
"Visualizations/Heatmap",
"Widgets/Code Editor",
];

Expand Down
2 changes: 2 additions & 0 deletions docs/guides/visualizations/Visualizations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ At the moment, the following visualizations are available and we are continuousl
- [Donut chart](./?path=/docs/visualizations-donut-chart--docs)
- [Confusion matrix](./?path=/docs/visualizations-confusion-matrix--docs)
- [Scatter plot](./?path=/docs/visualizations-scatter-plot--docs)
- [Treemap](./?path=/docs/visualizations-treemap--docs)
- [Heatmap](./?path=/docs/visualizations-heatmap--docs)

The KPI and Table components are available on the `uikit-react-core` package while all the other visualizations are in the `uikit-react-viz` package.

Expand Down
22 changes: 22 additions & 0 deletions packages/viz/src/Heatmap/Heatmap.styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createClasses, theme } from "@hitachivantara/uikit-react-core";

export const { useClasses, staticClasses } = createClasses("HvHeatmap", {
tooltipRoot: {
backgroundColor: theme.colors.atmo1,
width: "fit-content",
minWidth: 150,
boxShadow: theme.colors.shadow,
zIndex: theme.zIndices.sticky,
},
tooltipContainer: {
padding: theme.spacing("15px", "sm"),
display: "flex",
flexDirection: "column",
},
tooltipText: {
fontFamily: theme.fontFamily.body,
fontWeight: theme.fontWeights.normal,
fontSize: theme.fontSizes.sm,
color: theme.colors.secondary,
},
});
154 changes: 154 additions & 0 deletions packages/viz/src/Heatmap/Heatmap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { forwardRef } from "react";
import ReactECharts from "echarts-for-react/lib/core";
import { HeatmapChart } from "echarts/charts";
import { TooltipComponent, VisualMapComponent } from "echarts/components";
import * as echarts from "echarts/core";
import { ExtractNames, useTheme } from "@hitachivantara/uikit-react-core";

import { HvBaseChart } from "../BaseChart";
import {
useOption,
useTooltip,
useVisualMap,
useXAxis,
useYAxis,
} from "../hooks";
import { HvChartTooltip } from "../types";
import { HvChartCommonProps, XAxis, YAxis } from "../types/common";
import { useClasses } from "./Heatmap.styles";

// Register chart components
echarts.use([HeatmapChart, TooltipComponent, VisualMapComponent]);

export type HvHeatmapClasses = ExtractNames<typeof useClasses>;

export type HvHeatmapItem = Array<number | string>;

export type HvHeatmapData = Array<HvHeatmapItem>;

export interface HvHeatmapProps
extends Omit<
HvChartCommonProps,
"data" | "groupBy" | "sortBy" | "grid" | "legend" | "tooltip"
> {
/** The name of the heatmap */
name?: string;
/** The data to use on the heatmap */
data?: HvHeatmapData;
/** The min value of the Heatmap */
min: number;
/** The max value of the Heatmap */
max: number;
/** The X axis defintion */
xAxis?: XAxis;
/** The Y axis definition. */
yAxis?: YAxis;
/** The tooltip options. */
tooltip?: Omit<HvChartTooltip, "type">;
/** Color scale of the confusion matrix. Accepts an array of strings spanning from the lower to the upper ends of the scale. */
colorScale?: string[];
/** A Jss Object used to override or extend the styles applied to the component. */
classes?: HvHeatmapClasses;
}

/**
* A Heatmap uses color gradients to represent data intensity across a surface.
*/
export const HvHeatmap = forwardRef<ReactECharts, HvHeatmapProps>(
(props, ref) => {
const {
name,
data,
min,
max,
colorScale,
xAxis,
yAxis,
classes: classesProp,
tooltip,
width,
height,
onOptionChange,
...others
} = props;

const { classes } = useClasses(classesProp);
const { colors } = useTheme();

const chartTooltip = useTooltip({
component: (params) => {
const value = params?.value;
const title = params?.title;

const valueToShow = value
? `${yAxis?.data?.[value?.[1]]} - ${xAxis?.data?.[value?.[0]]}: ${params?.series?.[0]?.name}`
: "-";

return `
<div class="${classes.tooltipRoot}">
<div class="${classes.tooltipContainer}">
<div>
<p class="${classes.tooltipText}">${title}</p>
<p class="${classes.tooltipText}">${valueToShow}</p>
</div>
</div>
</div>`;
},
...tooltip,
});

const chartXAxis = useXAxis({ type: "categorical", ...xAxis });
const chartYAxis = useYAxis({
defaultType: "categorical",
axes: yAxis ? [yAxis] : [],
});

const chartVisualMap = useVisualMap({
min,
max,
orient: "horizontal",
left: "center",
calculable: true,
position: {
y: "bottom",
},
colorScale: colorScale || [colors?.cat1_180 || "", colors?.cat1_20 || ""],
});

const option = useOption({
option: {
xAxis: chartXAxis.xAxis,
yAxis: chartYAxis.yAxis,
visualMap: chartVisualMap.visualMap,
series: [
{
name,
type: "heatmap",
data,
label: {
show: true,
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
...chartTooltip,
},
onOptionChange,
});

return (
<HvBaseChart
ref={ref}
option={option}
width={width}
height={height}
{...others}
/>
);
},
);
1 change: 1 addition & 0 deletions packages/viz/src/Heatmap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Heatmap";
110 changes: 110 additions & 0 deletions packages/viz/src/Heatmap/stories/CustomTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { css, cx } from "@emotion/css";
import { theme } from "@hitachivantara/uikit-react-core";
import {
HvChartTooltipParams,
HvHeatmap,
} from "@hitachivantara/uikit-react-viz";

import { data as customData, days, hours } from "./data";

const styles = {
root: css({
display: "flex",
flexDirection: "column",
backgroundColor: theme.colors.atmo1,
width: "fit-content",
minWidth: 220,
boxShadow: theme.colors.shadow,
}),
container: css({
padding: theme.spacing("15px", "sm"),
display: "flex",
flexDirection: "column",
}),
containerBorder: css({
borderBottom: `3px solid ${theme.colors.atmo2}`,
}),
valuesContainer: css({
display: "flex",
gap: 8,
justifyContent: "space-between",
alignItems: "center",
width: "100%",
}),
title: css({
marginBottom: 10,
}),
separator: css({
marginRight: theme.space.md,
}),
thresholdContainer: css({
display: "flex",
alignItems: "center",
"& > div": {
width: 24,
height: 24,
"& > svg": {
marginLeft: 0,
},
},
}),
label: css({
fontFamily: theme.fontFamily.body,
fontWeight: theme.fontWeights.semibold,
fontSize: theme.fontSizes.sm,
color: theme.colors.secondary,
}),
color: css({
display: "flex",
width: 12,
height: 12,
padding: 4,
border: `1px solid ${theme.colors.secondary}`,
}),
};

const renderTooltip = (params?: HvChartTooltipParams) => {
const value = params?.value;

const valueToShow = value
? `${days[value?.[1]]} - ${hours[value?.[0]]}`
: "-";

return `
<div class="${styles.root}">
<div class="${cx(styles.container, styles.containerBorder)}">
<div>
<p class="${styles.label}">${params?.title}</p>
</div>
</div>
<div class="${cx(styles.container, styles.containerBorder)}">
<div class="${cx(styles.valuesContainer, styles.title)}">
<div class="${styles.color}" style="background-color: ${params?.series?.[0].color}"></div>
<div class="${cx(styles.label, styles.separator)}">
${valueToShow}
</div>
<div class="${cx(styles.label, styles.separator)}">
${params?.series?.[0]?.name}
</div>
</div>
</div>
</div>`;
};

export const CustomTooltip = () => {
return (
<HvHeatmap
name="My Heatmap"
data={customData}
xAxis={{ data: hours }}
yAxis={{ data: days }}
min={0}
max={12}
tooltip={{
show: true,
component: renderTooltip,
}}
colorScale={["#a65852", "#fbe45b"]}
/>
);
};

0 comments on commit 99c2a7a

Please sign in to comment.