231 lines
7.4 KiB
JavaScript
231 lines
7.4 KiB
JavaScript
import { LineChart } from 'recharts/lib/chart/LineChart'
|
|
import { AreaChart } from 'recharts/lib/chart/AreaChart'
|
|
import { ComposedChart } from 'recharts/lib/chart/ComposedChart'
|
|
import { Line } from 'recharts/lib/cartesian/Line'
|
|
import { XAxis } from 'recharts/lib/cartesian/XAxis'
|
|
import { YAxis } from 'recharts/lib/cartesian/YAxis'
|
|
import { Area } from 'recharts/lib/cartesian/Area'
|
|
import { Bar } from 'recharts/lib/cartesian/Bar'
|
|
import { Tooltip } from 'recharts/lib/component/Tooltip'
|
|
import { Legend } from 'recharts/lib/component/Legend'
|
|
import { ResponsiveContainer } from 'recharts/lib/component/ResponsiveContainer'
|
|
import { PieChart } from 'recharts/lib/chart/PieChart'
|
|
import { Cell } from 'recharts/lib/component/Cell'
|
|
import { Pie } from 'recharts/lib/polar/Pie'
|
|
import { abbrNum } from '@/lib/format'
|
|
import { useRouter } from 'next/router'
|
|
import { timeUnitForRange } from '@/lib/time'
|
|
|
|
const dateFormatter = (when, from, to) => {
|
|
const unit = xAxisName(when, from, to)
|
|
return timeStr => {
|
|
const date = new Date(timeStr)
|
|
switch (unit) {
|
|
case 'day':
|
|
case 'week':
|
|
return `${('0' + (date.getUTCMonth() % 12 + 1)).slice(-2)}/${date.getUTCDate()}`
|
|
case 'month':
|
|
return `${('0' + (date.getUTCMonth() % 12 + 1)).slice(-2)}/${String(date.getUTCFullYear()).slice(-2)}`
|
|
default:
|
|
return `${date.getHours() % 12 || 12}${date.getHours() >= 12 ? 'pm' : 'am'}`
|
|
}
|
|
}
|
|
}
|
|
|
|
const labelFormatter = (when, from, to) => {
|
|
const unit = xAxisName(when, from, to)
|
|
const dateFormat = dateFormatter(when, from, to)
|
|
return timeStr => `${unit} ${dateFormat(timeStr)}`
|
|
}
|
|
|
|
function xAxisName (when, from, to) {
|
|
if (from) {
|
|
return timeUnitForRange([from, to])
|
|
}
|
|
switch (when) {
|
|
case 'week':
|
|
case 'month':
|
|
return 'day'
|
|
case 'year':
|
|
case 'forever':
|
|
return 'month'
|
|
default:
|
|
return 'hour'
|
|
}
|
|
}
|
|
|
|
const transformData = data => {
|
|
return data.map(entry => {
|
|
const obj = { time: entry.time }
|
|
entry.data.forEach(entry1 => {
|
|
obj[entry1.name] = entry1.value
|
|
})
|
|
return obj
|
|
})
|
|
}
|
|
|
|
const COLORS = [
|
|
'var(--bs-secondary)',
|
|
'var(--bs-info)',
|
|
'var(--bs-success)',
|
|
'var(--bs-boost)',
|
|
'var(--theme-grey)',
|
|
'var(--bs-danger)',
|
|
'var(--bs-code-color)'
|
|
]
|
|
|
|
function getColor (i) {
|
|
return COLORS[i % COLORS.length]
|
|
}
|
|
|
|
export function WhenAreaChart ({ data }) {
|
|
const router = useRouter()
|
|
if (!data || data.length === 0) {
|
|
return null
|
|
}
|
|
// transform data into expected shape
|
|
data = transformData(data)
|
|
// need to grab when
|
|
const when = router.query.when
|
|
const from = router.query.from
|
|
const to = router.query.to
|
|
|
|
return (
|
|
<ResponsiveContainer width='100%' height={300} minWidth={300}>
|
|
<AreaChart
|
|
data={data}
|
|
margin={{
|
|
top: 5,
|
|
right: 5,
|
|
left: 0,
|
|
bottom: 0
|
|
}}
|
|
>
|
|
<XAxis
|
|
dataKey='time' tickFormatter={dateFormatter(when, from, to)} name={xAxisName(when, from, to)}
|
|
tick={{ fill: 'var(--theme-grey)' }}
|
|
/>
|
|
<YAxis tickFormatter={abbrNum} tick={{ fill: 'var(--theme-grey)' }} />
|
|
<Tooltip labelFormatter={labelFormatter(when, from, to)} contentStyle={{ color: 'var(--bs-body-color)', backgroundColor: 'var(--bs-body-bg)' }} />
|
|
<Legend />
|
|
{Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) =>
|
|
<Area key={v} type='monotone' dataKey={v} name={v} stackId='1' stroke={getColor(i)} fill={getColor(i)} />)}
|
|
</AreaChart>
|
|
</ResponsiveContainer>
|
|
)
|
|
}
|
|
|
|
export function WhenLineChart ({ data }) {
|
|
const router = useRouter()
|
|
if (!data || data.length === 0) {
|
|
return null
|
|
}
|
|
// transform data into expected shape
|
|
data = transformData(data)
|
|
// need to grab when
|
|
const when = router.query.when
|
|
const from = router.query.from
|
|
const to = router.query.to
|
|
|
|
return (
|
|
<ResponsiveContainer width='100%' height={300} minWidth={300}>
|
|
<LineChart
|
|
data={data}
|
|
margin={{
|
|
top: 5,
|
|
right: 5,
|
|
left: 0,
|
|
bottom: 0
|
|
}}
|
|
>
|
|
<XAxis
|
|
dataKey='time' tickFormatter={dateFormatter(when, from, to)} name={xAxisName(when, from, to)}
|
|
tick={{ fill: 'var(--theme-grey)' }}
|
|
/>
|
|
<YAxis tickFormatter={abbrNum} tick={{ fill: 'var(--theme-grey)' }} />
|
|
<Tooltip labelFormatter={labelFormatter(when, from, to)} contentStyle={{ color: 'var(--bs-body-color)', backgroundColor: 'var(--bs-body-bg)' }} />
|
|
<Legend />
|
|
{Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) =>
|
|
<Line key={v} type='monotone' dataKey={v} name={v} stroke={getColor(i)} fill={getColor(i)} />)}
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
)
|
|
}
|
|
|
|
export function WhenComposedChart ({
|
|
data,
|
|
lineNames = [], lineAxis = 'left',
|
|
areaNames = [], areaAxis = 'left',
|
|
barNames = [], barAxis = 'left', barStackId
|
|
}) {
|
|
const router = useRouter()
|
|
if (!data || data.length === 0) {
|
|
return null
|
|
}
|
|
// transform data into expected shape
|
|
data = transformData(data)
|
|
// need to grab when
|
|
const when = router.query.when
|
|
const from = router.query.from
|
|
const to = router.query.to
|
|
|
|
return (
|
|
<ResponsiveContainer width='100%' height={300} minWidth={300}>
|
|
<ComposedChart
|
|
data={data}
|
|
margin={{
|
|
top: 5,
|
|
right: 5,
|
|
left: 0,
|
|
bottom: 0
|
|
}}
|
|
>
|
|
<XAxis
|
|
dataKey='time' tickFormatter={dateFormatter(when, from, to)} name={xAxisName(when, from, to)}
|
|
tick={{ fill: 'var(--theme-grey)' }}
|
|
/>
|
|
<YAxis yAxisId='left' orientation='left' allowDecimals={false} stroke='var(--theme-grey)' tickFormatter={abbrNum} tick={{ fill: 'var(--theme-grey)' }} />
|
|
<YAxis yAxisId='right' orientation='right' allowDecimals={false} stroke='var(--theme-grey)' tickFormatter={abbrNum} tick={{ fill: 'var(--theme-grey)' }} />
|
|
<Tooltip labelFormatter={labelFormatter(when, from, to)} contentStyle={{ color: 'var(--bs-body-color)', backgroundColor: 'var(--bs-body-bg)' }} />
|
|
<Legend />
|
|
{barNames?.map((v, i) =>
|
|
<Bar yAxisId={barAxis} key={v} stackId={barStackId} type='monotone' dataKey={v} name={v} stroke={getColor(i)} fill={getColor(i)} />)}
|
|
{areaNames?.map((v, i) =>
|
|
<Area yAxisId={areaAxis} key={v} type='monotone' dataKey={v} name={v} stackId='1' stroke={getColor(barNames.length + i)} fill={getColor(barNames.length + i)} />)}
|
|
{lineNames?.map((v, i) =>
|
|
<Line yAxisId={lineAxis} key={v} type='monotone' dataKey={v} name={v} stackId='1' stroke={getColor(barNames.length + areaNames.length + i)} />)}
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
)
|
|
}
|
|
|
|
export function GrowthPieChart ({ data }) {
|
|
const nonZeroData = data.filter(d => d.value > 0)
|
|
|
|
return (
|
|
<ResponsiveContainer width='100%' height={250} minWidth={200}>
|
|
<PieChart margin={{ top: 5, right: 5, bottom: 5, left: 5 }}>
|
|
<Pie
|
|
dataKey='value'
|
|
isAnimationActive={false}
|
|
data={nonZeroData}
|
|
cx='50%'
|
|
cy='50%'
|
|
minAngle={5}
|
|
paddingAngle={0}
|
|
outerRadius={80}
|
|
fill='var(--bs-secondary)'
|
|
label
|
|
>
|
|
{
|
|
data.map((entry, index) => (
|
|
<Cell key={`cell-${index}`} fill={getColor(index)} />
|
|
))
|
|
}
|
|
</Pie>
|
|
<Tooltip />
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
)
|
|
}
|