Skip to content

Commit

Permalink
feat: finalize area and line charts
Browse files Browse the repository at this point in the history
  • Loading branch information
Pagebakers committed Jan 8, 2024
1 parent 4d5c8db commit 83a5aed
Show file tree
Hide file tree
Showing 9 changed files with 437 additions and 141 deletions.
1 change: 1 addition & 0 deletions packages/saas-ui-charts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"react-dom": ">=18"
},
"devDependencies": {
"date-fns": "^3.1.0",
"prop-types": "^15.8.1",
"tsup": "^6.7.0"
}
Expand Down
134 changes: 90 additions & 44 deletions packages/saas-ui-charts/src/area-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'

import { ClassNames } from '@emotion/react'
import {
Box,
SystemProps,
Expand All @@ -18,23 +19,39 @@ import {
Tooltip,
ResponsiveContainer,
TooltipProps,
Legend,
} from 'recharts'
import type { CurveType } from 'recharts/types/shape/Curve'

import { ClassNames } from '@emotion/react'
import { ChartData } from './types'
import { ChartLegend } from './legend'

export interface AreaChartProps {
data: ChartData[]
allowDecimals?: boolean
animationDuration?: number
data: Record<string, string | number>[]
categories?: string[]
colors?: string[]
index?: string
intervalType?: 'preserveStartEnd' | 'equidistantPreserveStart'
height: SystemProps['height']
showGrid?: boolean
color?: string
connectNulls?: boolean
curveType?: CurveType
strokeWidth?: string
name?: string
gradientOpacity?: number
tickFormatter?(value: number): string
variant?: 'line' | 'solid' | 'gradient'
valueFormatter?(value: number): string
showAnimation?: boolean
showGrid?: boolean
showLegend?: boolean
showXAxis?: boolean
showYAxis?: boolean
stack?: boolean
startEndOnly?: boolean
tooltipContent?(props: TooltipProps<any, any>): React.ReactNode
tooltipFormatter?(value: string, name: string, props: any): string
variant?: 'line' | 'solid' | 'gradient'
yAxisWidth?: number
legendHeight?: number
children?: React.ReactNode
}

Expand All @@ -45,32 +62,35 @@ export const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(
categories = [],
colors = ['primary'],
height,
showGrid = true,
color = 'primary',
connectNulls = false,
curveType = 'linear',
index = 'date',
startEndOnly = false,
intervalType = 'equidistantPreserveStart',
allowDecimals = true,
strokeWidth = 2,
showAnimation = true,
showGrid = true,
showLegend = true,
showXAxis = true,
showYAxis = true,
stack = false,
yAxisWidth = 30,
yAxisWidth = 40,
legendHeight = 32,
animationDuration = 500,
name,
tickFormatter,
valueFormatter,
variant = 'gradient',
gradientOpacity = 0.8,
tooltipContent,
tooltipFormatter = (value: string, name: string, props: any) => {
return props.payload.yv
},
children,
} = props

const theme = useTheme()
const id = useId()
const styles = useStyleConfig('Tooltip')

const tooltipTheme = useStyleConfig('Tooltip')
const tooltipStyles = css(tooltipTheme)(theme)

const categoryColors = Object.fromEntries(
categories.map((category, index) => [category, colors[index] || 'gray'])
Expand All @@ -80,57 +100,41 @@ export const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(
return theme.colors[categoryColors[category]]?.[500]
}

const getFill = (category: strong) => {
const getGradientId = (category: string) => {
return `${id}-${categoryColors[category]}-gradient`
}

const getFill = (category: string) => {
switch (variant) {
case 'solid':
return getColor(category)
case 'gradient':
return `url(#${categoryColors[category]}-gradient)`
return `url(#${getGradientId(category)})`
default:
return 'transparent'
}
}

const tooltipStyles = css(styles)(theme)

return (
<ClassNames>
{({ css }) => {
return (
<Box ref={ref} height={height} fontSize="sm">
<ResponsiveContainer width="100%" height="100%">
<ReAreaChart data={data}>
<defs>
{categories.map((category, index) => (
<linearGradient
id={`${categoryColors[category]}-gradient`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={getColor(category)}
stopOpacity={gradientOpacity}
/>
<stop
offset="95%"
stopColor={getColor(category)}
stopOpacity={0}
/>
</linearGradient>
))}
</defs>
{showGrid && (
<CartesianGrid
strokeDasharray=" 1 1 1"
vertical={false}
strokeOpacity={useColorModeValue(0.8, 0.3)}
/>
)}

<XAxis
padding={{ left: 20, right: 20 }}
dataKey={index}
hide={!showXAxis}
tick={{ transform: 'translate(0, 6)' }}
ticks={
startEndOnly
? [data[0][index], data[data.length - 1][index]]
Expand All @@ -140,20 +144,27 @@ export const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(
tickLine={false}
axisLine={false}
minTickGap={5}
style={{
color: 'var(--chakra-colors-muted)',
}}
/>

<YAxis
width={yAxisWidth}
hide={!showYAxis}
axisLine={false}
tickLine={false}
tick={{ transform: 'translate(-3, 0)' }}
type="number"
tickFormatter={tickFormatter}
tickFormatter={valueFormatter}
allowDecimals={allowDecimals}
style={{
color: 'var(--chakra-colors-muted)',
}}
/>

<Tooltip
formatter={tooltipFormatter}
formatter={valueFormatter}
wrapperStyle={{ outline: 'none' }}
contentStyle={{
background: 'var(--tooltip-bg)',
Expand All @@ -167,6 +178,41 @@ export const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(
content={tooltipContent}
/>

{showLegend && (
<Legend
verticalAlign="top"
align="right"
height={legendHeight}
content={({ payload }) => {
return <ChartLegend payload={payload} />
}}
/>
)}

<defs>
{categories.map((category) => (
<linearGradient
key={category}
id={getGradientId(category)}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={getColor(category)}
stopOpacity={gradientOpacity}
/>
<stop
offset="95%"
stopColor={getColor(category)}
stopOpacity={0}
/>
</linearGradient>
))}
</defs>

{children}

{categories.map((category) => (
Expand All @@ -180,7 +226,7 @@ export const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(
strokeLinecap="round"
fill={getFill(category)}
name={name}
// isAnimationActive={showAnimation}
isAnimationActive={showAnimation}
animationDuration={animationDuration}
stackId={stack ? 'a' : undefined}
connectNulls={connectNulls}
Expand Down
2 changes: 1 addition & 1 deletion packages/saas-ui-charts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { type SparklineProps, Sparklines } from './sparklines'
export { LineChart, type LineChartProps } from './line-chart'
export { AreaChart, type AreaChartProps } from './area-chart'
export { LineChart, type LineChartProps } from './line-chart'
export { BarChart, type BarChartProps } from './bar-chart'
export type { ChartData } from './types'
21 changes: 21 additions & 0 deletions packages/saas-ui-charts/src/legend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Box, HStack, forwardRef } from '@chakra-ui/react'
import { Payload } from 'recharts/types/component/DefaultLegendContent'

export interface ChartLegendProps {
payload?: Payload[]
}

export const ChartLegend = forwardRef<ChartLegendProps, 'div'>(
({ payload }, ref) => {
return (
<HStack ref={ref} justifyContent="flex-end" spacing="4">
{payload?.map((entry, index) => (
<HStack key={`item-${index}`} spacing="2">
<Box rounded="full" bg={entry.color} boxSize="2" />
<Box as="span">{entry.value}</Box>
</HStack>
))}
</HStack>
)
}
)
Loading

0 comments on commit 83a5aed

Please sign in to comment.