Skip to content

Commit

Permalink
feat: add spark charts (bar, line, area) (#759)
Browse files Browse the repository at this point in the history
* add spark charts (bar, line, area)

---------

Co-authored-by: Maxime BAUCHET <[email protected]>
Co-authored-by: Severin Landolt <[email protected]>
  • Loading branch information
3 people authored Oct 31, 2023
1 parent 2166632 commit 764e816
Show file tree
Hide file tree
Showing 13 changed files with 738 additions and 0 deletions.
118 changes: 118 additions & 0 deletions src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"use client";
import React from "react";
import { Area, AreaChart as ReChartsAreaChart, ResponsiveContainer, XAxis } from "recharts";

import { BaseColors, colorPalette, getColorClassNames, themeColorRange, tremorTwMerge } from "lib";
import { CurveType } from "../../../lib/inputTypes";
import BaseSparkChartProps from "../common/BaseSparkChartProps";
import { constructCategoryColors } from "components/chart-elements/common/utils";
import NoData from "components/chart-elements/common/NoData";

export interface SparkAreaChartProps extends BaseSparkChartProps {
stack?: boolean;
curveType?: CurveType;
connectNulls?: boolean;
showGradient?: boolean;
}

const AreaChart = React.forwardRef<HTMLDivElement, SparkAreaChartProps>((props, ref) => {
const {
data = [],
categories = [],
index,
stack = false,
colors = themeColorRange,
showAnimation = false,
animationDuration = 900,
showGradient = true,
curveType = "linear",
connectNulls = false,
noDataText,
className,
...other
} = props;
const categoryColors = constructCategoryColors(categories, colors);

return (
<div ref={ref} className={tremorTwMerge("w-28 h-12", className)} {...other}>
<ResponsiveContainer className="h-full w-full">
{data?.length ? (
<ReChartsAreaChart data={data} margin={{ top: 1, left: 1, right: 1, bottom: 1 }}>
<XAxis hide dataKey={index} />
{categories.map((category) => {
return (
<defs key={category}>
{showGradient ? (
<linearGradient
className={
getColorClassNames(
categoryColors.get(category) ?? BaseColors.Gray,
colorPalette.text,
).textColor
}
id={categoryColors.get(category)}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop offset="5%" stopColor="currentColor" stopOpacity={0.4} />
<stop offset="95%" stopColor="currentColor" stopOpacity={0} />
</linearGradient>
) : (
<linearGradient
className={
getColorClassNames(
categoryColors.get(category) ?? BaseColors.Gray,
colorPalette.text,
).textColor
}
id={categoryColors.get(category)}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop stopColor="currentColor" stopOpacity={0.3} />
</linearGradient>
)}
</defs>
);
})}
{categories.map((category) => (
<Area
className={
getColorClassNames(
categoryColors.get(category) ?? BaseColors.Gray,
colorPalette.text,
).strokeColor
}
strokeOpacity={1}
dot={false}
key={category}
name={category}
type={curveType}
dataKey={category}
stroke=""
fill={`url(#${categoryColors.get(category)})`}
strokeWidth={2}
strokeLinejoin="round"
strokeLinecap="round"
isAnimationActive={showAnimation}
animationDuration={animationDuration}
stackId={stack ? "a" : undefined}
connectNulls={connectNulls}
/>
))}
</ReChartsAreaChart>
) : (
<NoData noDataText={noDataText} />
)}
</ResponsiveContainer>
</div>
);
});

AreaChart.displayName = "AreaChart";

export default AreaChart;
2 changes: 2 additions & 0 deletions src/components/spark-elements/SparkAreaChart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as SparkAreaChart } from "./SparkAreaChart";
export type { SparkAreaChartProps } from "./SparkAreaChart";
72 changes: 72 additions & 0 deletions src/components/spark-elements/SparkBarChart/SparkBarChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client";
import { colorPalette, getColorClassNames, tremorTwMerge } from "lib";
import React from "react";

import { Bar, BarChart as ReChartsBarChart, ResponsiveContainer, XAxis } from "recharts";

import { BaseColors, themeColorRange } from "lib";
import BaseSparkChartProps from "../common/BaseSparkChartProps";
import { constructCategoryColors } from "components/chart-elements/common/utils";
import NoData from "components/chart-elements/common/NoData";

export interface SparkBarChartProps extends BaseSparkChartProps {
stack?: boolean;
relative?: boolean;
}

const SparkBarChart = React.forwardRef<HTMLDivElement, SparkBarChartProps>((props, ref) => {
const {
data = [],
categories = [],
index,
colors = themeColorRange,
stack = false,
relative = false,
animationDuration = 900,
showAnimation = false,
noDataText,
className,
...other
} = props;
const categoryColors = constructCategoryColors(categories, colors);

return (
<div ref={ref} className={tremorTwMerge("w-28 h-12", className)} {...other}>
<ResponsiveContainer className="h-full w-full">
{data?.length ? (
<ReChartsBarChart
data={data}
stackOffset={relative ? "expand" : "none"}
margin={{ top: 0, left: -1.5, right: -1.5, bottom: 0 }}
>
<XAxis hide dataKey={index} />
{categories.map((category) => (
<Bar
className={tremorTwMerge(
getColorClassNames(
categoryColors.get(category) ?? BaseColors.Gray,
colorPalette.background,
).fillColor,
)}
key={category}
name={category}
type="linear"
stackId={stack || relative ? "a" : undefined}
dataKey={category}
fill=""
isAnimationActive={showAnimation}
animationDuration={animationDuration}
/>
))}
</ReChartsBarChart>
) : (
<NoData noDataText={noDataText} />
)}
</ResponsiveContainer>
</div>
);
});

SparkBarChart.displayName = "SparkBarChart";

export default SparkBarChart;
2 changes: 2 additions & 0 deletions src/components/spark-elements/SparkBarChart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as SparkBarChart } from "./SparkBarChart";
export type { SparkBarChartProps } from "./SparkBarChart";
72 changes: 72 additions & 0 deletions src/components/spark-elements/SparkLineChart/SparkLineChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client";
import React from "react";
import { Line, LineChart as ReChartsLineChart, ResponsiveContainer, XAxis } from "recharts";

import { BaseColors, colorPalette, getColorClassNames, themeColorRange, tremorTwMerge } from "lib";
import { CurveType } from "../../../lib/inputTypes";
import BaseSparkChartProps from "../common/BaseSparkChartProps";
import { constructCategoryColors } from "components/chart-elements/common/utils";
import NoData from "components/chart-elements/common/NoData";

export interface SparkLineChartProps extends BaseSparkChartProps {
curveType?: CurveType;
connectNulls?: boolean;
}

const SparkLineChart = React.forwardRef<HTMLDivElement, SparkLineChartProps>((props, ref) => {
const {
data = [],
categories = [],
index,
colors = themeColorRange,
animationDuration = 900,
showAnimation = false,
curveType = "linear",
connectNulls = false,
noDataText,
className,
...other
} = props;
const categoryColors = constructCategoryColors(categories, colors);

return (
<div ref={ref} className={tremorTwMerge("w-28 h-12", className)} {...other}>
<ResponsiveContainer className="h-full w-full">
{data?.length ? (
<ReChartsLineChart data={data} margin={{ top: 1, left: 1, right: 1, bottom: 1 }}>
<XAxis hide dataKey={index} />
{categories.map((category) => (
<Line
className={tremorTwMerge(
getColorClassNames(
categoryColors.get(category) ?? BaseColors.Gray,
colorPalette.text,
).strokeColor,
)}
strokeOpacity={1}
dot={false}
key={category}
name={category}
type={curveType}
dataKey={category}
stroke=""
strokeWidth={2}
strokeLinejoin="round"
strokeLinecap="round"
isAnimationActive={showAnimation}
animationDuration={animationDuration}
connectNulls={connectNulls}
/>
))}
</ReChartsLineChart>
) : (
<NoData noDataText={noDataText} />
)}
</ResponsiveContainer>
</div>
);
});

SparkLineChart.displayName = "SparkLineChart";

export default SparkLineChart;
2 changes: 2 additions & 0 deletions src/components/spark-elements/SparkLineChart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as SparkLineChart } from "./SparkLineChart";
export type { SparkLineChartProps } from "./SparkLineChart";
25 changes: 25 additions & 0 deletions src/components/spark-elements/common/BaseSparkChartProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import BaseAnimationTimingProps from "components/chart-elements/common/BaseAnimationTimingProps";
import { Color } from "../../../lib";

type FixedProps = {
eventType: "dot" | "category" | "bar" | "slice" | "bubble";
categoryClicked: string;
};

type BaseEventProps = FixedProps & {
[key: string]: number | string;
};

export type EventProps = BaseEventProps | null | undefined;

interface BaseSparkChartProps
extends BaseAnimationTimingProps,
React.HTMLAttributes<HTMLDivElement> {
data: any[];
categories: string[];
index: string;
colors?: Color[];
noDataText?: string;
}

export default BaseSparkChartProps;
3 changes: 3 additions & 0 deletions src/components/spark-elements/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./SparkBarChart";
export * from "./SparkLineChart";
export * from "./SparkAreaChart";
38 changes: 38 additions & 0 deletions src/stories/chart-elements/helpers/testData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,44 @@ export const simpleBaseChartData = [
},
];

export const simpleBaseChartWithNegativeValues = [
{
month: "Jan 21",
Sales: 4000,
"Successful Payments": 3000,
},
{
month: "Feb 21",
Sales: 3000,
"Successful Payments": 2000,
},
{
month: "Mar 21",
Sales: 2000,
"Successful Payments": 1700,
},
{
month: "Apr 21",
Sales: 2780,
"Successful Payments": -2500,
},
{
month: "May 21",
Sales: 1890,
"Successful Payments": -1890,
},
{
month: "Jun 21",
Sales: 2390,
"Successful Payments": -2000,
},
{
month: "Jul 21",
Sales: 100,
"Successful Payments": -3000,
},
];

export const longIndexBaseChartData = [
{
month: "Januar 2023",
Expand Down
Loading

0 comments on commit 764e816

Please sign in to comment.