Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Click events on charts #711

Merged
merged 2 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Test On click events charts (#707)
Co-authored-by: jzfrank <77217626+jzfrank@users.noreply.github.com>
Co-authored-by: mbauchet <maximebauchet26@gmail.com>
Co-authored-by: Thomas McInnis <hey@thomasmcinnis.com>
Co-authored-by: Manav H Joshi <73978411+ManavJoshi111@users.noreply.github.com>
  • Loading branch information
5 people committed Sep 24, 2023
commit 761ef0e004960a9a2954381c447c788dd966660a
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@

<div align="center">
<a href="https://www.npmjs.com/package/@tremor/react">
<img alt="npm" src="https://img.shields.io/npm/dm/@tremor/react?color=5C9BA1&label=npm&logo=npm">
<img alt="npm" src="https://img.shields.io/npm/dm/@tremor/react?color=3b82f6&label=npm&logo=npm&labelColor=334155">
</a>
<a href="https://tremor.so/docs/getting-started/introduction">
<img alt="Read the documentation" src="https://img.shields.io/badge/Docs-blue?style=flat&logo=readthedocs&labelColor=5c5c5c&color=5C9BA1" height="20" width="auto">
<img alt="Read the documentation" src="https://img.shields.io/badge/Docs-blue?style=flat&logo=readthedocs&color=3b82f6&labelColor=334155&logoColor=f5f5f5" height="20" width="auto">
</a>
<a href="https://github.com/tremorlabs/tremor/blob/main/License">
<img alt="License Apache 2.0" src="https://img.shields.io/badge/license-Apache 2.0-blue.svg?style=flat&color=5C9BA1" height="20" width="auto">
<img alt="License Apache 2.0" src="https://img.shields.io/badge/license-Apache 2.0-blue.svg?style=flat&color=3b82f6&labelColor=334155 " height="20" width="auto">
</a>
<a href="https://join.slack.com/t/tremor-community/shared_invite/zt-21ug6czv6-RckDPEAR6GdYOqfMGKOWpQ">
<img src="https://img.shields.io/badge/Join-important.svg?color=4A154B&label=Slack&logo=slack" alt="Join Slack" />
<img src="https://img.shields.io/badge/Join-important.svg?color=4A154B&label=Slack&logo=slack&labelColor=334155&logoColor=f5f5f5" alt="Join Slack" />
</a>
<a href="https://twitter.com/intent/follow?screen_name=tremorlabs">
<!-- <a href="https://twitter.com/intent/follow?screen_name=tremorlabs">
<img src="https://img.shields.io/twitter/follow/tremorlabs?style=social" alt="Follow on Twitter" />
</a> -->
<a href="https://twitter.com/intent/follow?screen_name=tremorlabs">
<img src="https://img.shields.io/badge/Follow-important.svg?color=000000&label=@tremorlabs&logo=X&labelColor=334155&logoColor=f5f5f5" alt="Follow at Tremorlabs" />
</a>

</div>
<h3 align="center">
<a href="https://www.tremor.so/docs/getting-started/installation">Documentation</a> &bull;
Expand Down
226 changes: 192 additions & 34 deletions src/components/chart-elements/AreaChart/AreaChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import React, { useState } from "react";
import {
Area,
CartesianGrid,
Dot,
Legend,
Line,
AreaChart as ReChartsAreaChart,
ResponsiveContainer,
Tooltip,
Expand Down Expand Up @@ -34,6 +36,11 @@ export interface AreaChartProps extends BaseChartProps {
connectNulls?: boolean;
}

interface ActiveDot {
index?: number;
dataKey?: string;
}

const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref) => {
const {
data = [],
Expand All @@ -46,7 +53,7 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
showXAxis = true,
showYAxis = true,
yAxisWidth = 56,
showAnimation = true,
showAnimation = false,
animationDuration = 900,
showTooltip = true,
showLegend = true,
Expand All @@ -60,29 +67,80 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
allowDecimals = true,
noDataText,
className,
onValueChange,
...other
} = props;
const [legendHeight, setLegendHeight] = useState(60);
const [activeDot, setActiveDot] = useState<ActiveDot | undefined>(undefined);
const [activeLegend, setActiveLegend] = useState<string | undefined>(undefined);
const categoryColors = constructCategoryColors(categories, colors);

const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue);
const hasOnValueChange = !!onValueChange;

function onDotClick(data: any, event: React.MouseEvent) {
event.stopPropagation();

if (!hasOnValueChange) return;
if (data.index === activeDot?.index && data.dataKey === activeDot?.dataKey) {
setActiveLegend(undefined);
setActiveDot(undefined);
onValueChange?.(null);
} else {
setActiveLegend(data.dataKey);
setActiveDot({
index: data.index,
dataKey: data.dataKey,
});
onValueChange?.({
eventType: "dot",
categoryClicked: data.dataKey,
...data.payload,
});
}
}

function onCategoryClick(dataKey: string) {
if (!hasOnValueChange) return;
if (dataKey === activeLegend && !activeDot) {
setActiveLegend(undefined);
onValueChange?.(null);
} else {
setActiveLegend(dataKey);
onValueChange?.({
eventType: "category",
categoryClicked: dataKey,
});
}
setActiveDot(undefined);
}
return (
<div ref={ref} className={tremorTwMerge("w-full h-80", className)} {...other}>
<ResponsiveContainer className="h-full w-full">
{data?.length ? (
<ReChartsAreaChart data={data}>
<ReChartsAreaChart
data={data}
onClick={
hasOnValueChange && (activeLegend || activeDot)
? () => {
setActiveDot(undefined);
setActiveLegend(undefined);
onValueChange?.(null);
}
: undefined
}
>
{" "}
{showGridLines ? (
<CartesianGrid
className={tremorTwMerge(
// common
"stroke-1",
// light
"stroke-tremor-content-subtle",
"stroke-tremor-border",
// dark
"dark:stroke-dark-tremor-content-subtle",
"dark:stroke-dark-tremor-border",
)}
strokeDasharray="3 3"
horizontal={true}
vertical={false}
/>
Expand Down Expand Up @@ -129,28 +187,42 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
tickFormatter={valueFormatter}
allowDecimals={allowDecimals}
/>
{showTooltip ? (
<Tooltip
wrapperStyle={{ outline: "none" }}
isAnimationActive={false}
cursor={{ stroke: "#d1d5db", strokeWidth: 1 }} // @achi @severin
content={({ active, payload, label }) => (
<ChartTooltip
active={active}
payload={payload}
label={label}
valueFormatter={valueFormatter}
categoryColors={categoryColors}
/>
)}
position={{ y: 0 }}
/>
) : null}
<Tooltip
wrapperStyle={{ outline: "none" }}
isAnimationActive={false}
cursor={{ stroke: "#d1d5db", strokeWidth: 1 }}
content={
showTooltip ? (
({ active, payload, label }) => (
<ChartTooltip
active={active}
payload={payload}
label={label}
valueFormatter={valueFormatter}
categoryColors={categoryColors}
/>
)
) : (
<></>
)
}
position={{ y: 0 }}
/>
{showLegend ? (
<Legend
verticalAlign="top"
height={legendHeight}
content={({ payload }) => ChartLegend({ payload }, categoryColors, setLegendHeight)}
content={({ payload }) =>
ChartLegend(
{ payload },
categoryColors,
setLegendHeight,
activeLegend,
hasOnValueChange
? (clickedLegendItem: string) => onCategoryClick(clickedLegendItem)
: undefined,
)
}
/>
) : null}
{categories.map((category) => {
Expand All @@ -170,7 +242,13 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
x2="0"
y2="1"
>
<stop offset="5%" stopColor="currentColor" stopOpacity={0.4} />
<stop
offset="5%"
stopColor="currentColor"
stopOpacity={
activeDot || (activeLegend && activeLegend !== category) ? 0.15 : 0.4
}
/>
<stop offset="95%" stopColor="currentColor" stopOpacity={0} />
</linearGradient>
) : (
Expand All @@ -187,7 +265,12 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
x2="0"
y2="1"
>
<stop stopColor="currentColor" stopOpacity={0.3} />
<stop
stopColor="currentColor"
stopOpacity={
activeDot || (activeLegend && activeLegend !== category) ? 0.1 : 0.3
}
/>
</linearGradient>
)}
</defs>
Expand All @@ -201,16 +284,68 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
colorPalette.text,
).strokeColor
}
activeDot={{
className: tremorTwMerge(
"stroke-tremor-background dark:stroke-dark-tremor-background",
getColorClassNames(
categoryColors.get(category) ?? BaseColors.Gray,
colorPalette.text,
).fillColor,
),
strokeOpacity={activeDot || (activeLegend && activeLegend !== category) ? 0.3 : 1}
activeDot={(props: any) => {
const { cx, cy, stroke, strokeLinecap, strokeLinejoin, strokeWidth, dataKey } =
props;
return (
<Dot
className={tremorTwMerge(
"stroke-tremor-background dark:stroke-dark-tremor-background",
onValueChange ? "cursor-pointer" : "",
getColorClassNames(
categoryColors.get(dataKey) ?? BaseColors.Gray,
colorPalette.text,
).fillColor,
)}
cx={cx}
cy={cy}
r={5}
fill=""
stroke={stroke}
strokeLinecap={strokeLinecap}
strokeLinejoin={strokeLinejoin}
strokeWidth={strokeWidth}
onClick={(dotProps: any, event) => onDotClick(props, event)}
/>
);
}}
dot={(props: any) => {
const {
stroke,
strokeLinecap,
strokeLinejoin,
strokeWidth,
cx,
cy,
dataKey,
index,
} = props;

if (activeDot?.index === index && activeDot?.dataKey === category) {
return (
<Dot
cx={cx}
cy={cy}
r={5}
stroke={stroke}
fill=""
strokeLinecap={strokeLinecap}
strokeLinejoin={strokeLinejoin}
strokeWidth={strokeWidth}
className={tremorTwMerge(
"stroke-tremor-background dark:stroke-dark-tremor-background",
onValueChange ? "cursor-pointer" : "",
getColorClassNames(
categoryColors.get(dataKey) ?? BaseColors.Gray,
colorPalette.text,
).fillColor,
)}
/>
);
}
return <></>;
}}
dot={false}
key={category}
name={category}
type={curveType}
Expand All @@ -226,6 +361,29 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
connectNulls={connectNulls}
/>
))}
{onValueChange
? categories.map((category) => (
<Line
className={tremorTwMerge("cursor-pointer")}
strokeOpacity={0}
key={category}
name={category}
type={curveType}
dataKey={category}
stroke="transparent"
fill="transparent"
legendType="none"
tooltipType="none"
strokeWidth={12}
connectNulls={connectNulls}
onClick={(props: any, event) => {
event.stopPropagation();
const { name } = props;
onCategoryClick(name);
}}
/>
))
: null}
</ReChartsAreaChart>
) : (
<NoData noDataText={noDataText} />
Expand Down
Loading
Loading