ChartActiveDot.js
3 months ago
ChartCrosshair.js
3 months ago
ChartTooltip.js
3 months ago
EngagementAreaChart.js
3 months ago
RetentionTooltip.js
3 months ago
chartTheme.js
3 months ago
index.js
3 months ago
EngagementAreaChart.js
119 lines
| 1 | import { memo, useMemo } from "@wordpress/element"; |
| 2 | import { |
| 3 | ResponsiveContainer, |
| 4 | AreaChart, |
| 5 | Area, |
| 6 | CartesianGrid, |
| 7 | XAxis, |
| 8 | YAxis, |
| 9 | Tooltip, |
| 10 | } from "recharts"; |
| 11 | import { __ } from "@wordpress/i18n"; |
| 12 | import { humanSeconds, humanSecondsCompact, formatXAxis } from "../../utils"; |
| 13 | import { |
| 14 | CHART_COLOR, |
| 15 | GRID_COLOR, |
| 16 | AXIS_LABEL_COLOR, |
| 17 | GRADIENT_OPACITY, |
| 18 | STROKE_WIDTH, |
| 19 | ANIMATION_DURATION, |
| 20 | ANIMATION_EASING, |
| 21 | useChartGradientId, |
| 22 | } from "./chartTheme"; |
| 23 | import ChartActiveDot from "./ChartActiveDot"; |
| 24 | import ChartCrosshair from "./ChartCrosshair"; |
| 25 | import ChartTooltip from "./ChartTooltip"; |
| 26 | |
| 27 | const AXIS_TICK = { fontSize: "12px", fill: AXIS_LABEL_COLOR }; |
| 28 | const CHART_MARGIN = { top: 10, right: 10, bottom: 5, left: 0 }; |
| 29 | const X_AXIS_PADDING = { left: 15, right: 15 }; |
| 30 | const X_AXIS_LABEL_TARGET = 7; |
| 31 | const crosshairCursor = <ChartCrosshair />; |
| 32 | const activeDotElement = <ChartActiveDot />; |
| 33 | |
| 34 | // Recharts' "equidistantPreserveStart" decimates labels too aggressively |
| 35 | // for short ranges (e.g. a 7-day chart was rendering only 2 ticks). This |
| 36 | // computes a manual stride that aims for ~7 evenly-spaced labels no |
| 37 | // matter how many days are in the window — small ranges show every day, |
| 38 | // larger ones thin out without overlapping. |
| 39 | const computeXAxisInterval = (length) => |
| 40 | Math.max(0, Math.ceil(length / X_AXIS_LABEL_TARGET) - 1); |
| 41 | |
| 42 | const EngagementAreaChart = memo(({ data, isWatchTime }) => { |
| 43 | const gradientId = useChartGradientId("engagement"); |
| 44 | const xAxisInterval = useMemo( |
| 45 | () => computeXAxisInterval(data?.length || 0), |
| 46 | [data?.length] |
| 47 | ); |
| 48 | |
| 49 | const tooltipContent = useMemo( |
| 50 | () => ( |
| 51 | <ChartTooltip |
| 52 | metricLabel={ |
| 53 | isWatchTime |
| 54 | ? __("Watch Time", "presto-player") |
| 55 | : __("Views", "presto-player") |
| 56 | } |
| 57 | formatter={isWatchTime ? humanSeconds : undefined} |
| 58 | /> |
| 59 | ), |
| 60 | [isWatchTime] |
| 61 | ); |
| 62 | |
| 63 | return ( |
| 64 | <div className="w-full h-full min-h-[248px] min-[1427px]:min-h-[228px] [&_.recharts-surface]:cursor-crosshair"> |
| 65 | <ResponsiveContainer width="100%" height="100%"> |
| 66 | <AreaChart data={data} margin={CHART_MARGIN}> |
| 67 | <defs> |
| 68 | <linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1"> |
| 69 | <stop offset="0%" stopColor={CHART_COLOR} stopOpacity={GRADIENT_OPACITY} /> |
| 70 | <stop offset="100%" stopColor={CHART_COLOR} stopOpacity={0} /> |
| 71 | </linearGradient> |
| 72 | </defs> |
| 73 | <CartesianGrid |
| 74 | horizontal={true} |
| 75 | vertical={false} |
| 76 | stroke={GRID_COLOR} |
| 77 | strokeWidth={1} |
| 78 | /> |
| 79 | <XAxis |
| 80 | dataKey="month" |
| 81 | tickLine={false} |
| 82 | axisLine={false} |
| 83 | tickMargin={8} |
| 84 | tickFormatter={formatXAxis} |
| 85 | tick={AXIS_TICK} |
| 86 | interval={xAxisInterval} |
| 87 | padding={X_AXIS_PADDING} |
| 88 | /> |
| 89 | <YAxis |
| 90 | tickLine={false} |
| 91 | axisLine={false} |
| 92 | tickMargin={4} |
| 93 | width={40} |
| 94 | tick={AXIS_TICK} |
| 95 | allowDecimals={false} |
| 96 | tickFormatter={isWatchTime ? humanSecondsCompact : undefined} |
| 97 | /> |
| 98 | <Tooltip content={tooltipContent} cursor={crosshairCursor} /> |
| 99 | <Area |
| 100 | type="monotone" |
| 101 | dataKey="count" |
| 102 | stroke={CHART_COLOR} |
| 103 | strokeWidth={STROKE_WIDTH} |
| 104 | strokeLinecap="round" |
| 105 | fill={`url(#${gradientId})`} |
| 106 | dot={false} |
| 107 | activeDot={activeDotElement} |
| 108 | isAnimationActive={true} |
| 109 | animationDuration={ANIMATION_DURATION} |
| 110 | animationEasing={ANIMATION_EASING} |
| 111 | /> |
| 112 | </AreaChart> |
| 113 | </ResponsiveContainer> |
| 114 | </div> |
| 115 | ); |
| 116 | }); |
| 117 | |
| 118 | export default EngagementAreaChart; |
| 119 |