diff --git a/package.json b/package.json index da02aef..698a057 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ }, "devDependencies": { "@commitlint/cli": "^19.3.0", + "@icons-pack/react-simple-icons": "^9.6.0", "@lobehub/lint": "^1.24.3", "@lobehub/ui": "^1.146.8", "@testing-library/react": "^14.3.1", diff --git a/src/AreaChart/demos/axis.tsx b/src/AreaChart/demos/axis.tsx index a763d3f..436b817 100644 --- a/src/AreaChart/demos/axis.tsx +++ b/src/AreaChart/demos/axis.tsx @@ -1,6 +1,6 @@ -import { AreaChart } from '@lobehub/charts'; +import { AreaChart, AreaChartProps } from '@lobehub/charts'; -const chartdata = [ +const data: AreaChartProps['data'] = [ { Inverters: 2338, SolarPanels: 2890, @@ -63,7 +63,7 @@ const chartdata = [ }, ]; -const valueFormatter = (number: number) => { +const valueFormatter: AreaChartProps['valueFormatter'] = (number) => { return '$ ' + new Intl.NumberFormat('us').format(number).toString(); }; @@ -71,7 +71,7 @@ export default () => { return ( { setValue(v)} yAxisWidth={30} diff --git a/src/AreaChart/demos/customColors.tsx b/src/AreaChart/demos/customColors.tsx index 91d6ce6..c03b4bd 100644 --- a/src/AreaChart/demos/customColors.tsx +++ b/src/AreaChart/demos/customColors.tsx @@ -1,7 +1,7 @@ -import { AreaChart } from '@lobehub/charts'; +import { AreaChart, AreaChartProps } from '@lobehub/charts'; import { useTheme } from 'antd-style'; -const chartdata = [ +const data: AreaChartProps['data'] = [ { 'Distance Running': 167, 'Hatha Yoga': 115, @@ -58,7 +58,7 @@ export default () => { diff --git a/src/AreaChart/demos/customTooltip.tsx b/src/AreaChart/demos/customTooltip.tsx index 369f899..900d11b 100644 --- a/src/AreaChart/demos/customTooltip.tsx +++ b/src/AreaChart/demos/customTooltip.tsx @@ -1,9 +1,9 @@ -import { AreaChart, ChartTooltipFrame } from '@lobehub/charts'; +import { AreaChart, AreaChartProps, ChartTooltipFrame } from '@lobehub/charts'; import { Typography } from 'antd'; import { useTheme } from 'antd-style'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: AreaChartProps['data'] = [ { Running: 167, date: 'Jan 23', @@ -30,17 +30,10 @@ const chartdata = [ }, ]; -type CustomTooltipTypeBar = { - active: boolean | undefined; - label: any; - payload: any; -}; - export default () => { const theme = useTheme(); - const customTooltip = (props: CustomTooltipTypeBar) => { - const { payload, active } = props; + const customTooltip: AreaChartProps['customTooltip'] = ({ payload, active }) => { if (!active || !payload) return null; return ( @@ -70,7 +63,7 @@ export default () => { diff --git a/src/AreaChart/demos/example.tsx b/src/AreaChart/demos/example.tsx index 0f5ef95..e72b210 100644 --- a/src/AreaChart/demos/example.tsx +++ b/src/AreaChart/demos/example.tsx @@ -1,7 +1,7 @@ -import { AreaChart } from '@lobehub/charts'; +import { AreaChart, AreaChartProps } from '@lobehub/charts'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: AreaChartProps['data'] = [ { Inverters: 2338, SolarPanels: 2890, @@ -64,7 +64,7 @@ const chartdata = [ }, ]; -const valueFormatter = (number: number) => { +const valueFormatter: AreaChartProps['valueFormatter'] = (number) => { return '$ ' + new Intl.NumberFormat('us').format(number).toString(); }; @@ -75,7 +75,7 @@ export default () => {

$34,567

`$${Intl.NumberFormat('us').format(number).toString()}`; +const valueFormatter: AreaChartProps['valueFormatter'] = (number: number) => + `$${Intl.NumberFormat('us').format(number).toString()}`; export default () => { const store = useCreateStore(); - const props: any = useControls( + const props: AreaChartProps | any = useControls( { allowDecimals: true, animationDuration: { @@ -116,10 +117,10 @@ export default () => { console.log(v)} - valueFormatter={dataFormatter} + valueFormatter={valueFormatter} {...props} /> diff --git a/src/AreaChart/index.tsx b/src/AreaChart/index.tsx index a95ac64..f8ad0f1 100644 --- a/src/AreaChart/index.tsx +++ b/src/AreaChart/index.tsx @@ -79,6 +79,7 @@ const AreaChart = forwardRef((props, ref) => { yAxisLabel, width = '100%', height = '20rem', + style, ...rest } = props; const CustomTooltip = customTooltip; @@ -91,7 +92,7 @@ const AreaChart = forwardRef((props, ref) => { const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); const hasOnValueChange = !!onValueChange; - function onDotClick(itemData: any, event: MouseEvent) { + const onDotClick = (itemData: any, event: MouseEvent) => { event.stopPropagation(); if (!hasOnValueChange) return; @@ -116,9 +117,9 @@ const AreaChart = forwardRef((props, ref) => { ...itemData.payload, }); } - } + }; - function onCategoryClick(dataKey: string) { + const onCategoryClick = (dataKey: string) => { if (!hasOnValueChange) return; if ( (dataKey === activeLegend && !activeDot) || @@ -134,9 +135,16 @@ const AreaChart = forwardRef((props, ref) => { }); } setActiveDot(undefined); - } + }; return ( - + {data?.length ? ( Intl.NumberFormat('us').format(number).toString(); +const valueFormatter: BarChartProps['valueFormatter'] = (number) => + Intl.NumberFormat('us').format(number).toString(); export default () => { return ( @@ -40,10 +41,9 @@ export default () => {

Number of species threatened with extinction (2021)

{

Closed Pull Requests

setValue(v)} yAxisWidth={36} diff --git a/src/BarChart/demos/customColors.tsx b/src/BarChart/demos/customColors.tsx index a251cfe..dca6f68 100644 --- a/src/BarChart/demos/customColors.tsx +++ b/src/BarChart/demos/customColors.tsx @@ -1,7 +1,7 @@ -import { BarChart } from '@lobehub/charts'; +import { BarChart, BarChartProps } from '@lobehub/charts'; import { useTheme } from 'antd-style'; -const chartdata = [ +const data: BarChartProps['data'] = [ { 'Distance Running': 167, 'Hatha Yoga': 115, @@ -58,7 +58,7 @@ export default () => { diff --git a/src/BarChart/demos/customTooltip.tsx b/src/BarChart/demos/customTooltip.tsx index 41be79b..cd7970a 100644 --- a/src/BarChart/demos/customTooltip.tsx +++ b/src/BarChart/demos/customTooltip.tsx @@ -1,9 +1,9 @@ -import { BarChart, ChartTooltipFrame } from '@lobehub/charts'; +import { BarChart, BarChartProps, ChartTooltipFrame } from '@lobehub/charts'; import { Typography } from 'antd'; import { useTheme } from 'antd-style'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: BarChartProps['data'] = [ { Running: 167, date: 'Jan 23', @@ -42,17 +42,10 @@ const chartdata = [ }, ]; -type CustomTooltipTypeBar = { - active: boolean | undefined; - label: any; - payload: any; -}; - export default () => { const theme = useTheme(); - const customTooltip = (props: CustomTooltipTypeBar) => { - const { payload, active } = props; + const customTooltip: BarChartProps['customTooltip'] = ({ payload, active }) => { if (!active || !payload) return null; return ( @@ -83,7 +76,7 @@ export default () => { diff --git a/src/BarChart/demos/example.tsx b/src/BarChart/demos/example.tsx index f73b255..9d46fcd 100644 --- a/src/BarChart/demos/example.tsx +++ b/src/BarChart/demos/example.tsx @@ -1,7 +1,7 @@ -import { BarChart } from '@lobehub/charts'; +import { BarChart, BarChartProps } from '@lobehub/charts'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: BarChartProps['data'] = [ { 'Number of threatened species': 2488, 'name': 'Amphibians', @@ -32,7 +32,8 @@ const chartdata = [ }, ]; -const dataFormatter = (number: number) => Intl.NumberFormat('us').format(number).toString(); +const valueFormatter: BarChartProps['valueFormatter'] = (number) => + Intl.NumberFormat('us').format(number).toString(); export default () => { return ( @@ -41,9 +42,9 @@ export default () => {
diff --git a/src/BarChart/demos/groups.tsx b/src/BarChart/demos/groups.tsx index f863b42..421c36f 100644 --- a/src/BarChart/demos/groups.tsx +++ b/src/BarChart/demos/groups.tsx @@ -1,7 +1,7 @@ -import { BarChart } from '@lobehub/charts'; +import { BarChart, BarChartProps } from '@lobehub/charts'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: BarChartProps['data'] = [ { groupA: 890, groupB: 338, @@ -40,7 +40,8 @@ const chartdata = [ }, ]; -const dataFormatter = (number: number) => Intl.NumberFormat('us').format(number).toString(); +const valueFormatter: BarChartProps['valueFormatter'] = (number: number) => + Intl.NumberFormat('us').format(number).toString(); export default () => { return ( @@ -48,9 +49,9 @@ export default () => {

Writing Contest: Entries

diff --git a/src/BarChart/demos/index.tsx b/src/BarChart/demos/index.tsx index 1247f18..418c465 100644 --- a/src/BarChart/demos/index.tsx +++ b/src/BarChart/demos/index.tsx @@ -1,7 +1,7 @@ -import { BarChart } from '@lobehub/charts'; +import { BarChart, BarChartProps } from '@lobehub/charts'; import { StoryBook, useControls, useCreateStore } from '@lobehub/ui'; -const chartdata = [ +const data: BarChartProps['data'] = [ { groupA: 890, groupB: 338, @@ -28,12 +28,13 @@ const chartdata = [ }, ]; -const dataFormatter = (number: number) => Intl.NumberFormat('us').format(number).toString(); +const valueFormatter: BarChartProps['valueFormatter'] = (number) => + Intl.NumberFormat('us').format(number).toString(); export default () => { const store = useCreateStore(); - const props: any = useControls( + const props: BarChartProps | any = useControls( { allowDecimals: true, animationDuration: { @@ -81,10 +82,10 @@ export default () => { console.log(v)} - valueFormatter={dataFormatter} + valueFormatter={valueFormatter} {...props} /> diff --git a/src/BarChart/index.tsx b/src/BarChart/index.tsx index 2b38e13..a9e6b64 100644 --- a/src/BarChart/index.tsx +++ b/src/BarChart/index.tsx @@ -72,6 +72,7 @@ const BarChart = forwardRef((props, ref) => { className, width = '100%', height = '20rem', + style, ...rest } = props; const CustomTooltip = customTooltip; @@ -121,7 +122,14 @@ const BarChart = forwardRef((props, ref) => { const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); return ( - + {data?.length ? ( { + const [value, setValue] = useState({}); + return ( + <> + setValue(v)} /> + + {JSON.stringify(value, null, 2)} + + + ); +}; diff --git a/src/BarList/demos/customColors.tsx b/src/BarList/demos/customColors.tsx new file mode 100644 index 0000000..de399ba --- /dev/null +++ b/src/BarList/demos/customColors.tsx @@ -0,0 +1,14 @@ +import { BarList, BarListProps } from '@lobehub/charts'; +import { useTheme } from 'antd-style'; + +export default () => { + const theme = useTheme(); + + const data: BarListProps['data'] = [ + { name: '/home', value: 456 }, + { color: theme.magenta, name: '/imprint', value: 351 }, + { name: '/cancellation', value: 51 }, + ]; + + return ; +}; diff --git a/src/BarList/demos/example.tsx b/src/BarList/demos/example.tsx new file mode 100644 index 0000000..8c492c3 --- /dev/null +++ b/src/BarList/demos/example.tsx @@ -0,0 +1,45 @@ +import { SiGithub, SiGoogle, SiReddit, SiX, SiYoutube } from '@icons-pack/react-simple-icons'; +import { BarList, BarListProps } from '@lobehub/charts'; +import { Flexbox } from 'react-layout-kit'; + +const data: BarListProps['data'] = [ + { + href: 'https://x.com/tremorlabs', + icon: , + name: 'X', + value: 456, + }, + { + href: 'https://google.com', + icon: , + name: 'Google', + value: 351, + }, + { + href: 'https://github.com/tremorlabs/tremor', + icon: , + name: 'GitHub', + value: 271, + }, + { + href: 'https://reddit.com', + icon: , + name: 'Reddit', + value: 191, + }, + { + href: 'https://www.youtube.com/@tremorlabs3079', + icon: , + name: 'Youtube', + value: 91, + }, +]; + +export default () => { + return ( + +

Website Analytics

+ +
+ ); +}; diff --git a/src/BarList/demos/index.tsx b/src/BarList/demos/index.tsx new file mode 100644 index 0000000..e758bdf --- /dev/null +++ b/src/BarList/demos/index.tsx @@ -0,0 +1,29 @@ +import { BarList, BarListProps } from '@lobehub/charts'; +import { StoryBook, useControls, useCreateStore } from '@lobehub/ui'; + +const data: BarListProps['data'] = [ + { name: '/home', value: 456 }, + { name: '/imprint', value: 351 }, + { name: '/cancellation', value: 51 }, +]; + +export default () => { + const store = useCreateStore(); + + const props: BarListProps | any = useControls( + { + showAnimation: true, + sortOrder: { + options: ['ascending', 'descending', 'none'], + value: 'descending', + }, + }, + { store }, + ); + + return ( + + + + ); +}; diff --git a/src/BarList/index.md b/src/BarList/index.md new file mode 100644 index 0000000..e4ba70c --- /dev/null +++ b/src/BarList/index.md @@ -0,0 +1,29 @@ +--- +nav: Components +group: Charts +description: Horizontal bars with a customizable label inside. +--- + + + +### Usage example + +You can also pass an `href` into the data, then your datapoint gets rendered as a clickable link with `target="_blank"`. + + + +### Usage example with click event + +The example below shows an interacive chart using the `onValueChange` prop. + + + +### Usage example with a custom colors + +The example below shows a chart with custom `colors`. + + + +## API + + diff --git a/src/BarList/index.tsx b/src/BarList/index.tsx new file mode 100644 index 0000000..698aba5 --- /dev/null +++ b/src/BarList/index.tsx @@ -0,0 +1,164 @@ +'use client'; + +import A from '@lobehub/ui/es/A'; +import React, { HTMLAttributes, ReactNode, forwardRef, useMemo } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +import { useThemeColorRange } from '@/hooks/useThemeColorRange'; +import { ValueFormatter } from '@/types'; +import { defaultValueFormatter } from '@/utils'; + +import { useStyles } from './styles'; + +export interface Bar { + [key: string]: any; + color?: string; + href?: string; + icon?: ReactNode; + key?: string; + name: ReactNode; + target?: string; + value: number; +} + +export interface BarListProps extends HTMLAttributes { + color?: string; + data: Bar[]; + leftLabel?: ReactNode; + onValueChange?: (payload: Bar) => void; + rightLabel?: ReactNode; + showAnimation?: boolean; + sortOrder?: 'ascending' | 'descending' | 'none'; + valueFormatter?: ValueFormatter; +} + +const BarList = forwardRef((props, ref) => { + const { cx, styles } = useStyles(); + const themeColorRange = useThemeColorRange(); + const { + data = [], + color = themeColorRange[0], + valueFormatter = defaultValueFormatter, + showAnimation = false, + onValueChange, + sortOrder = 'descending', + className, + leftLabel, + rightLabel, + style, + ...rest + } = props; + + const sortedData = useMemo(() => { + if (sortOrder === 'none') { + return data; + } + return [...data].sort((a, b) => { + return sortOrder === 'ascending' ? a.value - b.value : b.value - a.value; + }); + }, [data, sortOrder]); + + const widths = useMemo(() => { + const maxValue = Math.max(...sortedData.map((item) => item.value), 0); + return sortedData.map((item) => + item.value === 0 ? 0 : Math.max((item.value / maxValue) * 100, 2), + ); + }, [sortedData]); + + const rowHeight = 32; + const labelHeight = 20; + + return ( + + + {(leftLabel || rightLabel) && ( + + {leftLabel} + + )} + {sortedData.map((item, index) => { + return ( + { + onValueChange?.(item); + }} + style={{ + cursor: onValueChange ? 'pointer' : 'default', + width: `${widths[index]}%`, + }} + width={'100%'} + > +
+ + {item.icon} + {item.href ? ( + event.stopPropagation()} + target={item.target} + > + {item.name} + + ) : ( +
{item.name}
+ )} +
+ + ); + })} + + + {(leftLabel || rightLabel) && ( + + {rightLabel} + + )} + {sortedData.map((item, index) => ( + +
+ {valueFormatter(item.value)} +
+
+ ))} +
+ + ); +}); + +BarList.displayName = 'BarList'; + +export default BarList; diff --git a/src/BarList/styles.ts b/src/BarList/styles.ts new file mode 100644 index 0000000..5459c6d --- /dev/null +++ b/src/BarList/styles.ts @@ -0,0 +1,56 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(({ css, token }) => ({ + bar: css` + position: absolute; + top: 0; + bottom: 0; + left: 0; + + max-width: 100%; + height: 100%; + + opacity: 0.25; + border-radius: ${token.borderRadius}px; + + transition: all 0.25s ${token.motionEaseInOut}; + `, + barContainer: css` + position: relative; + `, + barHover: css` + &:hover { + opacity: 0.4; + } + `, + emphasis: css` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + `, + label: css` + font-size: 14px; + line-height: 16px; + color: ${token.colorTextDescription}; + `, + sourceALabel: css` + font-size: 14px; + line-height: 16px; + color: ${token.colorText} !important; + + &:hover { + color: ${token.colorLinkHover} !important; + } + `, + sourceLabel: css` + font-size: 14px; + line-height: 16px; + color: ${token.colorText}; + `, + strongLabel: css` + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: ${token.colorText}; + `, +})); diff --git a/src/DonutChart/demos/clickEvent.tsx b/src/DonutChart/demos/clickEvent.tsx index a24ca14..c98f5a8 100644 --- a/src/DonutChart/demos/clickEvent.tsx +++ b/src/DonutChart/demos/clickEvent.tsx @@ -1,9 +1,9 @@ -import { DonutChart, EventProps } from '@lobehub/charts'; +import { DonutChart, DonutChartProps, EventProps } from '@lobehub/charts'; import { Highlighter } from '@lobehub/ui'; import { useState } from 'react'; import { Flexbox } from 'react-layout-kit'; -const sales = [ +const data: DonutChartProps['data'] = [ { name: 'New York', sales: 980, @@ -26,7 +26,8 @@ const sales = [ }, ]; -const valueFormatter = (number: number) => `$ ${Intl.NumberFormat('us').format(number).toString()}`; +const valueFormatter: DonutChartProps['valueFormatter'] = (number) => + `$ ${Intl.NumberFormat('us').format(number).toString()}`; export default () => { const [value, setValue] = useState(null); @@ -34,7 +35,7 @@ export default () => { setValue(v)} valueFormatter={valueFormatter} diff --git a/src/DonutChart/demos/customColors.tsx b/src/DonutChart/demos/customColors.tsx index 455f3e1..29a8ddd 100644 --- a/src/DonutChart/demos/customColors.tsx +++ b/src/DonutChart/demos/customColors.tsx @@ -1,7 +1,7 @@ -import { DonutChart } from '@lobehub/charts'; +import { DonutChart, DonutChartProps } from '@lobehub/charts'; import { useTheme } from 'antd-style'; -const sales = [ +const data: DonutChartProps['data'] = [ { name: 'New York', sales: 980, @@ -34,7 +34,7 @@ export default () => { ); diff --git a/src/DonutChart/demos/customTooltip.tsx b/src/DonutChart/demos/customTooltip.tsx index 62518a2..e502ebd 100644 --- a/src/DonutChart/demos/customTooltip.tsx +++ b/src/DonutChart/demos/customTooltip.tsx @@ -1,9 +1,9 @@ -import { ChartTooltipFrame, DonutChart } from '@lobehub/charts'; +import { ChartTooltipFrame, DonutChart, DonutChartProps } from '@lobehub/charts'; import { Typography } from 'antd'; import { useTheme } from 'antd-style'; import { Flexbox } from 'react-layout-kit'; -const sales = [ +const data: DonutChartProps['data'] = [ { name: 'New York', sales: 980, @@ -30,19 +30,13 @@ const sales = [ }, ]; -const valueFormatter = (number: number) => `$ ${Intl.NumberFormat('us').format(number).toString()}`; - -type CustomTooltipTypeBar = { - active: boolean | undefined; - label: any; - payload: any; -}; +const valueFormatter: DonutChartProps['valueFormatter'] = (number) => + `$ ${Intl.NumberFormat('us').format(number).toString()}`; export default () => { const theme = useTheme(); - const customTooltip = (props: CustomTooltipTypeBar) => { - const { payload, active } = props; + const customTooltip: DonutChartProps['customTooltip'] = ({ payload, active }) => { if (!active || !payload) return null; return ( @@ -70,7 +64,7 @@ export default () => { diff --git a/src/DonutChart/demos/example.tsx b/src/DonutChart/demos/example.tsx index 3f4fc80..a790d6c 100644 --- a/src/DonutChart/demos/example.tsx +++ b/src/DonutChart/demos/example.tsx @@ -1,7 +1,7 @@ -import { DonutChart } from '@lobehub/charts'; +import { DonutChart, DonutChartProps } from '@lobehub/charts'; import { Flexbox } from 'react-layout-kit'; -const datahero = [ +const data: DonutChartProps['data'] = [ { name: 'Noche Holding AG', value: 9800, @@ -28,22 +28,23 @@ const datahero = [ }, ]; -const dataFormatter = (number: number) => `$ ${Intl.NumberFormat('us').format(number).toString()}`; +const valueFormatter: DonutChartProps['valueFormatter'] = (number) => + `$ ${Intl.NumberFormat('us').format(number).toString()}`; export default () => (

Donut Variant

console.log(v)} - valueFormatter={dataFormatter} + valueFormatter={valueFormatter} variant="donut" />

Pie Variant

console.log(v)} - valueFormatter={dataFormatter} + valueFormatter={valueFormatter} variant="pie" />
diff --git a/src/DonutChart/demos/index.tsx b/src/DonutChart/demos/index.tsx index 8fa6fe4..3c6456c 100644 --- a/src/DonutChart/demos/index.tsx +++ b/src/DonutChart/demos/index.tsx @@ -1,7 +1,7 @@ -import { DonutChart } from '@lobehub/charts'; +import { DonutChart, DonutChartProps } from '@lobehub/charts'; import { StoryBook, useControls, useCreateStore } from '@lobehub/ui'; -const sales = [ +const data: DonutChartProps['data'] = [ { name: 'New York', sales: 980, @@ -24,7 +24,8 @@ const sales = [ }, ]; -const valueFormatter = (number: number) => `$ ${Intl.NumberFormat('us').format(number).toString()}`; +const valueFormatter: DonutChartProps['valueFormatter'] = (number) => + `$ ${Intl.NumberFormat('us').format(number).toString()}`; export default () => { const store = useCreateStore(); @@ -51,7 +52,7 @@ export default () => { `$ ${Intl.NumberFormat('us').format(number).toString()}`; +const valueFormatter: DonutChartProps['valueFormatter'] = (number) => + `$ ${Intl.NumberFormat('us').format(number).toString()}`; export default () => { return ( - + ); }; diff --git a/src/DonutChart/index.tsx b/src/DonutChart/index.tsx index 7770c7f..ac1bd49 100644 --- a/src/DonutChart/index.tsx +++ b/src/DonutChart/index.tsx @@ -1,7 +1,7 @@ 'use client'; import { css } from 'antd-style'; -import { ComponentType, MouseEvent, forwardRef, useEffect, useState } from 'react'; +import { CSSProperties, ComponentType, MouseEvent, forwardRef, useEffect, useState } from 'react'; import { Flexbox } from 'react-layout-kit'; import { Pie, PieChart as ReChartsDonutChart, ResponsiveContainer, Tooltip } from 'recharts'; @@ -33,6 +33,7 @@ export interface DonutChartProps extends BaseAnimationTimingProps { showAnimation?: boolean; showLabel?: boolean; showTooltip?: boolean; + style?: CSSProperties; valueFormatter?: ValueFormatter; variant?: DonutChartVariant; } @@ -58,6 +59,7 @@ const DonutChart = forwardRef((props, ref) => { className, width = '100%', height = '10rem', + style, ...rest } = props; const CustomTooltip = customTooltip; @@ -67,7 +69,7 @@ const DonutChart = forwardRef((props, ref) => { const [activeIndex, setActiveIndex] = useState(); const hasOnValueChange = !!onValueChange; - function onShapeClick(data: any, index: number, event: MouseEvent) { + const onShapeClick = (data: any, index: number, event: MouseEvent) => { event.stopPropagation(); if (!hasOnValueChange) return; @@ -81,7 +83,7 @@ const DonutChart = forwardRef((props, ref) => { ...data.payload.payload, }); } - } + }; useEffect(() => { const pieSectors = document.querySelectorAll('.recharts-pie-sector'); @@ -106,7 +108,14 @@ const DonutChart = forwardRef((props, ref) => { }); return ( - + {data?.length ? ( { +const valueFormatter: LineChartProps['valueFormatter'] = (number) => { return '$ ' + new Intl.NumberFormat('us').format(number).toString(); }; @@ -71,7 +71,7 @@ export default () => { return ( { setValue(v)} yAxisWidth={30} diff --git a/src/LineChart/demos/customColors.tsx b/src/LineChart/demos/customColors.tsx index dfacaa2..c08d244 100644 --- a/src/LineChart/demos/customColors.tsx +++ b/src/LineChart/demos/customColors.tsx @@ -1,7 +1,7 @@ -import { LineChart } from '@lobehub/charts'; +import { LineChart, LineChartProps } from '@lobehub/charts'; import { useTheme } from 'antd-style'; -const chartdata = [ +const data: LineChartProps['data'] = [ { 'Distance Running': 167, 'Hatha Yoga': 115, @@ -58,7 +58,7 @@ export default () => { diff --git a/src/LineChart/demos/customTooltip.tsx b/src/LineChart/demos/customTooltip.tsx index f71be6e..404e5cc 100644 --- a/src/LineChart/demos/customTooltip.tsx +++ b/src/LineChart/demos/customTooltip.tsx @@ -1,9 +1,9 @@ -import { ChartTooltipFrame, LineChart } from '@lobehub/charts'; +import { ChartTooltipFrame, LineChart, LineChartProps } from '@lobehub/charts'; import { Typography } from 'antd'; import { useTheme } from 'antd-style'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: LineChartProps['data'] = [ { Running: 167, date: 'Jan 23', @@ -30,17 +30,10 @@ const chartdata = [ }, ]; -type CustomTooltipTypeBar = { - active: boolean | undefined; - label: any; - payload: any; -}; - export default () => { const theme = useTheme(); - const customTooltip = (props: CustomTooltipTypeBar) => { - const { payload, active } = props; + const customTooltip: LineChartProps['customTooltip'] = ({ payload, active }) => { if (!active || !payload) return null; return ( @@ -70,7 +63,7 @@ export default () => { diff --git a/src/LineChart/demos/example.tsx b/src/LineChart/demos/example.tsx index 2027e91..9e38bf2 100644 --- a/src/LineChart/demos/example.tsx +++ b/src/LineChart/demos/example.tsx @@ -1,7 +1,7 @@ -import { LineChart } from '@lobehub/charts'; +import { LineChart, LineChartProps } from '@lobehub/charts'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: LineChartProps['data'] = [ { Inverters: 2338, SolarPanels: 2890, @@ -64,7 +64,7 @@ const chartdata = [ }, ]; -const valueFormatter = (number: number) => { +const valueFormatter: LineChartProps['valueFormatter'] = (number) => { return '$ ' + new Intl.NumberFormat('us').format(number).toString(); }; @@ -75,7 +75,7 @@ export default () => {

$34,567

`$${Intl.NumberFormat('us').format(number).toString()}`; +const valueFormatter: LineChartProps['valueFormatter'] = (number) => + `$${Intl.NumberFormat('us').format(number).toString()}`; export default () => { const store = useCreateStore(); - const props: any = useControls( + const props: LineChartProps | any = useControls( { allowDecimals: true, animationDuration: { @@ -115,10 +116,10 @@ export default () => { console.log(v)} - valueFormatter={dataFormatter} + valueFormatter={valueFormatter} {...props} /> diff --git a/src/LineChart/index.tsx b/src/LineChart/index.tsx index 58378e3..a764faa 100644 --- a/src/LineChart/index.tsx +++ b/src/LineChart/index.tsx @@ -74,6 +74,7 @@ const LineChart = forwardRef((props, ref) => { yAxisLabel, width = '100%', height = '20rem', + style, ...rest } = props; const CustomTooltip = customTooltip; @@ -86,7 +87,7 @@ const LineChart = forwardRef((props, ref) => { const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); const hasOnValueChange = !!onValueChange; - function onDotClick(itemData: any, event: MouseEvent) { + const onDotClick = (itemData: any, event: MouseEvent) => { event.stopPropagation(); if (!hasOnValueChange) return; @@ -111,9 +112,9 @@ const LineChart = forwardRef((props, ref) => { ...itemData.payload, }); } - } + }; - function onCategoryClick(dataKey: string) { + const onCategoryClick = (dataKey: string) => { if (!hasOnValueChange) return; if ( (dataKey === activeLegend && !activeDot) || @@ -129,10 +130,17 @@ const LineChart = forwardRef((props, ref) => { }); } setActiveDot(undefined); - } + }; return ( - + {data?.length ? ( ((props, ref) => { ) : undefined } - cursor={{ stroke: '#d1d5db', strokeWidth: 1 }} + cursor={{ stroke: theme.colorTextSecondary, strokeWidth: 1 }} isAnimationActive={false} position={{ y: 0 }} wrapperStyle={{ outline: 'none' }} diff --git a/src/ScatterChart/demos/axis.tsx b/src/ScatterChart/demos/axis.tsx index e2f6a6e..3f48d49 100644 --- a/src/ScatterChart/demos/axis.tsx +++ b/src/ScatterChart/demos/axis.tsx @@ -1,6 +1,6 @@ -import { ScatterChart } from '@lobehub/charts'; +import { ScatterChart, ScatterChartProps } from '@lobehub/charts'; -const chartdata = [ +const data: ScatterChartProps['data'] = [ { 'Country': 'Argentina', 'GDP': 13_467.1236, @@ -129,20 +129,22 @@ const chartdata = [ }, ]; +const valueFormatter: ScatterChartProps['valueFormatter'] = { + size: (population) => `${(population / 1_000_000).toFixed(1)}M people`, + x: (amount) => `$${(amount / 1000).toFixed(1)}K`, + y: (lifeExp) => `${lifeExp} yrs`, +}; + export default () => { return ( `${(population / 1_000_000).toFixed(1)}M people`, - x: (amount) => `$${(amount / 1000).toFixed(1)}K`, - y: (lifeExp) => `${lifeExp} yrs`, - }} + valueFormatter={valueFormatter} x="GDP" xAxisLabel="GDP" y="Life expectancy" diff --git a/src/ScatterChart/demos/clickEvent.tsx b/src/ScatterChart/demos/clickEvent.tsx index 898787e..ee53e6c 100644 --- a/src/ScatterChart/demos/clickEvent.tsx +++ b/src/ScatterChart/demos/clickEvent.tsx @@ -1,9 +1,9 @@ -import { EventProps, ScatterChart } from '@lobehub/charts'; +import { EventProps, ScatterChart, ScatterChartProps } from '@lobehub/charts'; import { Highlighter } from '@lobehub/ui'; import { useState } from 'react'; import { Flexbox } from 'react-layout-kit'; -const chartdata = [ +const data: ScatterChartProps['data'] = [ { 'Country': 'Argentina', 'GDP': 13_467.1236, @@ -132,6 +132,12 @@ const chartdata = [ }, ]; +const valueFormatter: ScatterChartProps['valueFormatter'] = { + size: (population) => `${(population / 1_000_000).toFixed(1)}M people`, + x: (amount) => `$${(amount / 1000).toFixed(1)}K`, + y: (lifeExp) => `${lifeExp} yrs`, +}; + export default () => { const [value, setValue] = useState(null); return ( @@ -139,17 +145,13 @@ export default () => {

Closed Pull Requests

setValue(v)} showLegend={false} showOpacity={true} size="Population" - valueFormatter={{ - size: (population) => `${(population / 1_000_000).toFixed(1)}M people`, - x: (amount) => `$${(amount / 1000).toFixed(1)}K`, - y: (lifeExp) => `${lifeExp} yrs`, - }} + valueFormatter={valueFormatter} x="GDP" y="Life expectancy" yAxisWidth={50} diff --git a/src/ScatterChart/demos/customColors.tsx b/src/ScatterChart/demos/customColors.tsx index f6aefc8..365fd99 100644 --- a/src/ScatterChart/demos/customColors.tsx +++ b/src/ScatterChart/demos/customColors.tsx @@ -1,7 +1,7 @@ -import { ScatterChart } from '@lobehub/charts'; +import { ScatterChart, ScatterChartProps } from '@lobehub/charts'; import { useTheme } from 'antd-style'; -const chartdata = [ +const data: ScatterChartProps['data'] = [ { location: 'Location A', x: 100, @@ -70,7 +70,7 @@ export default () => { { const theme = useTheme(); - const customTooltip = ({ payload, active, label }: CustomTooltipTypeBar) => { + const customTooltip: ScatterChartProps['customTooltip'] = ({ payload, active, label }) => { if (!active || !payload) return null; return ( @@ -114,7 +108,7 @@ export default () => { `${(population / 1_000_000).toFixed(1)}M people`, + x: (amount) => `$${(amount / 1000).toFixed(1)}K`, + y: (lifeExp) => `${lifeExp} yrs`, +}; + export default () => { return ( `${(population / 1_000_000).toFixed(1)}M people`, - x: (amount) => `$${(amount / 1000).toFixed(1)}K`, - y: (lifeExp) => `${lifeExp} yrs`, - }} + valueFormatter={valueFormatter} x="GDP" y="Life expectancy" yAxisWidth={50} diff --git a/src/ScatterChart/demos/index.tsx b/src/ScatterChart/demos/index.tsx index d0b80b8..b99b270 100644 --- a/src/ScatterChart/demos/index.tsx +++ b/src/ScatterChart/demos/index.tsx @@ -1,7 +1,7 @@ -import { ScatterChart } from '@lobehub/charts'; +import { ScatterChart, ScatterChartProps } from '@lobehub/charts'; import { StoryBook, useControls, useCreateStore } from '@lobehub/ui'; -const chartdata = [ +const data: ScatterChartProps['data'] = [ { 'Country': 'Argentina', 'GDP': 13_467.1236, @@ -130,10 +130,16 @@ const chartdata = [ }, ]; +const valueFormatter: ScatterChartProps['valueFormatter'] = { + size: (population) => `${(population / 1_000_000).toFixed(1)}M people`, + x: (amount) => `$${(amount / 1000).toFixed(1)}K`, + y: (lifeExp) => `${lifeExp} yrs`, +}; + export default () => { const store = useCreateStore(); - const props: any = useControls( + const props: ScatterChartProps | any = useControls( { allowDecimals: true, animationDuration: { @@ -175,14 +181,10 @@ export default () => { console.log(v)} size="Population" - valueFormatter={{ - size: (population) => `${(population / 1_000_000).toFixed(1)}M people`, - x: (amount) => `$${(amount / 1000).toFixed(1)}K`, - y: (lifeExp) => `${lifeExp} yrs`, - }} + valueFormatter={valueFormatter} x="GDP" y="Life expectancy" {...props} diff --git a/src/ScatterChart/index.tsx b/src/ScatterChart/index.tsx index 5acdf74..aff3a90 100644 --- a/src/ScatterChart/index.tsx +++ b/src/ScatterChart/index.tsx @@ -128,6 +128,7 @@ const ScatterChart = forwardRef((props, ref) yAxisLabel, width = '100%', height = '20rem', + style, ...rest } = props; const CustomTooltip = customTooltip; @@ -136,7 +137,7 @@ const ScatterChart = forwardRef((props, ref) const [activeLegend, setActiveLegend] = useState(); const hasOnValueChange = !!onValueChange; - function onNodeClick(data: any, index: number, event: MouseEvent) { + const onNodeClick = (data: any, index: number, event: MouseEvent) => { event.stopPropagation(); if (!hasOnValueChange) return; if (deepEqual(activeNode, data.node)) { @@ -152,9 +153,9 @@ const ScatterChart = forwardRef((props, ref) ...data.payload, }); } - } + }; - function onCategoryClick(dataKey: string) { + const onCategoryClick = (dataKey: string) => { if (!hasOnValueChange) return; if (dataKey === activeLegend && !activeNode) { setActiveLegend(undefined); @@ -167,7 +168,7 @@ const ScatterChart = forwardRef((props, ref) }); } setActiveNode(undefined); - } + }; const categories = constructCategories(data, category); const categoryColors = constructCategoryColors(categories, colors); @@ -177,7 +178,14 @@ const ScatterChart = forwardRef((props, ref) const yAxisDomain = getYAxisDomain(autoMinYValue, minYValue, maxYValue); return ( - + {data?.length ? ( ((props, ref) } : undefined } - cursor={{ stroke: '#d1d5db', strokeWidth: 1 }} + cursor={{ stroke: theme.colorTextSecondary, strokeWidth: 1 }} isAnimationActive={false} wrapperStyle={{ outline: 'none' }} /> diff --git a/src/SparkChart/SparkAreaChart.tsx b/src/SparkChart/SparkAreaChart.tsx new file mode 100644 index 0000000..9f15acf --- /dev/null +++ b/src/SparkChart/SparkAreaChart.tsx @@ -0,0 +1,132 @@ +'use client'; + +import { css } from 'antd-style'; +import { forwardRef } from 'react'; +import { Flexbox } from 'react-layout-kit'; +import { Area, AreaChart as ReChartsAreaChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'; +import { AxisDomain } from 'recharts/types/util/types'; + +import { useStyles } from '@/AreaChart/styles'; +import BaseSparkChartProps from '@/common/BaseSparkChartProps'; +import NoData from '@/common/NoData'; +import { constructCategoryColors, getYAxisDomain } from '@/common/utils'; +import { useThemeColorRange } from '@/hooks/useThemeColorRange'; +import { CurveType } from '@/types'; + +export interface SparkAreaChartProps extends BaseSparkChartProps { + connectNulls?: boolean; + curveType?: CurveType; + showGradient?: boolean; + stack?: boolean; +} + +const SparkAreaChart = forwardRef((props, ref) => { + const { cx, theme } = useStyles(); + const themeColorRange = useThemeColorRange(); + + const { + data = [], + categories = [], + index, + stack = false, + colors = themeColorRange, + showAnimation = false, + animationDuration = 900, + showGradient = true, + curveType = 'linear', + connectNulls = false, + noDataText =
, + autoMinValue = false, + minValue, + maxValue, + className, + width = 112, + height = 48, + style, + ...rest + } = props; + const categoryColors = constructCategoryColors(categories, colors); + const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); + + return ( + + + {data?.length ? ( + + + + {categories.map((category) => { + return ( + + {showGradient ? ( + + + + + ) : ( + + + + )} + + ); + })} + {categories.map((category) => ( + + ))} + + ) : ( + + )} + + + ); +}); + +SparkAreaChart.displayName = 'AreaChart'; + +export default SparkAreaChart; diff --git a/src/SparkChart/SparkBarChart.tsx b/src/SparkChart/SparkBarChart.tsx new file mode 100644 index 0000000..282e1b2 --- /dev/null +++ b/src/SparkChart/SparkBarChart.tsx @@ -0,0 +1,89 @@ +'use client'; + +import { css } from 'antd-style'; +import { forwardRef } from 'react'; +import { Flexbox } from 'react-layout-kit'; +import { Bar, BarChart as ReChartsBarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'; +import { AxisDomain } from 'recharts/types/util/types'; + +import { useStyles } from '@/BarChart/styles'; +import BaseSparkChartProps from '@/common/BaseSparkChartProps'; +import NoData from '@/common/NoData'; +import { constructCategoryColors, getYAxisDomain } from '@/common/utils'; +import { useThemeColorRange } from '@/hooks/useThemeColorRange'; + +export interface SparkBarChartProps extends BaseSparkChartProps { + relative?: boolean; + stack?: boolean; +} + +const SparkBarChart = forwardRef((props, ref) => { + const { cx, theme } = useStyles(); + const themeColorRange = useThemeColorRange(); + const { + data = [], + categories = [], + index, + colors = themeColorRange, + stack = false, + relative = false, + animationDuration = 900, + showAnimation = false, + noDataText =
, + autoMinValue = false, + minValue, + maxValue, + className, + width = 112, + height = 48, + style, + ...rest + } = props; + const categoryColors = constructCategoryColors(categories, colors); + const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); + + return ( + + + {data?.length ? ( + + + + {categories.map((category) => ( + + ))} + + ) : ( + + )} + + + ); +}); + +SparkBarChart.displayName = 'SparkBarChart'; + +export default SparkBarChart; diff --git a/src/SparkChart/SparkLineChart.tsx b/src/SparkChart/SparkLineChart.tsx new file mode 100644 index 0000000..90afb4c --- /dev/null +++ b/src/SparkChart/SparkLineChart.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { css } from 'antd-style'; +import { forwardRef } from 'react'; +import { Flexbox } from 'react-layout-kit'; +import { Line, LineChart as ReChartsLineChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'; +import { AxisDomain } from 'recharts/types/util/types'; + +import { useStyles } from '@/LineChart/styles'; +import BaseSparkChartProps from '@/common/BaseSparkChartProps'; +import NoData from '@/common/NoData'; +import { constructCategoryColors, getYAxisDomain } from '@/common/utils'; +import { useThemeColorRange } from '@/hooks/useThemeColorRange'; +import { CurveType } from '@/types'; + +export interface SparkLineChartProps extends BaseSparkChartProps { + connectNulls?: boolean; + curveType?: CurveType; +} + +const SparkLineChart = forwardRef((props, ref) => { + const { cx, theme } = useStyles(); + const themeColorRange = useThemeColorRange(); + const { + data = [], + categories = [], + index, + colors = themeColorRange, + animationDuration = 900, + showAnimation = false, + curveType = 'linear', + connectNulls = false, + noDataText =
, + autoMinValue = false, + minValue, + maxValue, + className, + width = 112, + height = 48, + style, + ...rest + } = props; + const categoryColors = constructCategoryColors(categories, colors); + const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); + + return ( + + + {data?.length ? ( + + + + {categories.map((category) => ( + + ))} + + ) : ( + + )} + + + ); +}); + +SparkLineChart.displayName = 'SparkLineChart'; + +export default SparkLineChart; diff --git a/src/SparkChart/demos/index.tsx b/src/SparkChart/demos/index.tsx new file mode 100644 index 0000000..8f8519b --- /dev/null +++ b/src/SparkChart/demos/index.tsx @@ -0,0 +1,50 @@ +import { SparkAreaChart, SparkBarChart, SparkLineChart } from '@lobehub/charts'; +import { Flexbox } from 'react-layout-kit'; + +const data = [ + { + Benchmark: 3000, + Performance: 4000, + month: 'Jan 21', + }, + { + Benchmark: 2000, + Performance: 3000, + month: 'Feb 21', + }, + { + Benchmark: 1700, + Performance: 2000, + month: 'Mar 21', + }, + { + Benchmark: 2500, + Performance: 2780, + month: 'Apr 21', + }, + { + Benchmark: 1890, + Performance: 1890, + month: 'May 21', + }, + { + Benchmark: 2000, + Performance: 2390, + month: 'Jun 21', + }, + { + Benchmark: 3000, + Performance: 3490, + month: 'Jul 21', + }, +]; + +export default () => { + return ( + + + + + + ); +}; diff --git a/src/SparkChart/index.md b/src/SparkChart/index.md new file mode 100644 index 0000000..a2db2f7 --- /dev/null +++ b/src/SparkChart/index.md @@ -0,0 +1,11 @@ +--- +nav: Components +group: Charts +description: A small graph capable of visualizing data in a simplified form. +--- + + + +## API + + diff --git a/src/SparkChart/index.tsx b/src/SparkChart/index.tsx new file mode 100644 index 0000000..b33e8aa --- /dev/null +++ b/src/SparkChart/index.tsx @@ -0,0 +1,3 @@ +export { default as SparkAreaChart, type SparkAreaChartProps } from './SparkAreaChart'; +export { default as SparkBarChart, type SparkBarChartProps } from './SparkBarChart'; +export { default as SparkLineChart, type SparkLineChartProps } from './SparkLineChart'; diff --git a/src/Tracker/TrackerBlock.tsx b/src/Tracker/TrackerBlock.tsx new file mode 100644 index 0000000..32efdd1 --- /dev/null +++ b/src/Tracker/TrackerBlock.tsx @@ -0,0 +1,70 @@ +'use client'; + +import { Tooltip } from '@lobehub/ui'; +import { createStyles } from 'antd-style'; +import { ReactNode, forwardRef, useMemo } from 'react'; +import { Flexbox, FlexboxProps } from 'react-layout-kit'; + +const useStyles = createStyles(({ css, token }) => ({ + block: css` + background: ${token.colorFill}; + + &:first-child { + border-top-left-radius: ${token.borderRadiusSM}px; + border-bottom-left-radius: ${token.borderRadiusSM}px; + } + + &:last-child { + border-top-right-radius: ${token.borderRadiusSM}px; + border-bottom-right-radius: ${token.borderRadiusSM}px; + } + + &:hover { + opacity: 0.5; + } + `, +})); + +export interface TrackerBlockProps { + color?: 'success' | 'warning' | 'error' | string; + key?: string | number; + tooltip?: ReactNode; +} + +const TrackerBlock = forwardRef( + ({ color, tooltip, width, style, ...rest }, ref) => { + const { styles, theme } = useStyles(); + + const blockColor = useMemo(() => { + switch (color) { + case 'success': { + return theme.colorSuccess; + } + case 'warning': { + return theme.colorWarning; + } + case 'error': { + return theme.colorError; + } + default: { + return color; + } + } + }, [color]); + + return ( + + + + ); + }, +); + +export default TrackerBlock; diff --git a/src/Tracker/demos/clickEvent.tsx b/src/Tracker/demos/clickEvent.tsx new file mode 100644 index 0000000..8b57848 --- /dev/null +++ b/src/Tracker/demos/clickEvent.tsx @@ -0,0 +1,41 @@ +import { Tracker, TrackerProps } from '@lobehub/charts'; +import { Highlighter } from '@lobehub/ui'; +import { useState } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +const data: TrackerProps['data'] = [ + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, +]; + +export default () => { + const [value, setValue] = useState({}); + return ( + + setValue(v)} /> + + {JSON.stringify(value, null, 2)} + + + ); +}; diff --git a/src/Tracker/demos/customColors.tsx b/src/Tracker/demos/customColors.tsx new file mode 100644 index 0000000..7f740e3 --- /dev/null +++ b/src/Tracker/demos/customColors.tsx @@ -0,0 +1,27 @@ +import { Tracker, TrackerProps } from '@lobehub/charts'; + +const data: TrackerProps['data'] = [ + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#f0652f', tooltip: 'Online' }, + { color: '#ffcc33', tooltip: 'Event' }, + { color: '#ffcc33', tooltip: 'Event' }, + { tooltip: 'Downtime' }, + { color: '#ffcc33', tooltip: 'Event' }, + { color: '#ffcc33', tooltip: 'Event' }, + { color: '#ffcc33', tooltip: 'Event' }, + { color: '#ffcc33', tooltip: 'Event' }, +]; + +export default () => { + return ; +}; diff --git a/src/Tracker/demos/customTooltip.tsx b/src/Tracker/demos/customTooltip.tsx new file mode 100644 index 0000000..fecbde2 --- /dev/null +++ b/src/Tracker/demos/customTooltip.tsx @@ -0,0 +1,77 @@ +import { Tracker, TrackerProps } from '@lobehub/charts'; +import { Typography } from 'antd'; +import { useTheme } from 'antd-style'; +import { Flexbox } from 'react-layout-kit'; + +const data: TrackerProps['data'] = [ + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, +]; + +export default () => { + const theme = useTheme(); + const customTooltip: TrackerProps['customTooltip'] = (item) => { + let color; + switch (item.color) { + case 'success': { + color = theme.colorSuccess; + break; + } + case 'warning': { + color = theme.colorWarning; + break; + } + case 'error': { + color = theme.colorError; + break; + } + default: { + color = item.color; + break; + } + } + + return ( + + + + + {item.tooltip} + + + {item.color?.toUpperCase()} + + + + ); + }; + + return ; +}; diff --git a/src/Tracker/demos/example.tsx b/src/Tracker/demos/example.tsx new file mode 100644 index 0000000..2fb617d --- /dev/null +++ b/src/Tracker/demos/example.tsx @@ -0,0 +1,30 @@ +import { Tracker, TrackerProps } from '@lobehub/charts'; + +const data: TrackerProps['data'] = [ + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, +]; + +export default () => { + return ; +}; diff --git a/src/Tracker/demos/index.tsx b/src/Tracker/demos/index.tsx new file mode 100644 index 0000000..03238bb --- /dev/null +++ b/src/Tracker/demos/index.tsx @@ -0,0 +1,55 @@ +import { Tracker, TrackerProps } from '@lobehub/charts'; +import { StoryBook, useControls, useCreateStore } from '@lobehub/ui'; + +const data: TrackerProps['data'] = [ + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'error', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'warning', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, + { color: 'success', tooltip: 'Tracker Info' }, +]; + +export default () => { + const store = useCreateStore(); + + const props: TrackerProps | any = useControls( + { + blockWidth: { + step: 1, + value: 12, + }, + gap: { + step: 1, + value: 4, + }, + height: { + step: 1, + value: 40, + }, + }, + { store }, + ); + + return ( + + + + ); +}; diff --git a/src/Tracker/index.md b/src/Tracker/index.md new file mode 100644 index 0000000..ef6bbcc --- /dev/null +++ b/src/Tracker/index.md @@ -0,0 +1,35 @@ +--- +nav: Components +group: Charts +description: Component for visualizing activity logs or other data related to monitoring. +--- + + + +### Usage example + +The example below shows a composition of an uptime monitor with a `Tracker` component. The \`tooltip property acts as pop-up information and displays the provided status. + + + +### Usage example with click event + +The example below shows an interacive chart using the `onValueChange` prop. + + + +### Usage example with custom tooltip + +The example below shows a custom tooltip using `customTooltip` prop. + + + +### Usage example with a custom colors + +The example below shows a chart with custom `colors`. + + + +## API + + diff --git a/src/Tracker/index.tsx b/src/Tracker/index.tsx new file mode 100644 index 0000000..c066083 --- /dev/null +++ b/src/Tracker/index.tsx @@ -0,0 +1,99 @@ +'use client'; + +import { ReactNode, forwardRef } from 'react'; +import { Flexbox, FlexboxProps } from 'react-layout-kit'; + +import TrackerBlock, { TrackerBlockProps } from './TrackerBlock'; +import { useStyles } from './styles'; + +interface DataItem extends TrackerBlockProps { + [key: string]: any; +} + +export interface TrackerProps extends FlexboxProps { + blockGap?: number | string; + blockHeight?: number | string; + blockWidth?: number | string; + customTooltip?: (payload: DataItem) => ReactNode; + data: DataItem[]; + leftLabel?: ReactNode; + onValueChange?: (payload: DataItem) => void; + rightLabel?: ReactNode; +} + +const Tracker = forwardRef((props, ref) => { + const { + data = [], + className, + height, + width = 'fit-content', + blockWidth = 12, + blockHeight = 40, + blockGap = 4, + customTooltip, + onValueChange, + leftLabel, + rightLabel, + style, + ...rest + } = props; + + const { styles, cx } = useStyles(); + + const content = data.map((item, idx) => ( + onValueChange?.(item)} + style={{ + cursor: onValueChange ? 'pointer' : 'default', + }} + tooltip={customTooltip ? customTooltip(item) : item.tooltip} + width={blockWidth} + /> + )); + + if (leftLabel || rightLabel) { + return ( + + +
{leftLabel}
+
+ {rightLabel} +
+
+ + {content} + +
+ ); + } + + return ( + + {content} + + ); +}); + +Tracker.displayName = 'Tracker'; + +export default Tracker; diff --git a/src/Tracker/styles.ts b/src/Tracker/styles.ts new file mode 100644 index 0000000..731f97b --- /dev/null +++ b/src/Tracker/styles.ts @@ -0,0 +1,20 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(({ css, token }) => ({ + emphasis: css` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + `, + label: css` + font-size: 14px; + line-height: 16px; + color: ${token.colorTextDescription}; + `, + strongLabel: css` + font-size: 14px; + font-weight: 500; + line-height: 16px; + fill: ${token.colorTextSecondary}; + `, +})); diff --git a/src/common/BaseSparkChartProps.tsx b/src/common/BaseSparkChartProps.tsx new file mode 100644 index 0000000..c555581 --- /dev/null +++ b/src/common/BaseSparkChartProps.tsx @@ -0,0 +1,16 @@ +import { HTMLAttributes } from 'react'; + +import BaseAnimationTimingProps from './BaseAnimationTimingProps'; + +interface BaseSparkChartProps extends BaseAnimationTimingProps, HTMLAttributes { + autoMinValue?: boolean; + categories: string[]; + colors?: string[]; + data: any[]; + index: string; + maxValue?: number; + minValue?: number; + noDataText?: string; +} + +export default BaseSparkChartProps; diff --git a/src/common/utils.ts b/src/common/utils.ts index 73f11ad..289a518 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -29,7 +29,7 @@ export const constructCategories = (data: any[], color?: string): string[] => { return [...categories]; }; -export function deepEqual(obj1: any, obj2: any) { +export const deepEqual = (obj1: any, obj2: any) => { if (obj1 === obj2) return true; if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) @@ -45,9 +45,9 @@ export function deepEqual(obj1: any, obj2: any) { } return true; -} +}; -export function hasOnlyOneValueForThisKey(array: any[], keyToCheck: string) { +export const hasOnlyOneValueForThisKey = (array: any[], keyToCheck: string) => { const val = []; for (const obj of array) { @@ -60,4 +60,4 @@ export function hasOnlyOneValueForThisKey(array: any[], keyToCheck: string) { } return true; -} +}; diff --git a/src/hooks/useThemeColorRange.ts b/src/hooks/useThemeColorRange.ts index 71aaa70..ac7b5d2 100644 --- a/src/hooks/useThemeColorRange.ts +++ b/src/hooks/useThemeColorRange.ts @@ -9,7 +9,6 @@ export const useThemeColorRange = () => { theme.lime, theme.volcano, theme.purple, - theme.blue, theme.orange, theme.green, diff --git a/src/index.ts b/src/index.ts index be23049..396858a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,11 @@ export { default as AreaChart, type AreaChartProps } from './AreaChart'; export { default as BarChart, type BarChartProps } from './BarChart'; +export { type Bar, default as BarList, type BarListProps } from './BarList'; export { default as ChartTooltipFrame } from './common/ChartTooltip/ChartTooltipFrame'; export { default as Legend, type LegendProps } from './components/Legend'; export { default as DonutChart, type DonutChartProps } from './DonutChart'; export { default as LineChart, type LineChartProps } from './LineChart'; export { default as ScatterChart, type ScatterChartProps } from './ScatterChart'; +export * from './SparkChart'; +export { default as Tracker, type TrackerProps } from './Tracker'; export type * from './types';