UNPKG

@graphique/geom-area

Version:

For area charts, stacked area charts, or streamgraphs

1 lines 73.6 kB
{"version":3,"sources":["../src/index.ts","../src/geomArea.tsx","../src/types/index.ts","../src/tooltip/Tooltip.tsx","../src/tooltip/DefaultTooltip.tsx","../src/tooltip/LineMarker.tsx","../src/hooks/useHandleSpecificationErrors.ts","../src/legend/AppearanceLegend.tsx","../src/legend/CategoricalLegend.tsx"],"sourcesContent":["export { GeomArea, GeomProps } from './geomArea'\nexport { Legend, LegendProps } from './legend'\nexport { GeomAes, Position } from './types'\n","import React, {\n useEffect,\n useMemo,\n SVGAttributes,\n useState,\n useCallback,\n} from 'react'\nimport {\n useGG,\n themeState,\n generateID,\n EventArea,\n isDate,\n widen,\n yScaleState,\n zoomState,\n defaultScheme,\n fillScaleState,\n strokeScaleState,\n VisualEncodingTypes,\n BrushAction,\n} from '@graphique/graphique'\nimport { Animate } from 'react-move'\nimport { easeCubic } from 'd3-ease'\nimport { scaleOrdinal } from 'd3-scale'\nimport { interpolate } from 'd3-interpolate'\nimport { interpolatePath } from 'd3-interpolate-path'\nimport {\n area,\n CurveFactory,\n curveLinear,\n stack,\n stackOffsetDiverging,\n stackOffsetWiggle,\n stackOffsetExpand,\n stackOrderNone,\n} from 'd3-shape'\nimport { min, max, sum, extent } from 'd3-array'\nimport { useAtom } from 'jotai'\nimport { GeomAes, Position, StackedArea } from './types'\nimport { LineMarker, Tooltip } from './tooltip'\nimport { useHandleSpecificationErrors } from './hooks/useHandleSpecificationErrors'\n\nexport interface GeomProps<Datum> {\n /**\n * **data used by this Geom**\n *\n * This will overwrite top-level `data` passed to `GG` as it relates to mappings defined in `aes`.\n */\n data?: Datum[]\n /**\n * **functional mapping applied to `data` for this Geom**\n *\n * This extends the top-level `aes` passed to `GG`. Any repeated mappings defined here will take precedence within the Geom.\n */\n aes?: GeomAes<Datum>\n /** attributes passed to the underlying SVG elements */\n attr?: SVGAttributes<SVGPathElement>\n /** determines how grouped elements are positioned relative to each other (_default_: `Position.IDENTITY`) */\n position?: Position\n /** should this Geom have a tooltip associated with it (_default_: `true`) */\n showTooltip?: boolean\n /** determines what happens when brushing (clicking and dragging) over the drawing area */\n brushAction?: BrushAction\n /** [d3 curve](https://d3js.org/d3-shape/curve) factory imported from `d3-shape` (_default_: `curveLinear`) */\n curve?: CurveFactory\n /** should this Geom have a line marker for its focused data (_default_: `true`) */\n showLineMarker?: boolean\n /** radius in px of the line marker's points (_default_: `3.5`) */\n markerRadius?: number\n /** stroke color of the line marker's points (_default_: `\"#ffffff\"`) */\n markerStroke?: string\n /** callback called for mousemove events on the drawing area when focusing data */\n onDatumFocus?: (data: Datum[], index: number[]) => void\n /** callback called for click events on the drawing area when selecting focused data */\n onDatumSelection?: (data: Datum[], index: number[]) => void\n /** callback called for mouseleave events on the drawing area */\n onExit?: () => void\n /** should elements enter/update/exit with animated transitions (_default_: `true`) */\n isAnimated?: boolean\n}\n\nconst GeomArea = <Datum,>({\n data: localData,\n aes: localAes,\n brushAction,\n curve = curveLinear,\n onDatumFocus,\n onDatumSelection,\n onExit,\n attr,\n showTooltip = true,\n showLineMarker = true,\n markerRadius = 3.5,\n markerStroke = '#fff',\n position = Position.IDENTITY,\n isAnimated = true,\n}: GeomProps<Datum>) => {\n const { ggState } = useGG<Datum>() || {}\n const { id, data, aes, scales, copiedScales } = ggState || {}\n const [theme, setTheme] = useAtom(themeState)\n const [{ values: fillScaleColors, domain: fillDomain }] =\n useAtom(fillScaleState)\n const [{ values: strokeScaleColors, domain: strokeDomain }] =\n useAtom(strokeScaleState)\n const [, setYScale] = useAtom(yScaleState)\n const [{ xDomain: xZoomDomain, yDomain: yZoomDomain }] = useAtom(zoomState)\n\n const geomAes = useMemo(() => {\n if (localAes) {\n return {\n ...aes,\n ...localAes,\n }\n }\n return aes as GeomAes<Datum>\n }, [aes, localAes])\n\n const geomData = localData || data\n\n const zoomedData = useMemo(\n () =>\n geomData?.filter((d) => {\n if (!xZoomDomain?.current) return true\n\n const xVal = geomAes?.x?.(d)\n return (\n xVal &&\n xVal >= xZoomDomain?.current?.[0] &&\n xVal <= xZoomDomain?.current?.[1]\n )\n }),\n [geomData, xZoomDomain?.current, geomAes.x],\n )\n\n const allXUndefined = useMemo(() => {\n const undefinedX = geomData\n ? geomData.filter(\n (d) =>\n geomAes?.x &&\n (geomAes?.x(d) === null ||\n typeof geomAes?.x(d) === 'undefined' ||\n Number.isNaN(geomAes.x(d)?.valueOf()) ||\n (isDate(geomAes.x(d)) && geomAes.x(d)?.valueOf() === 0)),\n )\n : []\n return geomData && undefinedX.length === geomData.length\n }, [geomData, geomAes])\n\n const allYUndefined = useMemo(() => {\n const undefinedY = geomData\n ? geomData.filter(\n (d) =>\n (!geomAes.y0 &&\n !geomAes.y1 &&\n geomAes?.y &&\n (geomAes.y(d) === null ||\n typeof geomAes.y(d) === 'undefined' ||\n Number.isNaN(geomAes.y(d)?.valueOf()))) ||\n (geomAes.y0 &&\n (geomAes.y0(d) === null ||\n typeof geomAes.y0(d) === 'undefined' ||\n Number.isNaN(geomAes.y0(d)?.valueOf()))) ||\n (geomAes.y1 &&\n (geomAes.y1(d) === null ||\n typeof geomAes.y1(d) === 'undefined' ||\n Number.isNaN(geomAes.y1(d)?.valueOf()))),\n )\n : []\n return geomData && undefinedY.length === geomData.length\n }, [geomData])\n\n const { defaultFill, defaultStroke, animationDuration: duration } = theme\n\n const geomID = useMemo(() => generateID(), [])\n\n const [firstRender, setFirstRender] = useState(true)\n\n useEffect(() => {\n const timeout = setTimeout(() => setFirstRender(false), 0)\n return () => clearTimeout(timeout)\n }, [])\n\n const shouldStack = useMemo(\n () => [Position.STACK, Position.FILL, Position.STREAM].includes(position),\n [position],\n )\n\n // draw an area for each registered group\n const group = useMemo(\n () =>\n geomAes?.group ?? geomAes?.fill\n ? geomAes?.group ?? geomAes?.fill\n : scales?.groupAccessor ?? (() => '__group'),\n [geomAes],\n )\n\n const groups = useMemo(() => {\n if (group) {\n const g = Array.from(new Set(geomData?.map(group))) as string[]\n\n return g\n }\n\n return undefined\n }, [geomData, group])\n\n const fillGroups = useMemo(\n () => copiedScales?.fillScale?.domain(),\n [copiedScales],\n )\n\n const strokeGroups = useMemo(\n () => copiedScales?.strokeScale?.domain(),\n [copiedScales],\n )\n\n const x = useMemo(\n () => (d: Datum) =>\n geomAes?.x && scales?.xScale && scales.xScale(geomAes?.x(d)),\n [scales, geomAes],\n )\n const y = useMemo(\n () => (d: Datum) =>\n geomAes?.y && scales?.yScale && scales.yScale(geomAes?.y(d)),\n [scales, geomAes],\n )\n\n const stackOffset = useMemo(() => {\n if (position === Position.FILL) return stackOffsetExpand\n if (position === Position.STREAM) return stackOffsetWiggle\n\n return stackOffsetDiverging\n }, [position])\n\n const stackOrder = useMemo(() => stackOrderNone, [position])\n\n // error checking for missing y-related aes\n useHandleSpecificationErrors({ geomAes, position, shouldStack })\n\n const geomFillScale = useMemo(() => {\n if (groups && geomAes.fill) {\n return scaleOrdinal()\n .domain(fillDomain || fillGroups || groups)\n .range(\n (fillScaleColors as string[]) || defaultScheme,\n ) as VisualEncodingTypes\n }\n return undefined\n }, [geomAes, fillScaleColors, fillDomain])\n\n const geomStrokeScale = useMemo(() => {\n if (groups && geomAes.stroke) {\n return scaleOrdinal()\n .domain(strokeDomain || strokeGroups || groups)\n .range(\n (strokeScaleColors as string[]) || defaultScheme,\n ) as VisualEncodingTypes\n }\n return undefined\n }, [geomAes, strokeDomain])\n\n const stackedData = useMemo(() => {\n if (\n geomData &&\n geomAes?.x &&\n geomAes?.y &&\n // geomFillScale &&\n group &&\n groups\n ) {\n const stacked = stack()\n .keys([...(fillDomain || groups)]?.reverse())\n .order(stackOrder)\n .offset(stackOffset)(widen(geomData, geomAes.x, group, geomAes.y))\n\n const formattedStacked = stacked\n .map((s) => {\n const thisGroup = s.key\n return s\n .map((thisStack) => ({\n group: thisGroup,\n x: thisStack.data.key,\n y0: thisStack[0],\n y1: thisStack[1],\n }))\n .flat()\n })\n .flat()\n .sort((a, b) => a.x - b.x)\n\n return formattedStacked\n }\n return null\n }, [geomData, geomAes, stackOffset, stackOrder])\n\n const isStream = useMemo(\n () => position === Position.STREAM && !!stackedData,\n [stackedData, position],\n )\n\n const getYValExtent = useCallback(\n (areaData: Datum[]) => {\n // reset the yScale based on position\n const existingYExtent = [0, 1]\n\n let resolvedYExtent = [0, 1]\n if (!group && !groups && !geomAes.y0 && !geomAes.y1)\n resolvedYExtent = [0, existingYExtent[1]]\n if (!group && !groups) resolvedYExtent = existingYExtent\n if (group && groups && areaData && geomAes?.x) {\n const groupYMaximums = groups.map((g) =>\n max(\n areaData.filter((d) => group(d) === g),\n (d) => {\n const thisYAcc = !shouldStack\n ? geomAes.y1 || geomAes.y || (() => undefined)\n : geomAes.y || (() => undefined)\n return thisYAcc(d) as number\n },\n ),\n )\n\n const groupYMinimums = groups.map((g) =>\n min(\n areaData.filter((d) => group(d) === g),\n (d) => {\n const thisYAcc = geomAes.y0 || (() => undefined)\n return thisYAcc(d) as number\n },\n ),\n )\n\n if ([Position.STACK].includes(position)) {\n const totalGroupYMaximums = max([\n sum(groupYMaximums),\n existingYExtent[1],\n ])\n\n return [0, totalGroupYMaximums]\n }\n if (position === Position.FILL) return [0, 1]\n\n if (isStream) {\n const zoomedStackData = xZoomDomain?.current\n ? stackedData!.filter(\n (d) =>\n d.x >= xZoomDomain?.current?.[0] &&\n d.x <= xZoomDomain?.current?.[1],\n )\n : stackedData!\n\n const groupYMinimum = min(zoomedStackData, (d) => d.y0)\n const groupYMaximum = max(zoomedStackData, (d) => d.y1)\n\n return [groupYMinimum, groupYMaximum]\n }\n\n if (position === Position.IDENTITY) {\n const identityYVals: (number | undefined)[][] | undefined =\n areaData?.map((d) => {\n const yVal = geomAes?.y ? (geomAes.y(d) as number) : undefined\n const y0Val = geomAes?.y0 ? (geomAes.y0(d) as number) : undefined\n const y1Val = geomAes?.y1 ? (geomAes.y1(d) as number) : undefined\n return [yVal, y0Val, y1Val]\n })\n\n const yExtent = identityYVals\n ? (extent(identityYVals.flat() as number[]) as [number, number])\n : [0, 1]\n\n const yMin =\n geomAes?.y0 && geomAes?.y1 && !geomAes.y\n ? min([groupYMinimums as number[]].flat())\n : min([0, yExtent[0] as number])\n\n const yMax = max([\n !geomAes.y0 && !geomAes.y1 && 0,\n max(\n [groupYMaximums as number[], existingYExtent[1] as number].flat(),\n ),\n ] as number[])\n\n resolvedYExtent = [yMin, yMax] as [number, number]\n }\n }\n\n return resolvedYExtent\n },\n [\n position,\n geomAes,\n shouldStack,\n groups,\n isStream,\n stackedData,\n xZoomDomain?.current,\n ],\n )\n\n const yValExtent = useMemo(() => {\n if (yZoomDomain?.original && !yZoomDomain?.current)\n return yZoomDomain?.original\n\n if (yZoomDomain?.current)\n return yZoomDomain.current\n\n return zoomedData ? getYValExtent(zoomedData) : [0, 1]\n }, [zoomedData, yZoomDomain])\n\n useEffect(() => {\n setYScale((prev) => ({\n ...prev,\n domain: yValExtent,\n }))\n }, [yValExtent])\n\n const y0 = useMemo(\n () => (d: Datum) =>\n geomAes?.y0 && scales?.yScale && scales.yScale(geomAes.y0(d)),\n [scales, geomAes],\n )\n\n const y1 = useMemo(\n () => (d: Datum) =>\n geomAes?.y1 && scales?.yScale && scales.yScale(geomAes.y1(d)),\n [scales, geomAes],\n )\n\n const drawStackArea = useMemo(\n () =>\n area<StackedArea>()\n .x((d) => scales?.xScale(d.x) as number)\n .y0((d) => scales?.yScale?.(d.y0) as number)\n .y1((d) => scales?.yScale?.(d.y1) as number)\n .defined((d) => {\n const dataVal = d\n const xVal = isDate(dataVal.x) ? dataVal.x.valueOf() : dataVal.x\n\n const areDefined =\n typeof xVal !== 'undefined' &&\n typeof dataVal.y0 !== 'undefined' &&\n typeof dataVal.y1 !== 'undefined'\n\n const areNumbers =\n !Number.isNaN(xVal) &&\n !Number.isNaN(dataVal.y0) &&\n !Number.isNaN(dataVal.y1)\n\n return areDefined && areNumbers\n })\n .curve(curve),\n [curve, scales, geomAes, localAes, yValExtent],\n )\n\n const drawArea = useMemo(\n () =>\n area<Datum>()\n .x((d) => x(d) as number)\n .y0((d) => (localAes?.y0 ? (y0(d) as number) : scales?.yScale(0)))\n .y1((d) => (localAes?.y1 ? (y1(d) as number) : (y(d) as number)))\n .defined((d) => {\n const xVal =\n geomAes.x &&\n (isDate(geomAes.x(d)) ? geomAes.x(d)?.valueOf() : geomAes.x(d))\n\n const y0Val =\n geomAes.y0 && geomAes.y1 ? geomAes.y0(d) : geomAes.y && geomAes.y(d)\n const y1Val =\n geomAes.y0 && geomAes.y1 ? geomAes.y1(d) : geomAes.y && geomAes.y(d)\n\n const areDefined =\n typeof xVal !== 'undefined' &&\n typeof y0Val !== 'undefined' &&\n y0Val !== null &&\n typeof y1Val !== 'undefined' &&\n y1Val !== null\n\n const areNumbers =\n !Number.isNaN(xVal) && !Number.isNaN(y0Val) && !Number.isNaN(y1Val)\n\n return areDefined && areNumbers\n })\n .curve(curve || curveLinear),\n [curve, geomAes, localAes, scales, yValExtent],\n )\n\n // merge GG-level scales with Geom-level scales\n const baseAttr: SVGAttributes<SVGPathElement> = {\n fillOpacity: 1,\n strokeOpacity: 1,\n strokeWidth: 0,\n }\n\n const geomAttr: SVGAttributes<SVGPathElement> = {\n ...baseAttr,\n ...attr,\n }\n\n const resolvedOpacity = useMemo(\n () =>\n attr?.style?.fillOpacity ||\n attr?.style?.opacity ||\n attr?.opacity ||\n attr?.fillOpacity,\n [attr],\n )\n\n useEffect(() => {\n setTheme((prev) => ({\n ...prev,\n geoms: {\n ...prev.geoms,\n area: {\n position,\n fillOpacity: resolvedOpacity,\n stroke: geomAttr.stroke,\n fill: geomAttr.fill,\n fillScale: geomFillScale,\n strokeScale: geomStrokeScale,\n groupAccessor: group ?? geomAes?.fill ?? geomAes?.group,\n usableGroups: groups,\n y0,\n y1,\n strokeWidth: geomAttr?.style?.strokeWidth || geomAttr?.strokeWidth,\n strokeOpacity:\n geomAttr?.style?.strokeOpacity || geomAttr?.strokeOpacity,\n strokeDasharray:\n geomAttr?.style?.strokeDasharray || geomAttr?.strokeDasharray,\n },\n },\n }))\n }, [\n resolvedOpacity,\n setTheme,\n attr,\n position,\n shouldStack,\n group,\n geomFillScale,\n geomStrokeScale,\n ])\n\n const isAbleToDrawArea = useMemo(\n () => (shouldStack ? !!stackedData : true),\n [stackedData, shouldStack],\n )\n\n const getStackedData = useMemo(\n () => (g: unknown) => {\n const thisStack = stackedData\n ? stackedData.filter((sd) => sd.group === g)\n : []\n\n return thisStack\n },\n [stackedData, scales, geomAes, position],\n )\n\n // map through groups to draw an area for each group\n return !firstRender &&\n !allXUndefined &&\n !allYUndefined &&\n isAbleToDrawArea ? (\n <>\n {geomData && groups && group ? (\n groups.map((g) => {\n const groupData = geomData.filter((d) => group(d) === g)\n const groupStack = getStackedData(g)\n\n const thisFillGroups =\n geomFillScale && geomAes?.fill\n ? Array.from(\n new Set(\n groupData.map((gd) => geomAes.fill && geomAes.fill(gd)),\n ),\n )\n : undefined\n\n let thisFill =\n geomAttr.fill ||\n (geomFillScale && geomFillScale(g)) ||\n (copiedScales?.fillScale ? copiedScales.fillScale(g) : defaultFill)\n\n if (thisFillGroups && geomFillScale) {\n thisFillGroups.forEach((fg) => {\n thisFill = geomFillScale(fg)\n })\n }\n\n const thisStrokeGroups =\n geomStrokeScale && geomAes?.stroke\n ? Array.from(\n new Set(\n groupData.map((gd) => geomAes.stroke && geomAes.stroke(gd)),\n ),\n )\n : undefined\n\n let thisStroke =\n geomAttr.stroke ||\n (geomStrokeScale && geomStrokeScale(g)) ||\n (copiedScales?.strokeScale\n ? copiedScales.strokeScale(g)\n : defaultStroke)\n\n if (thisStrokeGroups && geomStrokeScale) {\n thisStrokeGroups.forEach((fg) => {\n thisStroke = geomStrokeScale(fg)\n })\n }\n\n const thisDasharray =\n geomAttr.strokeDasharray ||\n (copiedScales?.strokeDasharrayScale\n ? copiedScales.strokeDasharrayScale(g)\n : undefined)\n\n return (\n <Animate\n key={`${geomID}-${g}`}\n start={{\n path: shouldStack\n ? drawStackArea(groupStack)\n : drawArea(groupData),\n fill: 'transparent',\n stroke: 'transparent',\n strokeOpacity: 0,\n fillOpacity: 0,\n }}\n enter={() => {\n const path = shouldStack\n ? drawStackArea(groupStack)\n : drawArea(groupData)\n\n return {\n path: isAnimated ? [path] : path,\n fill: thisFill,\n stroke: thisStroke,\n fillOpacity: isAnimated\n ? [geomAttr.fillOpacity]\n : geomAttr.fillOpacity,\n strokeOpacity: isAnimated\n ? [geomAttr.strokeOpacity]\n : geomAttr.strokeOpacity,\n timing: { duration, ease: easeCubic },\n }\n }}\n update={() => {\n const path = shouldStack\n ? drawStackArea(groupStack)\n : drawArea(groupData)\n\n return {\n path: isAnimated ? [path] : path,\n fill: thisFill,\n stroke: thisStroke,\n fillOpacity: isAnimated\n ? [geomAttr.fillOpacity]\n : geomAttr.fillOpacity,\n strokeOpacity: isAnimated\n ? [geomAttr.strokeOpacity]\n : geomAttr.strokeOpacity,\n timing: { duration, ease: easeCubic },\n }\n }}\n leave={() => ({\n fill: isAnimated ? ['transparent'] : 'transparent',\n stroke: isAnimated ? ['transparent'] : 'transparent',\n timing: { duration, ease: easeCubic },\n })}\n interpolation={(begValue, endValue, a) => {\n if (a === 'path') {\n return interpolatePath(begValue, endValue)\n }\n return interpolate(begValue, endValue)\n }}\n >\n {(state) => (\n <path\n // eslint-disable-next-line react/jsx-props-no-spreading\n {...attr}\n d={state.path}\n fill={state.fill}\n fillOpacity={state.fillOpacity}\n stroke={state.stroke}\n strokeOpacity={state.strokeOpacity}\n strokeWidth={geomAttr.strokeWidth}\n strokeDasharray={thisDasharray}\n style={{\n pointerEvents: 'none',\n ...geomAttr?.style,\n }}\n data-testid=\"__gg_geom_area\"\n clipPath={`url(#__gg_canvas_${id})`}\n />\n )}\n </Animate>\n )\n })\n ) : (\n <></>\n )}\n {(showTooltip || brushAction) && (\n <>\n <EventArea\n data={geomData}\n aes={geomAes}\n group=\"x\"\n x={x}\n y={() => 0}\n onDatumFocus={onDatumFocus}\n onMouseLeave={() => {\n if (onExit) onExit()\n }}\n onClick={\n onDatumSelection\n ? ({ d, i }: { d: Datum[]; i: number[] }) => {\n onDatumSelection(d, i)\n }\n : undefined\n }\n showTooltip={showTooltip}\n brushAction={brushAction}\n customYExtent={yValExtent}\n getYValExtent={getYValExtent}\n />\n {showTooltip && (\n <>\n {showLineMarker && (\n <LineMarker\n x={x}\n y={y}\n y0={y0}\n y1={y1}\n aes={geomAes}\n markerRadius={markerRadius}\n markerStroke={markerStroke}\n stackedData={stackedData}\n position={position}\n />\n )}\n <Tooltip\n x={x}\n y={y}\n y0={y0}\n y1={y1}\n aes={geomAes}\n geomID={geomID}\n position={position}\n />\n </>\n )}\n </>\n )}\n </>\n ) : null\n}\n\nGeomArea.displayName = 'GeomArea'\nexport { GeomArea }\n","import { DataValue, Aes } from '@graphique/graphique'\n\nexport type GeomAes<Datum> = Omit<Aes<Datum>, 'x' | 'size'> & {\n x?: DataValue<Datum>\n /** a functional mapping to `data` representing an initial **y** value */\n y0?: DataValue<Datum>\n /** a functional mapping to `data` representing a secondary **y** value */\n y1?: DataValue<Datum>\n}\n\nexport enum Position {\n /** groups are overlaid directly \"on top\" of each other (no effect on y scale) */\n IDENTITY = 'identity',\n /** groups are stacked vertically after any previous groups (additive effect on y scale) */\n STACK = 'stack',\n /** groups are stacked vertically as proportion of group total after any previous groups (y scale domain set to [0, 1]) */\n FILL = 'fill',\n /** groups are stacked vertically in a streamgraph (y scale domain set to a relatively meaningless/uninterpretable domain based on streamgraph layout) */\n STREAM = 'stream',\n}\n\nexport type StackedArea = {\n x: number\n group: string\n y0: number\n y1: number\n}\n","import React, { useMemo } from 'react'\nimport {\n useGG,\n tooltipState,\n themeState,\n TooltipContent,\n XTooltip,\n YTooltip,\n TooltipContainer,\n TooltipProps,\n TooltipPosition,\n} from '@graphique/graphique'\nimport { useAtom } from 'jotai'\nimport { mean, sum, min, max } from 'd3-array'\nimport { DefaultTooltip } from './DefaultTooltip'\nimport { Position, type GeomAes } from '../types'\n\nexport { LineMarker } from './LineMarker'\n\ninterface Props<Datum> {\n x: (d: Datum) => number | undefined\n y: (d: Datum) => number | undefined\n y0: (d: Datum) => number | undefined\n y1: (d: Datum) => number | undefined\n aes: GeomAes<Datum>\n geomID: string\n position: Position\n}\n\nexport const Tooltip = <Datum,>({\n x,\n y,\n y0,\n y1,\n aes,\n geomID,\n position,\n}: Props<Datum>) => {\n const { ggState } = useGG<Datum>() || {}\n const { id, scales, copiedScales, width, height, margin } = ggState || {\n height: 0,\n }\n\n const [\n { datum, position: tooltipPosition, xAxis, xFormat, yFormat, content },\n ] = useAtom<TooltipProps<Datum>>(tooltipState)\n\n const [{ geoms, defaultStroke, defaultFill }] = useAtom(themeState)\n const { area } = geoms || {}\n\n const left = useMemo(\n () => min([datum && x(datum[0]), width - (margin?.right ?? 0)] as number[]),\n [datum, x, width],\n )\n\n const hasYVal = useMemo(\n () => datum?.some(y1) || datum?.some(y),\n [datum, y, y1],\n )\n\n const datumInGroups = useMemo(() => {\n const groups = scales?.groups\n\n return groups\n ? datum?.filter((d) => {\n const group = scales?.groupAccessor?.(d)\n const inGroups = scales?.groups?.includes(group as string)\n\n return inGroups && group\n })\n : datum\n }, [datum, geoms, scales])\n\n const group = useMemo(() => area?.groupAccessor || (() => '__group'), [area])\n\n const meanYVal = useMemo(\n () =>\n (datumInGroups &&\n mean(\n datumInGroups.map((d, i, stacks) => {\n let thisYCoord\n\n // stacked area (sum)\n if (position === Position.STACK) {\n const yTotal = stacks\n .slice(0, i + 1)\n // @ts-ignore\n .map(aes.y)\n .reduce((a, b) => (a as number) + (b as number), 0) as number\n\n thisYCoord = scales?.yScale(yTotal)\n } else if (position === Position.FILL && aes?.y) {\n const yTotal = stacks\n .slice(0, i + 1)\n .map(\n (s) =>\n // @ts-ignore\n aes.y(s) / sum(stacks, aes.y),\n )\n .reduce((a, b) => (a as number) + (b as number), 0) as number\n\n thisYCoord = scales?.yScale(yTotal)\n } else if (aes.y0 && aes.y1) {\n thisYCoord = mean([y0(d), y1(d)] as [number, number])\n } else {\n thisYCoord = y(d)\n }\n return thisYCoord\n }),\n )) ||\n 0,\n [datumInGroups, y, y0, y1],\n )\n\n const cappedYVal = max([0, min([meanYVal, height]) as number]) as number\n\n const yVal = useMemo(\n () =>\n position === Position.STREAM\n ? (height - margin.top - margin.bottom) / 2\n : cappedYVal,\n [position, cappedYVal],\n )\n\n const xVal = useMemo(\n () => datum && datum[0] && aes?.x && aes.x(datum[0]),\n [datum, aes],\n )\n\n const fillDomain = copiedScales?.fillScale?.domain()\n\n const areaVals = useMemo(() => {\n const vals = datumInGroups\n ?.filter(\n (d) =>\n (aes?.y1 && typeof aes.y1(d) !== 'undefined' && aes.y1(d) !== null) ||\n (aes?.y && typeof aes.y(d) !== 'undefined' && aes.y(d) !== null),\n )\n .sort(\n (a, b) =>\n fillDomain!.indexOf(String(group(a))) -\n fillDomain!.indexOf(String(group(b))),\n )\n .map((md) => {\n const thisGroup = scales?.groupAccessor?.(md)\n const autoGrouped = thisGroup === '__group'\n\n let formattedY\n\n if (aes?.y) {\n formattedY = yFormat ? yFormat(aes.y(md)) : aes.y(md)\n }\n\n if (aes?.y1) {\n formattedY = yFormat ? yFormat(aes.y1(md)) : aes.y1(md)\n }\n\n const mark = (\n <svg width={15} height={15}>\n <rect\n transform=\"translate(1, 1)\"\n width={12}\n height={12}\n fill={\n area?.fill ||\n (area?.fillScale && area.fillScale(thisGroup)) ||\n (copiedScales?.fillScale\n ? copiedScales.fillScale(thisGroup)\n : defaultFill)\n }\n stroke={\n area?.stroke ||\n (area?.strokeScale && area.strokeScale(thisGroup)) ||\n (copiedScales?.strokeScale\n ? copiedScales.strokeScale(thisGroup)\n : undefined)\n }\n strokeDasharray={\n area?.strokeDasharray ||\n (copiedScales?.strokeDasharrayScale\n ? copiedScales.strokeDasharrayScale(thisGroup)\n : undefined)\n }\n strokeWidth={0.6}\n fillOpacity={area?.fillOpacity}\n strokeOpacity={area?.strokeOpacity}\n />\n </svg>\n )\n return {\n group: autoGrouped ? undefined : thisGroup,\n mark: thisGroup && !autoGrouped ? mark : undefined,\n datum,\n x: xVal,\n y: (aes?.y1 && aes?.y1(md)) ?? (aes?.y && aes.y(md)),\n formattedY,\n formattedX: xFormat ? xFormat(xVal) : String(xVal),\n }\n })\n return vals as TooltipContent<Datum>[]\n }, [\n datumInGroups,\n xVal,\n aes,\n yFormat,\n xFormat,\n copiedScales,\n geoms,\n defaultStroke,\n ])\n\n const tooltipValue = content ? (\n <div>{content(areaVals)}</div>\n ) : (\n <DefaultTooltip data={areaVals} hasXAxisTooltip={!!xAxis} geomID={geomID} />\n )\n\n return datum ? (\n <>\n {xAxis && margin && left && (\n <XTooltip\n id={id as string}\n left={left}\n top={-margin.bottom}\n value={\n typeof xAxis === 'boolean' ? (\n <TooltipContainer>{xFormat && xFormat(xVal)}</TooltipContainer>\n ) : (\n xAxis(xVal)\n )\n }\n />\n )}\n {left && hasYVal && (\n <YTooltip\n id={id as string}\n left={left}\n top={\n tooltipPosition === TooltipPosition.DATA\n ? -(height - yVal)\n : -height\n }\n value={tooltipValue}\n wait\n />\n )}\n </>\n ) : null\n}\n","import React from 'react'\nimport {\n TooltipContent,\n TooltipContainer,\n formatMissing,\n themeState,\n} from '@graphique/graphique'\nimport { useAtom } from 'jotai'\n\nexport interface DefaultTooltipProps<Datum> {\n data: TooltipContent<Datum>[]\n hasXAxisTooltip?: boolean\n geomID: string\n}\n\nexport const DefaultTooltip = <Datum,>({\n data,\n hasXAxisTooltip,\n geomID,\n}: DefaultTooltipProps<Datum>) => {\n const xVal = data && data[0] ? data[0]?.formattedX : undefined\n\n const [{ tooltip }] = useAtom(themeState)\n\n return data ? (\n <TooltipContainer>\n {!hasXAxisTooltip && xVal && (\n <div\n style={{\n marginTop: 2,\n marginBottom: data.length === 1 ? 2 : 6,\n fontSize: tooltip?.xLabel?.fontSize || tooltip?.font?.size,\n color: '#555',\n }}\n >\n {xVal}\n </div>\n )}\n {data.map((d, i: number) => {\n const formattedGroup = formatMissing(d.group)\n return (\n <div\n key={`group-tooltip-${\n d.label || formattedGroup\n }-${geomID}-${i.toString()}`}\n >\n <div\n style={{\n marginTop: 3,\n marginBottom: data.length < i + 1 ? 3 : 2,\n display: 'flex',\n alignItems: 'center',\n }}\n >\n {(d.label || d.group) && (\n <>\n {d.mark}\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-end',\n marginLeft: 4,\n }}\n >\n <div style={{ marginRight: 5 }}>\n <span\n style={{\n fontSize:\n tooltip?.groupLabel?.fontSize ||\n tooltip?.font?.size,\n }}\n >\n {d.label || formattedGroup}{' '}\n </span>\n </div>\n </div>\n </>\n )}\n <div\n style={{\n fontWeight: 500,\n fontSize:\n tooltip?.yLabel?.fontSize ||\n (tooltip?.font?.size || 12) + 1,\n }}\n >\n {d.formattedY}\n </div>\n </div>\n </div>\n )\n })}\n </TooltipContainer>\n ) : null\n}\n","import React, { useMemo } from 'react'\nimport {\n useGG,\n tooltipState,\n themeState,\n formatMissing,\n} from '@graphique/graphique'\nimport { useAtom } from 'jotai'\nimport { min } from 'd3-array'\nimport { GeomAes, Position, StackedArea } from '../types'\n\nexport interface LineMarkerProps<Datum> {\n x: (d: Datum) => number | undefined\n y: (d: Datum) => number | undefined\n y0: (d: Datum) => number | undefined\n y1: (d: Datum) => number | undefined\n aes: GeomAes<Datum>\n markerRadius: number\n markerStroke: string\n stackedData: StackedArea[] | null\n position: Position\n}\n\nexport const LineMarker = <Datum,>({\n x,\n y,\n y0,\n y1,\n aes,\n markerRadius,\n markerStroke,\n stackedData,\n position,\n}: LineMarkerProps<Datum>) => {\n const { ggState } = useGG<Datum>() || {}\n const { scales, copiedScales, width, height, margin, id } = ggState || {}\n\n const [{ datum }] = useAtom(tooltipState)\n const [{ defaultFill, geoms }] = useAtom(themeState) || {}\n\n const { area } = geoms || {}\n\n const left = useMemo(\n () => min([datum && x(datum[0]), width - (margin?.right ?? 0)] as number[]),\n [datum, x, width],\n )\n\n const getY = useMemo(() => (aes?.y1 ? y1 : y), [y1, y, aes])\n\n return height && margin ? (\n <>\n {left && datum && (\n <g\n className={`__gg-tooltip-${id}`}\n style={{ transform: `translateX(${left}px)` }}\n >\n <line\n y1={height - margin.bottom}\n y2={margin.top}\n strokeDasharray={2}\n stroke=\"#888\"\n strokeWidth={1.5}\n style={{ pointerEvents: 'none' }}\n data-testid=\"__gg_geom_area_marker\"\n />\n {datum.map((d, i) => {\n const formattedGroup = copiedScales?.groupAccessor\n ? formatMissing(copiedScales?.groupAccessor(d))\n : '__group'\n\n const inGroups = scales?.groups\n ? scales.groups.includes(formattedGroup)\n : true\n\n let thisYCoord: any[] = [y(d)]\n\n // not in groups\n if (!inGroups) {\n thisYCoord = []\n } else if (stackedData) {\n const stackData = stackedData.find(\n (s) =>\n aes.x &&\n s.group === formattedGroup &&\n Number(s.x) === Number(aes.x(d)),\n )\n\n // stream\n if (position === Position.STREAM) {\n thisYCoord = [\n scales?.yScale(stackData?.y0),\n scales?.yScale(stackData?.y1),\n ]\n }\n\n // stacked/filled area\n if ([Position.STACK, Position.FILL].includes(position)) {\n thisYCoord = [scales?.yScale(stackData?.y1)]\n }\n } else if (aes.y0 && aes.y1) {\n thisYCoord = [y0(d), y1(d)]\n }\n\n const thisFill =\n area?.fill && !['none', 'transparent'].includes(area?.fill ?? '')\n ? area.fill\n : (area?.fillScale &&\n aes.fill &&\n area.fillScale(aes.fill(d))) ||\n (copiedScales?.fillScale && aes?.fill\n ? copiedScales.fillScale(aes.fill(d))\n : (area?.stroke ||\n (area?.strokeScale &&\n aes.stroke &&\n area.strokeScale(aes.stroke(d))) ||\n copiedScales?.strokeScale?.(aes?.stroke?.(d))) ??\n defaultFill)\n\n return thisYCoord?.map((c, j) => {\n const inRange =\n position === Position.FILL ||\n (c <= copiedScales?.yScale.range()[0] &&\n c >= copiedScales?.yScale.range()[1])\n\n return (\n typeof getY(d) !== 'undefined' &&\n inRange && (\n <g\n key={`marker-${\n copiedScales?.groups ? copiedScales.groups[i] : i\n }-${j.toString()}`}\n style={{ pointerEvents: 'none' }}\n >\n <circle\n r={markerRadius * 2 + 0.5}\n fill={thisFill}\n cy={c as number}\n fillOpacity={Math.min(\n 0.5,\n Math.max(\n ((area?.strokeOpacity || 0.9) as number) - 0.35,\n 0,\n ),\n )}\n />\n <circle\n r={markerRadius}\n fill={thisFill}\n stroke={markerStroke}\n strokeWidth={markerRadius / 3.2}\n cy={c as number}\n fillOpacity={area?.strokeOpacity || 0.9}\n strokeOpacity={0.7}\n data-testid=\"__gg_geom_area_marker_point\"\n />\n </g>\n )\n )\n })\n })}\n </g>\n )}\n </>\n ) : null\n}\n","import { GeomAes, Position } from '../types'\n\ninterface SpecificationErrorProps<Datum> {\n geomAes?: GeomAes<Datum>\n shouldStack?: boolean\n position?: Position\n}\n\nconst GEOM = 'GeomArea'\n\nconst useHandleSpecificationErrors = <Datum>({\n geomAes,\n shouldStack,\n position,\n}: SpecificationErrorProps<Datum>) => {\n if (shouldStack && !geomAes?.y) {\n throw new Error(\n `${GEOM}: aes.y is required when using position=\"${position}\"`,\n )\n }\n\n if (geomAes?.y1 && !geomAes?.y0) {\n throw new Error(\n `${GEOM}: aes.y1 can only be specified when combined with aes.y0`,\n )\n }\n\n if (geomAes?.y0 && !geomAes.y1 && !geomAes.y) {\n throw new Error(\n `${GEOM}: aes.y0 needs to be specified with aes.y1 or aes.y`,\n )\n }\n\n if (!geomAes?.y && !(geomAes?.y0 && geomAes?.y1)) {\n throw new Error(\n `${GEOM}: need to specify at least aes.y, or some combination of (aes.y, aes.y0) | (aes.y0, aes.y1)`,\n )\n }\n}\n\nexport { useHandleSpecificationErrors }\n","import React, { CSSProperties } from 'react'\nimport {\n useGG,\n themeState,\n IScale,\n LegendOrientation,\n} from '@graphique/graphique'\nimport { useAtom } from 'jotai'\nimport { CategoricalLegend } from './CategoricalLegend'\n\nexport interface LegendProps {\n /** title of legend */\n title?: React.ReactNode\n /** determines vertical/horizontal orientation of legend members (_default_: `LegendOrientation.V`) */\n orientation?: LegendOrientation\n /** function for formatting legend member labels */\n format?: (v: string, i?: number) => string\n /** callback called for click events on legend members */\n onSelection?: (v: string) => void\n /** additional styles passed to legend container */\n style?: CSSProperties\n}\n\nexport const Legend = <Datum,>({\n title,\n orientation = LegendOrientation.V,\n format,\n onSelection,\n style,\n}: LegendProps) => {\n const { ggState } = useGG<Datum>() || {}\n const { copiedScales, copiedData, aes } = ggState || {}\n const [{ font, geoms }] = useAtom(themeState)\n\n const { area } = geoms || {}\n\n const { groups } = copiedScales || {}\n\n const hasAppearanceAes =\n area?.fillScale || aes?.fill || aes?.stroke || aes?.strokeDasharray\n\n const { fontSize } = { ...style }\n\n return hasAppearanceAes ? (\n <div\n style={{\n marginTop: 12,\n fontFamily: font?.family,\n ...style,\n }}\n >\n {title}\n {copiedData &&\n (copiedScales || area?.fillScale) &&\n (groups || area?.usableGroups) ? (\n <CategoricalLegend\n legendData={copiedData}\n orientation={orientation}\n legendScales={\n {\n ...copiedScales,\n strokeScale: area ? area.strokeScale : copiedScales?.strokeScale,\n fillScale: area ? area.fillScale : copiedScales?.fillScale,\n } as IScale<Datum>\n }\n labelFormat={format}\n fontSize={fontSize}\n onSelection={onSelection}\n />\n ) : null}\n </div>\n ) : null\n}\n","import React, { useState, useEffect, useMemo } from 'react'\nimport {\n useGG,\n themeState,\n fillScaleState,\n formatMissing,\n IScale,\n LegendOrientation,\n} from '@graphique/graphique'\nimport { useAtom } from 'jotai'\n\nexport interface CategoricalLegendProps<Datum> {\n legendData: Datum[]\n legendScales: IScale<Datum>\n orientation?: LegendOrientation\n labelFormat?: (v: any, i: number) => string\n fontSize?: string | number\n onSelection?: (v: string) => void\n}\n\nexport const CategoricalLegend = <Datum,>({\n legendData,\n legendScales,\n orientation = LegendOrientation.V,\n labelFormat,\n fontSize = 12,\n onSelection,\n}: CategoricalLegendProps<Datum>) => {\n const [focused, setFocused] = useState<string[]>(\n legendScales.groups || legendScales.fillScale?.domain() || [],\n )\n\n const [{ geoms, defaultFill, legend }] = useAtom(themeState)\n const [{ domain }] = useAtom(fillScaleState)\n\n const legendGroups = useMemo(\n () =>\n (domain as string[]) ||\n legendScales.groups ||\n legendScales.fillScale?.domain(),\n [domain, legendScales],\n )\n\n const { ggState, updateData } = useGG<Datum>() || {}\n const { scales, data } = ggState || {}\n\n useEffect(() => {\n setFocused(scales?.groups || [])\n }, [scales, data])\n\n const getGroup = useMemo(() => scales.groupAccessor || undefined, [scales])\n\n const isHorizontal = orientation === LegendOrientation.H\n\n const toggleLegendGroup = (g: string) => {\n const prevFocused = focused\n let focusedGroups\n if (prevFocused.includes(g)) {\n if (prevFocused.length === 1) {\n focusedGroups = legendGroups as string[]\n } else {\n focusedGroups = prevFocused.filter((p) => p !== g)\n }\n } else {\n focusedGroups = [...prevFocused, g]\n }\n setFocused(focusedGroups)\n\n const includedGroups = Array.from(\n new Set(data?.map((d) => (getGroup ? getGroup(d) : undefined))),\n )\n\n if (onSelection) {\n onSelection(g)\n }\n if (data && updateData && getGroup) {\n let updatedData\n if (includedGroups.includes(g)) {\n if (includedGroups.length === 1) {\n updatedData = legendData\n } else {\n updatedData = data.filter((d) => getGroup(d) !== g)\n }\n } else {\n updatedData = legendData.filter(\n (d) => includedGroups.includes(getGroup(d)) || getGroup(d) === g,\n )\n }\n\n updateData(updatedData)\n }\n }\n\n return (\n <div\n style={{\n marginTop: 8,\n display: 'flex',\n flexDirection: !isHorizontal ? 'column' : 'row',\n flexWrap: 'wrap',\n alignItems: isHorizontal ? 'center' : undefined,\n }}\n >\n {geoms?.area &&\n legendGroups?.map((g: string, i, groups) => (\n <div\n key={g}\n style={{\n display: 'flex',\n alignItems: 'center',\n marginBottom: isHorizontal ? 6 : 2,\n }}\n >\n <div\n tabIndex={0}\n role=\"button\"\n style={{\n cursor: 'pointer',\n marginRight: i < groups.length - 1 && isHorizontal ? 12 : 2,\n fontSize,\n opacity: focused.includes(g) ? 1 : 0.5,\n transition: 'opacity 200ms',\n display: 'flex',\n alignItems: 'center',\n }}\n onKeyDown={(e) => {\n if (['Enter', ' '].includes(e.key)) {\n toggleLegendGroup(g)\n }\n }}\n onClick={() => toggleLegendGroup(g)}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <svg width={14} height={14}>\n <rect\n width={14}\n height={14}\n fill={\n geoms?.area?.fill ||\n (legendScales.fillScale\n ? legendScales.fillScale(g)\n : defaultFill)\n }\n stroke={\n geoms?.area?.stroke ||\n (legendScales.strokeScale\n ? legendScales.strokeScale(g)\n : 'none')\n }\n strokeWidth={1.8}\n fillOpacity={\n focused.includes(g) ? geoms?.area?.fillOpacity : 0.5\n }\n strokeOpacity={\n focused.includes(g) ? geoms?.area?.strokeOpacity : 0.5\n }\n style={{\n transition: 'fill-opacity 200ms',\n }}\n />\n </svg>\n </div>\n <div\n style={{\n marginLeft: 4,\n fontSize,\n color: legend?.labelColor ?? 'currentcolor',\n }}\n >\n {labelFormat ? labelFormat(g, i) : formatMissing(g)}\n </div>\n </div>\n </div>\n ))}\n </div>\n )\n}\n"],"mappings":"+kBAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,cAAAE,GAAA,WAAAC,GAAA,aAAAC,KAAA,eAAAC,GAAAL,ICAA,IAAAM,EAMO,uBACPC,EAcO,gCACPC,GAAwB,sBACxBC,GAA0B,mBAC1BC,GAA6B,oBAC7BC,GAA4B,0BAC5BC,GAAgC,+BAChCC,EASO,oBACPC,EAAsC,oBACtCC,GAAwB,iBC5BjB,IAAKC,QAEVA,EAAA,SAAW,WAEXA,EAAA,MAAQ,QAERA,EAAA,KAAO,OAEPA,EAAA,OAAS,SARCA,QAAA,ICVZ,IAAAC,EAA+B,uBAC/BC,EAUO,gCACPC,GAAwB,iBACxBC,GAAoC,oBCbpC,IAAAC,EAAkB,uBAClBC,GAKO,gCACPC,GAAwB,iBAQXC,GAAiB,CAAS,CACrC,KAAAC,EACA,gBAAAC,EACA,OAAAC,CACF,IAAkC,CAChC,IAAMC,EAAOH,GAAQA,EAAK,CAAC,EAAIA,EAAK,CAAC,GAAG,WAAa,OAE/C,CAAC,CAAE,QAAAI,CAAQ,CAAC,KAAI,YAAQ,aAAU,EAExC,OAAOJ,EACL,EAAAK,QAAA,cAAC,yBACE,CAACJ,GAAmBE,GACnB,EAAAE,QAAA,cAAC,OACC,MAAO,CACL,UAAW,EACX,aAAcL,EAAK,SAAW,EAAI,EAAI,EACtC,SAAUI,GAAS,QAAQ,UAAYA,GAAS,MAAM,KACtD,MAAO,MACT,GAECD,CACH,EAEDH,EAAK,IAAI,CAACM,EAAGC,IAAc,CAC1B,IAAMC,KAAiB,kBAAcF,EAAE,KAAK,EAC5C,OACE,EAAAD,QAAA,cAAC,OACC,IAAK,iBACHC,EAAE,OAASE,CACb,IAAIN,CAAM,IAAIK,EAAE,SAAS,CAAC,IAE1B,EAAAF,QAAA,cAAC,OACC,MAAO,CACL,UAAW,EACX,aAAcL,EAAK,OAASO,EAAI,EAAI,EAAI,EACxC,QAAS,OACT,WAAY,QACd,IAEED,EAAE,OAASA,EAAE,QACb,EAAAD,QAAA,gBAAAA,QAAA,cACGC,EAAE,KACH,EAAAD,QAAA,cAAC,OACC,MAAO,CACL,QAAS,OACT,WAAY,WACZ,WAAY,CACd,GAEA,EAAAA,QAAA,cAAC,OAAI,MAAO,CAAE,YAAa,CAAE,GAC3B,EAAAA,QAAA,cAAC,QACC,MAAO,CACL,SACED,GAAS,YAAY,UACrBA,GAAS,MAAM,IACnB,GAECE,EAAE,OAASE,EAAgB,GAC9B,CACF,CACF,CACF,EAEF,EAAAH,QAAA,cAAC,OACC,MAAO,CACL,WAAY,IACZ,SACED,GAAS,QAAQ,WAChBA,GAAS,MAAM,MAAQ,IAAM,CAClC,GAECE,EAAE,UACL,CACF,CACF,CAEJ,CAAC,CACH,EACE,IACN,EC9FA,IAAAG,GAA+B,uBAC/BC,GAKO,gCACPC,GAAwB,iBACxBC,GAAoB,oBAeb,IAAMC,GAAa,CAAS,CACjC,EAAAC,EACA,EAAAC,EACA,GAAAC,EACA,GAAAC,EACA,IAAAC,EACA,aAAAC,EACA,aAAAC,EACA,YAAAC,EACA,SAAAC,CACF,IAA8B,CAC5B,GAAM,CAAE,QAAAC,CAAQ,KAAI,UAAa,GAAK,CAAC,EACjC,CAAE,OAAAC,EAAQ,aAAAC,EAAc,MAAAC,EAAO,OAAAC,EAAQ,OAAAC,EAAQ,GAAAC,EAAG,EAAIN,GAAW,CAAC,EAElE,CAAC,CAAE,MAAAO,CAAM,CAAC,KAAI,YAAQ,eAAY,EAClC,CAAC,CAAE,YAAAC,EAAa