diff --git a/src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx b/src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx new file mode 100644 index 000000000..549f5c6b9 --- /dev/null +++ b/src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx @@ -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((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 ( +
+ + {data?.length ? ( + + + {categories.map((category) => { + return ( + + {showGradient ? ( + + + + + ) : ( + + + + )} + + ); + })} + {categories.map((category) => ( + + ))} + + ) : ( + + )} + +
+ ); +}); + +AreaChart.displayName = "AreaChart"; + +export default AreaChart; diff --git a/src/components/spark-elements/SparkAreaChart/index.ts b/src/components/spark-elements/SparkAreaChart/index.ts new file mode 100644 index 000000000..adf7015d5 --- /dev/null +++ b/src/components/spark-elements/SparkAreaChart/index.ts @@ -0,0 +1,2 @@ +export { default as SparkAreaChart } from "./SparkAreaChart"; +export type { SparkAreaChartProps } from "./SparkAreaChart"; diff --git a/src/components/spark-elements/SparkBarChart/SparkBarChart.tsx b/src/components/spark-elements/SparkBarChart/SparkBarChart.tsx new file mode 100644 index 000000000..7a9c2e954 --- /dev/null +++ b/src/components/spark-elements/SparkBarChart/SparkBarChart.tsx @@ -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((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 ( +
+ + {data?.length ? ( + + + {categories.map((category) => ( + + ))} + + ) : ( + + )} + +
+ ); +}); + +SparkBarChart.displayName = "SparkBarChart"; + +export default SparkBarChart; diff --git a/src/components/spark-elements/SparkBarChart/index.ts b/src/components/spark-elements/SparkBarChart/index.ts new file mode 100644 index 000000000..ea3b365e8 --- /dev/null +++ b/src/components/spark-elements/SparkBarChart/index.ts @@ -0,0 +1,2 @@ +export { default as SparkBarChart } from "./SparkBarChart"; +export type { SparkBarChartProps } from "./SparkBarChart"; diff --git a/src/components/spark-elements/SparkLineChart/SparkLineChart.tsx b/src/components/spark-elements/SparkLineChart/SparkLineChart.tsx new file mode 100644 index 000000000..c1fa1ddec --- /dev/null +++ b/src/components/spark-elements/SparkLineChart/SparkLineChart.tsx @@ -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((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 ( +
+ + {data?.length ? ( + + + {categories.map((category) => ( + + ))} + + ) : ( + + )} + +
+ ); +}); + +SparkLineChart.displayName = "SparkLineChart"; + +export default SparkLineChart; diff --git a/src/components/spark-elements/SparkLineChart/index.ts b/src/components/spark-elements/SparkLineChart/index.ts new file mode 100644 index 000000000..5e93d8e5c --- /dev/null +++ b/src/components/spark-elements/SparkLineChart/index.ts @@ -0,0 +1,2 @@ +export { default as SparkLineChart } from "./SparkLineChart"; +export type { SparkLineChartProps } from "./SparkLineChart"; diff --git a/src/components/spark-elements/common/BaseSparkChartProps.tsx b/src/components/spark-elements/common/BaseSparkChartProps.tsx new file mode 100644 index 000000000..0e5f97125 --- /dev/null +++ b/src/components/spark-elements/common/BaseSparkChartProps.tsx @@ -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 { + data: any[]; + categories: string[]; + index: string; + colors?: Color[]; + noDataText?: string; +} + +export default BaseSparkChartProps; diff --git a/src/components/spark-elements/index.ts b/src/components/spark-elements/index.ts new file mode 100644 index 000000000..b81686344 --- /dev/null +++ b/src/components/spark-elements/index.ts @@ -0,0 +1,3 @@ +export * from "./SparkBarChart"; +export * from "./SparkLineChart"; +export * from "./SparkAreaChart"; diff --git a/src/stories/chart-elements/helpers/testData.tsx b/src/stories/chart-elements/helpers/testData.tsx index 8db475ab4..879569ad2 100644 --- a/src/stories/chart-elements/helpers/testData.tsx +++ b/src/stories/chart-elements/helpers/testData.tsx @@ -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", diff --git a/src/stories/spark-elements/SparkAreaChart.stories.tsx b/src/stories/spark-elements/SparkAreaChart.stories.tsx new file mode 100644 index 000000000..9b5f5dfc6 --- /dev/null +++ b/src/stories/spark-elements/SparkAreaChart.stories.tsx @@ -0,0 +1,133 @@ +import React from "react"; + +import type { Meta, StoryObj } from "@storybook/react"; + +import { + simpleBaseChartData as data, + longBaseChartData, + simpleBaseChartDataWithNulls, + singleAndMultipleData, +} from "../chart-elements/helpers/testData"; +import { SparkAreaChart } from "components/spark-elements"; +import ExampleCard from "./helpers/ExampleCard"; + +const meta: Meta = { + title: "Components/Chart/SparkAreaChart", + component: SparkAreaChart, + args: { + categories: ["Sales", "Successful Payments"], + index: "month", + data, + colors: ["emerald", "rose"], + }, + parameters: { layout: "centered" }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { args: { categories: ["Sales"] } }; + +export const Stacked: Story = { + args: { + stack: true, + }, +}; + +export const OtherColors: Story = { + args: { colors: ["rose", "purple"] }, +}; + +export const NoGradient: Story = { + args: { showGradient: false }, +}; + +export const ChangedCategoriesOrder: Story = { + args: { categories: ["Successful Payments", "Sales"] }, +}; + +export const LessColorsThanCategories: Story = { + args: { colors: ["green"] }, +}; + +export const NoData: Story = { + args: { data: [] }, +}; + +export const NoDataText: Story = { + args: { data: [], noDataText: "No data, try again later." }, +}; + +export const NoCategories: Story = { + args: { categories: undefined }, +}; + +export const NoIndex: Story = { + args: { index: undefined }, +}; + +export const CurveTypeNatural: Story = { + args: { curveType: "natural" }, +}; + +export const ConnectNullsTrue: Story = { + args: { data: simpleBaseChartDataWithNulls, connectNulls: true }, +}; + +export const ConnectNullsFalse: Story = { + args: { data: simpleBaseChartDataWithNulls, connectNulls: false }, +}; + +export const Animation: Story = { + args: { showAnimation: true }, +}; + +export const LongAnimationDuration: Story = { + args: { showAnimation: true, animationDuration: 5000 }, +}; + +export const OneDataValue: Story = { + args: { data: data.slice(0, 1) }, +}; + +export const SingleAndMultipleData: Story = { + args: { data: singleAndMultipleData }, +}; +export const LongDataInput: Story = { + args: { data: longBaseChartData }, +}; + +export const MultipleZeroValues: Story = { + args: { + data: [ + { + month: "May 21", + Sales: 2390, + "Successful Payments": 0, + }, + { + month: "Jun 21", + Sales: 2390, + "Successful Payments": 0, + }, + { + month: "Jul 21", + Sales: 3490, + "Successful Payments": 0, + }, + ], + }, +}; + +export const WithCard: Story = { + args: { + categories: ["Sales"], + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; diff --git a/src/stories/spark-elements/SparkBarChart.stories.tsx b/src/stories/spark-elements/SparkBarChart.stories.tsx new file mode 100644 index 000000000..a383d78c5 --- /dev/null +++ b/src/stories/spark-elements/SparkBarChart.stories.tsx @@ -0,0 +1,128 @@ +import React from "react"; + +import type { Meta, StoryObj } from "@storybook/react"; + +import { + simpleBaseChartData as data, + longBaseChartData, + simpleBaseChartWithNegativeValues, + singleAndMultipleData, +} from "../chart-elements/helpers/testData"; +import { SparkBarChart } from "components/spark-elements"; +import ExampleCard from "./helpers/ExampleCard"; + +const meta: Meta = { + title: "Components/Chart/SparkBarChart", + component: SparkBarChart, + args: { + categories: ["Sales", "Successful Payments"], + index: "month", + data, + colors: ["emerald", "rose"], + }, + parameters: { layout: "centered" }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { categories: ["Sales"] }, +}; + +export const DefaultNegativeValues: Story = { + args: { + data: simpleBaseChartWithNegativeValues, + }, +}; + +export const Stacked: Story = { + args: { stack: true }, +}; + +export const Relative: Story = { + args: { relative: true }, +}; + +export const OtherColors: Story = { + args: { colors: ["blue", "green"] }, +}; + +export const ChangedCategoriesOrder: Story = { + args: { categories: ["Successful Payments", "Sales"] }, +}; + +export const LessColorsThanCategories: Story = { + args: { colors: ["green"] }, +}; + +export const NoData: Story = { + args: { data: [] }, +}; + +export const NoDataText: Story = { + args: { data: [], noDataText: "No data, try again later." }, +}; + +export const NoCategories: Story = { + args: { categories: undefined }, +}; + +export const NoIndex: Story = { + args: { index: undefined }, +}; + +export const Animation: Story = { + args: { showAnimation: true }, +}; + +export const LongAnimationDuration: Story = { + args: { showAnimation: true, animationDuration: 5000 }, +}; + +export const OneDataValue: Story = { + args: { data: data.slice(0, 1) }, +}; + +export const SingleAndMultipleData: Story = { + args: { data: singleAndMultipleData }, +}; + +export const LongDataInput: Story = { + args: { data: longBaseChartData }, +}; + +export const MultipleZeroValues: Story = { + args: { + data: [ + { + month: "May 21", + Sales: 2390, + "Successful Payments": 0, + }, + { + month: "Jun 21", + Sales: 2390, + "Successful Payments": 0, + }, + { + month: "Jul 21", + Sales: 3490, + "Successful Payments": 0, + }, + ], + }, +}; + +export const WithCard: Story = { + args: { + categories: ["Sales"], + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; diff --git a/src/stories/spark-elements/SparkLineChart.stories.tsx b/src/stories/spark-elements/SparkLineChart.stories.tsx new file mode 100644 index 000000000..2f8eeac97 --- /dev/null +++ b/src/stories/spark-elements/SparkLineChart.stories.tsx @@ -0,0 +1,123 @@ +import React from "react"; + +import type { Meta, StoryObj } from "@storybook/react"; + +import { + simpleBaseChartData as data, + longBaseChartData, + simpleBaseChartDataWithNulls, + singleAndMultipleData, +} from "../chart-elements/helpers/testData"; +import { SparkLineChart } from "components/spark-elements"; +import ExampleCard from "./helpers/ExampleCard"; + +const meta: Meta = { + title: "Components/Chart/SparkLineChart", + component: SparkLineChart, + args: { + categories: ["Sales", "Successful Payments"], + index: "month", + data, + colors: ["emerald", "rose"], + }, + parameters: { layout: "centered" }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { args: { categories: ["Sales"] } }; + +export const OtherColors: Story = { + args: { colors: ["rose", "purple"] }, +}; + +export const ChangedCategoriesOrder: Story = { + args: { categories: ["Successful Payments", "Sales"] }, +}; + +export const LessColorsThanCategories: Story = { + args: { colors: ["green"] }, +}; + +export const NoData: Story = { + args: { data: [] }, +}; + +export const NoDataText: Story = { + args: { data: [], noDataText: "No data, try again later." }, +}; + +export const NoCategories: Story = { + args: { categories: undefined }, +}; + +export const NoIndex: Story = { + args: { index: undefined }, +}; + +export const CurveTypeNatural: Story = { + args: { curveType: "natural" }, +}; + +export const ConnectNullsTrue: Story = { + args: { data: simpleBaseChartDataWithNulls, connectNulls: true }, +}; + +export const ConnectNullsFalse: Story = { + args: { data: simpleBaseChartDataWithNulls, connectNulls: false }, +}; + +export const Animation: Story = { + args: { showAnimation: true }, +}; + +export const LongAnimationDuration: Story = { + args: { showAnimation: true, animationDuration: 5000 }, +}; + +export const OneDataValue: Story = { + args: { data: data.slice(0, 1) }, +}; + +export const SingleAndMultipleData: Story = { + args: { data: singleAndMultipleData }, +}; +export const LongDataInput: Story = { + args: { data: longBaseChartData }, +}; + +export const MultipleZeroValues: Story = { + args: { + data: [ + { + month: "May 21", + Sales: 2390, + "Successful Payments": 0, + }, + { + month: "Jun 21", + Sales: 2390, + "Successful Payments": 0, + }, + { + month: "Jul 21", + Sales: 3490, + "Successful Payments": 0, + }, + ], + }, +}; + +export const WithCard: Story = { + args: { + categories: ["Sales"], + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; diff --git a/src/stories/spark-elements/helpers/ExampleCard.tsx b/src/stories/spark-elements/helpers/ExampleCard.tsx new file mode 100644 index 000000000..791be6492 --- /dev/null +++ b/src/stories/spark-elements/helpers/ExampleCard.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { Card, Title } from "components"; + +const ExampleCard = ({ children }: { children: React.ReactNode }) => ( + +
+ AAPL + Apple Inc. +
+
+ {children} +
+ 196.26 + +2.69% +
+
+
+); + +export default ExampleCard;