UNPKG

@graphique/geom-label

Version:

For creating labels based on data in Graphique

1 lines 57.7 kB
{"version":3,"sources":["../src/index.ts","../src/geomLabel.tsx","../src/types/index.ts","../src/tooltip/Tooltip.tsx","../src/tooltip/DefaultTooltip.tsx","../src/legend/AppearanceLegend.tsx","../src/legend/CategoricalLegend.tsx","../src/legend/ColorBandLegend.tsx"],"sourcesContent":["export { GeomLabel, GeomProps } from './geomLabel'\nexport { Legend, LegendProps } from './legend'\nexport { GeomAes, Entrance } from './types'\n","import React, {\n useState,\n useEffect,\n useMemo,\n useCallback,\n CSSProperties,\n SVGAttributes,\n useRef,\n} from 'react'\nimport { NodeGroup } from 'react-move'\nimport { easeCubic } from 'd3-ease'\nimport { interpolate } from 'd3-interpolate'\nimport { useAtom } from 'jotai'\nimport {\n useGG,\n focusNodes,\n unfocusNodes,\n EventArea,\n themeState,\n zoomState,\n xScaleState,\n yScaleState,\n isDate,\n defineGroupAccessor,\n BrushAction,\n usePageVisibility,\n} from '@graphique/graphique'\nimport { Entrance, type GeomAes } from './types'\nimport { Tooltip } from './tooltip'\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<SVGTextElement>\n /** should this Geom have a tooltip associated with it (_default_: `false`) */\n showTooltip?: boolean\n /** determines what happens when brushing (clicking and dragging) over the drawing area */\n brushAction?: BrushAction\n /** where elements should start as they enter the drawing area (_default_: `Entrance.BOTTOM`) */\n entrance?: Entrance\n /** should elements be strictly clipped at the bounds of the drawing area (_default_: `false`) */\n isClipped?: boolean\n /** array of keys (of the kind that are generated by `aes.key`) used to programmatically focus associated points */\n focusedKeys?: string[]\n /** styles applied to focused elements */\n focusedStyle?: CSSProperties\n /** styles applied to unfocused elements */\n unfocusedStyle?: CSSProperties\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 GeomLabel = <Datum,>({\n data: localData,\n aes: localAes,\n attr,\n focusedStyle,\n unfocusedStyle,\n focusedKeys = [],\n onDatumFocus,\n onDatumSelection,\n entrance = Entrance.BOTTOM,\n onExit,\n showTooltip = false,\n brushAction,\n isClipped = false,\n isAnimated = true,\n}: GeomProps<Datum>) => {\n const { ggState } = useGG<Datum>() || {}\n const { id, data, aes, scales, copiedScales, width, height, margin } =\n ggState || { width: 0 }\n\n const [theme, setTheme] = useAtom(themeState)\n const [{ xDomain: xZoomDomain, yDomain: yZoomDomain }] = useAtom(zoomState)\n const [{ isFixed: isFixedX }] = useAtom(xScaleState)\n const [{ isFixed: isFixedY }] = useAtom(yScaleState)\n\n const isVisible = usePageVisibility()\n\n const baseAttr: SVGAttributes<SVGTextElement> = {\n fillOpacity: 1,\n strokeOpacity: 1,\n strokeWidth: 3,\n dy: 3.8,\n }\n\n const geomAttr: SVGAttributes<SVGTextElement> = {\n ...baseAttr,\n ...attr,\n }\n\n const { defaultFill, animationDuration: duration, font } = theme\n\n const initialGeomData = useMemo(() => localData || data, [data, localData])\n\n const geomAes = useMemo(() => {\n if (localAes) {\n return {\n ...aes,\n ...localAes,\n }\n }\n return aes\n }, [aes, localAes])\n\n const group = useMemo(\n () => geomAes && defineGroupAccessor(geomAes),\n [geomAes, defineGroupAccessor],\n )\n\n const keyAccessor = useCallback(\n (d: Datum) =>\n geomAes?.key\n ? geomAes.key(d)\n : (`${geomAes?.x && geomAes.x(d)}-${geomAes?.y && geomAes.y(d)}-${\n group && group(d)\n }` as string),\n [geomAes, group],\n )\n\n const getLabel = useMemo(() => {\n if (!geomAes?.label)\n throw new Error('GeomLabel needs a label mapped in `aes.label`')\n\n return geomAes?.label ?? (() => '')\n }, [geomAes])\n\n const undefinedX = useMemo(\n () =>\n initialGeomData\n ? initialGeomData.filter(\n (d) =>\n geomAes?.x &&\n (geomAes.x(d) === null ||\n typeof geomAes.x(d) === 'undefined' ||\n (isDate(geomAes.x(d)) &&\n Number.isNaN(geomAes.x(d)?.valueOf()))),\n )\n : [],\n [initialGeomData, geomAes],\n )\n const undefinedY = useMemo(\n () =>\n initialGeomData\n ? initialGeomData.filter(\n (d) =>\n geomAes?.y &&\n (geomAes.y(d) === null || typeof geomAes.y(d) === 'undefined'),\n )\n : [],\n [initialGeomData],\n )\n\n const geomData = useMemo(() => {\n const presentData = initialGeomData?.filter(\n (d) =>\n geomAes?.x &&\n geomAes?.x(d) !== null &&\n !(typeof geomAes?.x(d) === 'undefined') &&\n (isDate(geomAes?.x(d))\n ? !Number.isNaN(geomAes?.x(d)?.valueOf())\n : true) &&\n geomAes.y &&\n geomAes.y(d) !== null &&\n !(typeof geomAes.y(d) === 'undefined'),\n )\n\n const uniqueKeyVals = Array.from(\n new Set(presentData?.map((d) => keyAccessor(d))),\n )\n\n return uniqueKeyVals.flatMap((k) => {\n const dataWithKey = presentData?.filter((d) => keyAccessor(d) === k)\n if (dataWithKey && dataWithKey.length > 1) {\n return dataWithKey.map((dk: any, i) => ({\n ...dk,\n gg_gen_index: i,\n }))\n }\n return dataWithKey?.flat()\n }) as Datum[]\n }, [initialGeomData, keyAccessor])\n\n const [firstRender, setFirstRender] = useState(true)\n useEffect(() => {\n const timeout = setTimeout(() => setFirstRender(false), 0)\n return () => clearTimeout(timeout)\n }, [])\n\n useEffect(() => {\n if (firstRender && undefinedX.length > 0) {\n console.warn(\n `Ignoring ${undefinedX.length} labels with missing x values.`,\n )\n }\n\n if (firstRender && undefinedY.length > 0) {\n console.warn(\n `Ignoring ${undefinedY.length} labels with missing y values.`,\n )\n }\n }, [firstRender, undefinedX, undefinedY])\n\n const bottomPos = useMemo(\n () => (height && margin ? height - margin.bottom : undefined),\n [height, margin],\n )\n\n useEffect(() => {\n setTheme((prev) => ({\n ...prev,\n geoms: {\n ...prev.geoms,\n label: {\n fillOpacity: geomAttr?.style?.fillOpacity || geomAttr?.fillOpacity,\n stroke: geomAttr?.stroke,\n strokeWidth: geomAttr?.style?.strokeWidth || geomAttr?.strokeWidth,\n strokeOpacity:\n geomAttr?.style?.strokeOpacity || geomAttr?.strokeOpacity,\n },\n },\n }))\n }, [attr, setTheme])\n\n const baseStyles: CSSProperties = {\n transition: 'fill-opacity 200ms',\n fillOpacity: geomAttr.fillOpacity,\n strokeOpacity: geomAttr.strokeOpacity,\n ...geomAttr.style,\n }\n\n const focusedStyles: CSSProperties = {\n ...baseStyles,\n ...focusedStyle,\n }\n\n const unfocusedStyles: CSSProperties = {\n ...baseStyles,\n fillOpacity: 0.2,\n strokeOpacity: 0.2,\n ...unfocusedStyle,\n }\n\n const fill = useMemo(\n () => (d: Datum) =>\n geomAttr.fill ||\n (geomAes?.fill && copiedScales?.fillScale\n ? (copiedScales.fillScale(geomAes.fill(d)) as string | undefined)\n : defaultFill),\n [geomAes, copiedScales, geomAttr, defaultFill],\n )\n\n const getStroke = useMemo(\n () => (d: Datum) =>\n geomAttr.stroke ||\n (geomAes?.stroke && copiedScales?.strokeScale\n ? (copiedScales.strokeScale(geomAes.stroke(d)) as string | undefined)\n : '#fff'),\n [geomAes, copiedScales, geomAttr],\n )\n\n const x = useMemo(() => {\n if (scales?.xScale.bandwidth) {\n return (d: Datum) =>\n (scales?.xScale(geomAes?.x && geomAes.x(d)) || 0) +\n scales?.xScale.bandwidth() / 2 +\n 0.9\n }\n return (d: Datum) =>\n scales?.xScale && geomAes?.x && (scales.xScale(geomAes.x(d)) || 0)\n }, [scales, geomAes])\n\n const y = useMemo(() => {\n if (scales?.yScale.bandwidth) {\n return (d: Datum) =>\n (scales?.yScale(geomAes?.y && geomAes.y(d)) || 0) +\n scales?.yScale.bandwidth() / 2\n }\n return (d: Datum) =>\n scales?.yScale && geomAes?.y && (scales.yScale(geomAes.y(d)) || 0)\n }, [scales, geomAes])\n\n const groupRef = useRef<SVGGElement>(null)\n const texts = groupRef.current?.getElementsByTagName('text')\n\n const [shouldClip, setShouldClip] = useState(isClipped)\n useEffect(() => {\n if (xZoomDomain?.current || yZoomDomain?.current) {\n setShouldClip(true)\n } else {\n const timeout = setTimeout(() => setShouldClip(isClipped), duration)\n return () => clearTimeout(timeout)\n }\n return undefined\n }, [isFixedX, isFixedY, xZoomDomain?.current, yZoomDomain?.current, duration])\n\n return (\n <>\n <g\n ref={groupRef}\n clipPath={shouldClip ? `url(#__gg_canvas_${id})` : undefined}\n >\n {!firstRender && isVisible && (\n <NodeGroup\n data={[...geomData]}\n keyAccessor={(d) =>\n geomAes?.key\n ? keyAccessor(d)\n : `${keyAccessor(d)}-${d.gg_gen_index}`\n }\n start={(d) => ({\n x: x(d),\n y: entrance === Entrance.DATA ? y(d) : bottomPos,\n fill: 'transparent',\n stroke: 'transparent',\n })}\n enter={(d) => ({\n x: isAnimated ? [x(d)] : x(d),\n y: isAnimated ? [y(d)] : y(d),\n fill: isAnimated ? [fill(d)] : fill(d),\n stroke: isAnimated ? [getStroke(d)] : getStroke(d),\n timing: { duration, ease: easeCubic },\n })}\n update={(d) => ({\n x: isAnimated ? [x(d)] : x(d),\n y: isAnimated ? [y(d)] : y(d),\n fill: isAnimated ? [fill(d)] : fill(d),\n stroke: isAnimated ? [getStroke(d)] : getStroke(d),\n timing: { duration, ease: easeCubic },\n })}\n leave={() => ({\n fill: isAnimated ? ['transparent'] : 'transparent',\n stroke: isAnimated ? ['transparent'] : 'transparent',\n y: isAnimated ? [bottomPos] : bottomPos,\n timing: { duration, ease: easeCubic },\n })}\n interpolation={(begVal, endVal) => interpolate(begVal, endVal)}\n >\n {(nodes) => (\n <>\n {nodes.map(({ state, key, data: nodeData }) => {\n let styles = {}\n if (focusedKeys.includes(key)) styles = focusedStyles\n if (focusedKeys?.length > 0 && !focusedKeys.includes(key))\n styles = unfocusedStyles\n\n const nodeX = x(nodeData) ?? 0\n\n return (\n <text\n key={key}\n // eslint-disable-next-line react/jsx-props-no-spreading\n {...attr}\n fillOpacity={state.fillOpacity}\n strokeOpacity={state.strokeOpacity}\n stroke={state.stroke}\n fill={state.fill}\n strokeWidth={geomAttr.strokeWidth}\n paintOrder=\"stroke\"\n pointerEvents=\"none\"\n textAnchor={\n geomAttr.textAnchor ??\n (nodeX > width / 2 ? 'end' : undefined)\n }\n dx={geomAttr?.dx ?? (nodeX > width / 2 ? -7 : 7)}\n dy={geomAttr?.dy}\n x={state.x}\n y={state.y}\n style={{\n pointerEvents: 'none',\n fontFamily:\n geomAttr?.style?.fontFamily ??\n geomAttr?.fontFamily ??\n font?.family ??\n '-apple-system, sans-serif',\n fontSize: geomAttr?.fontSize ?? 11,\n fontWeight: geomAttr?.fontWeight ?? 600,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n ...baseStyles,\n ...styles,\n }}\n data-testid=\"__gg_geom_label\"\n >\n {getLabel(nodeData)}\n </text>\n )\n })}\n </>\n )}\n </NodeGroup>\n )}\n </g>\n {(showTooltip || brushAction) && geomAes && (\n <>\n <EventArea\n data={geomData}\n showTooltip={showTooltip}\n brushAction={brushAction}\n aes={geomAes}\n x={x}\n y={y}\n onDatumFocus={onDatumFocus}\n onMouseOver={({ i }: { d: Datum[]; i: number[] }) => {\n const focusedIndexes = geomData.flatMap((gd, fi) =>\n focusedKeys.includes(keyAccessor(gd)) ? fi : [],\n )\n\n if (texts) {\n focusNodes({\n nodes: texts,\n focusedIndex: [...focusedIndexes, ...[i].flat()],\n focusedStyles,\n unfocusedStyles,\n })\n }\n }}\n onClick={\n onDatumSelection\n ? ({ d, i }: { d: Datum[]; i: number[] }) => {\n onDatumSelection(d, i)\n }\n : undefined\n }\n onMouseLeave={() => {\n if (texts) {\n if (showTooltip) {\n unfocusNodes({ nodes: texts, baseStyles })\n if (focusedKeys?.length > 0) {\n focusNodes({\n nodes: texts,\n focusedIndex: geomData.flatMap((d, i) =>\n focusedKeys.includes(keyAccessor(d)) ? i : [],\n ),\n focusedStyles,\n unfocusedStyles,\n })\n }\n }\n }\n\n if (onExit) onExit()\n }}\n />\n {showTooltip && <Tooltip aes={geomAes} group={group} />}\n </>\n )}\n </>\n )\n}\n\nGeomLabel.displayName = 'GeomLabel'\nexport { GeomLabel }\n","import { Aes, DataValue } from '@graphique/graphique'\n\nexport enum Entrance {\n DATA = 'data',\n BOTTOM = 'bottom',\n}\n\nexport type GeomAes<Datum> = Omit<Aes<Datum>, 'x'> & {\n x?: DataValue<Datum>\n}\n","import React, { useMemo } from 'react'\nimport { useAtom } from 'jotai'\nimport {\n useGG,\n tooltipState,\n labelsState,\n TooltipContent,\n YTooltip,\n DataValue,\n TooltipProps,\n TooltipPosition,\n} from '@graphique/graphique'\nimport { DefaultTooltip } from './DefaultTooltip'\nimport { type GeomAes } from '../types'\n\ninterface Props<Datum> {\n aes: GeomAes<Datum>\n group?: DataValue<Datum>\n}\n\nexport const Tooltip = <Datum,>({ aes, group }: Props<Datum>) => {\n const { ggState } = useGG<Datum>() || {}\n const { id, scales, height, width } = ggState || { width: 0, height: 0 }\n\n const [\n { datum: tooltipDatum, position, xFormat, yFormat, measureFormat, content },\n ] = useAtom<TooltipProps<Datum>>(tooltipState)\n\n const [{ x: xLab, y: yLab }] = useAtom(labelsState)\n\n const datum = useMemo(() => tooltipDatum && tooltipDatum[0], [tooltipDatum])\n\n const label = useMemo(() => {\n const labelResolution = {\n given: datum && aes?.label && aes.label(datum),\n keyed: datum && aes?.key && aes.key(datum),\n }\n\n return labelResolution?.given || labelResolution?.keyed\n }, [aes, datum])\n\n const xScale = scales?.xScale\n const yScale = scales?.yScale\n\n const xAdj = useMemo(\n () => (scales?.xScale.bandwidth ? scales?.xScale.bandwidth() / 2 : 0),\n [scales],\n )\n const yAdj = useMemo(\n () => (scales?.yScale?.bandwidth ? scales.yScale.bandwidth() / 2 : 0),\n [scales],\n )\n\n const thisGroup = useMemo(\n () => datum && group && group(datum),\n [datum, group],\n )\n\n const tooltipContents: TooltipContent<Datum>[] = [\n {\n x: datum && aes?.x && xScale && xScale(aes.x(datum)),\n y: datum && aes?.y && yScale && yScale(aes.y(datum)),\n xLab: xLab?.toString(),\n yLab: yLab?.toString(),\n formattedX:\n datum &&\n aes?.x &&\n ((xFormat ? xFormat(aes.x(datum)) : aes.x(datum)) as string),\n formattedY:\n datum &&\n aes?.y &&\n ((yFormat ? yFormat(aes.y(datum)) : aes.y(datum)) as string),\n group: thisGroup,\n label,\n formattedMeasure:\n measureFormat &&\n (label || String(thisGroup)) &&\n measureFormat(label || thisGroup),\n datum: tooltipDatum,\n containerWidth: width,\n },\n ]\n\n const tooltipValue = content\n ? datum && <div>{content(tooltipContents)}</div>\n : datum && <DefaultTooltip data={tooltipContents} />\n\n const shouldShow =\n datum &&\n tooltipContents[0].x !== undefined &&\n tooltipContents[0].y !== undefined\n\n return shouldShow ? (\n <div>\n <YTooltip\n id={id as string}\n left={(tooltipContents[0].x || 0) + xAdj}\n top={\n position === TooltipPosition.DATA\n ? -(height - (tooltipContents[0].y || 0) - yAdj)\n : -height\n }\n value={tooltipValue}\n />\n </div>\n ) : null\n}\n","import React, { useState, useEffect } from 'react'\nimport { useAtom } from 'jotai'\nimport {\n labelsState,\n TooltipContent,\n TooltipContainer,\n formatMissing,\n themeState,\n nodeToString,\n} from '@graphique/graphique'\n\ninterface Props<Datum> {\n data: TooltipContent<Datum>[]\n}\n\nexport const DefaultTooltip = <Datum,>({ data }: Props<Datum>) => {\n const [{ x: xLab, y: yLab }] = useAtom(labelsState)\n const [{ tooltip }] = useAtom(themeState)\n\n const [yLabel, setYLabel] = useState('')\n useEffect(() => {\n const timeout = setTimeout(() => setYLabel(nodeToString(yLab)))\n\n return () => clearTimeout(timeout)\n }, [yLab])\n\n return data ? (\n <TooltipContainer>\n {data.map((d) => {\n const formattedGroup = formatMissing(d.group)\n return (\n <div key={`group-tooltip-${d.label || formattedGroup}`}>\n <div\n style={{\n marginTop: 4,\n marginBottom: 4,\n }}\n >\n {(d.label || d.group !== '__group') && (\n <>\n {d.mark}\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-end',\n fontWeight: 500,\n }}\n >\n <div style={{ marginBottom: 4 }}>\n <span\n style={{\n fontSize:\n tooltip?.groupLabel?.fontSize ||\n tooltip?.font?.size,\n }}\n >\n {d.formattedMeasure || formattedGroup}\n </span>\n </div>\n </div>\n </>\n )}\n <div style={{ display: 'flex', marginBottom: 2 }}>\n {xLab && (\n <div\n style={{\n fontSize:\n tooltip?.xLabel?.fontSize || tooltip?.font?.size,\n }}\n >\n {`${xLab}:`}\n </div>\n )}\n <div\n style={{\n marginLeft: 1,\n fontWeight: 500,\n fontSize:\n tooltip?.xLabel?.fontSize ||\n (tooltip?.font?.size || 12) + 1,\n }}\n >\n {d.formattedX}\n </div>\n </div>\n <div style={{ display: 'flex' }}>\n {yLabel && (\n <div\n style={{\n fontSize:\n tooltip?.yLabel?.fontSize || tooltip?.font?.size,\n }}\n >\n {`${yLabel}:`}\n </div>\n )}\n <div\n style={{\n marginLeft: 1,\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 </div>\n )\n })}\n </TooltipContainer>\n ) : null\n}\n","import React, { CSSProperties } from 'react'\nimport { useGG, themeState, LegendOrientation } from '@graphique/graphique'\nimport { useAtom } from 'jotai'\nimport { CategoricalLegend } from './CategoricalLegend'\nimport { ColorBandLegend } from './ColorBandLegend'\n\nexport interface LegendProps {\n /** title of legend */\n title?: React.ReactNode\n /** determines vertical/horizontal orientation of categorical legend members (_default_: `LegendOrientation.V`) */\n orientation?: LegendOrientation\n /** function for formatting legend member labels (categorical) or tick labels (continuous) */\n format?: (v: string, i?: number) => string\n /** width of continuous legend in pixels (_default_: `320`) */\n width?: number\n /** approximate number of ticks for continuous legend (_default_: `width / 64`) */\n numTicks?: number\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 style,\n orientation = LegendOrientation.V,\n format,\n numTicks,\n width,\n onSelection,\n}: LegendProps) => {\n const { ggState } = useGG<Datum>() || {}\n const { copiedScales, copiedData, aes } = ggState || {}\n const [{ font }] = useAtom(themeState)\n\n const { groups } = copiedScales || {}\n\n // include aes?.strokeDasharray\n const hasAppearanceAes = aes?.fill || aes?.stroke\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 && copiedScales && groups ? (\n <CategoricalLegend\n legendData={copiedData}\n orientation={orientation}\n legendScales={copiedScales}\n labelFormat={format}\n fontSize={fontSize}\n onSelection={onSelection}\n />\n ) : (\n <ColorBandLegend\n scales={copiedScales}\n tickFormat={format}\n numTicks={numTicks}\n fontSize={fontSize}\n width={width}\n />\n )}\n </div>\n ) : null\n}\n","import React, { useState, useEffect } from 'react'\nimport {\n useGG,\n themeState,\n fillScaleState,\n strokeScaleState,\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: string, i?: number) => string\n fontSize?: string | number\n onSelection?: (v: string) => void\n}\n\nexport const CategoricalLegend = <Datum,>({\n legendData,\n legendScales,\n orientation,\n labelFormat,\n fontSize = 12,\n onSelection,\n}: CategoricalLegendProps<Datum>) => {\n const [isFocused, setIsFocused] = useState<string[]>(\n legendScales.groups || [],\n )\n\n const [{ geoms, legend }] = useAtom(themeState)\n const [{ domain: fillDomain }] = useAtom(fillScaleState)\n const [{ domain: strokeDomain }] = useAtom(strokeScaleState)\n\n const legendGroups =\n ((fillDomain || strokeDomain) as string[]) || legendScales.groups\n\n const { ggState, updateData } = useGG<Datum>() || {}\n const { scales, data } = ggState || {}\n\n useEffect(() => {\n setIsFocused(scales?.groups || [])\n }, [scales, data])\n\n const getGroup = legendScales.groupAccessor\n ? legendScales.groupAccessor\n : () => legendScales.groups && legendScales.groups[0]\n\n const isHorizontal = orientation === LegendOrientation.H\n\n const toggleLegendGroup = (g: string) => {\n const prevFocused = isFocused\n let focusedGroups\n if (prevFocused.includes(g)) {\n if (prevFocused.length === 1) {\n focusedGroups = legendScales.groups as string[]\n } else {\n focusedGroups = prevFocused.filter((p) => p !== g)\n }\n } else {\n focusedGroups = [...prevFocused, g]\n }\n setIsFocused(focusedGroups)\n\n const includedGroups = Array.from(new Set(data?.map((d) => getGroup(d))))\n\n if (onSelection) {\n onSelection(g)\n }\n if (data && updateData) {\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 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?.label?.fillOpacity &&\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 // scales?.fillScale?.domain().includes(g) ||\n // legendScales.groups?.includes(g)\n // ? \"pointer\"\n // : \"not-allowed\",\n marginRight: i < groups.length - 1 && isHorizontal ? 12 : 2,\n fontSize,\n opacity: isFocused.includes(g) ? 1 : 0.5,\n transition: 'opacity 200ms',\n display: 'flex',\n alignItems: 'center',\n }}\n onKeyPress={(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={12} height={12}>\n <circle\n r={4}\n cx={6}\n cy={6}\n fill={\n geoms?.label?.fill ||\n (legendScales.fillScale\n ? legendScales.fillScale(g)\n : 'none')\n }\n stroke={\n geoms?.label?.stroke ||\n (legendScales.strokeScale\n ? legendScales.strokeScale(g)\n : 'none')\n }\n strokeWidth={1.8}\n fillOpacity={\n isFocused.includes(g) ? geoms?.label?.fillOpacity : 0.5\n }\n strokeOpacity={\n isFocused.includes(g) ? geoms?.label?.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","import React, { useCallback, useEffect, useState, useRef } from 'react'\nimport { useAtom } from 'jotai'\nimport {\n themeState,\n fillScaleState,\n strokeScaleState,\n IScale,\n} from '@graphique/graphique'\nimport { interpolateRound } from 'd3-interpolate'\nimport { select } from 'd3-selection'\nimport { axisBottom } from 'd3-axis'\nimport { range, quantile } from 'd3-array'\nimport { transition } from 'd3-transition'\n\nexport interface ColorBandLegendProps<Datum> {\n scales: IScale<Datum>\n tickFormat?: (v: string, i?: number) => string\n width?: number\n tickSize?: number\n height?: number\n margin?: {\n top?: number\n right?: number\n bottom?: number\n left?: number\n }\n numTicks?: number\n fontSize?: number | string\n}\n\nexport const ColorBandLegend = <Datum,>({\n scales,\n tickFormat,\n width = 320,\n tickSize = 6,\n height = 30 + tickSize,\n margin,\n numTicks = width / 64,\n fontSize = 10,\n}: ColorBandLegendProps<Datum>) => {\n const legendRef = useRef<SVGSVGElement | null>(null)\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const axisRef = useRef<SVGGElement | null>(null)\n const ticksRef = useRef<SVGGElement | null>(null)\n const imageRef = useRef<SVGImageElement | null>(null)\n const colorScale = scales?.fillScale || scales?.strokeScale\n const [{ geoms, font: themeFont, legend, animationDuration }] =\n useAtom(themeState)\n\n const [{ reverse: reverseFill }] = useAtom(fillScaleState)\n const [{ reverse: reverseStroke }] = useAtom(strokeScaleState)\n\n const [firstRender, setFirstRender] = useState(true)\n useEffect(() => {\n const timeout = setTimeout(() => setFirstRender(false), 0)\n return () => clearTimeout(timeout)\n }, [])\n\n const isReversed = reverseFill || reverseStroke\n\n const RAMP_N = 256\n\n const usedMargin = {\n top: 4,\n right: 0,\n bottom: 16 + tickSize,\n left: 0,\n ...margin,\n }\n const drawLegend = useCallback(\n (scale: any, font?: string) => {\n if (\n legendRef.current &&\n canvasRef.current &&\n axisRef.current &&\n ticksRef.current &&\n imageRef.current\n ) {\n const ramp = (canvas: HTMLCanvasElement, color: any, n: number) => {\n const context = canvas.getContext('2d')\n for (let i = 0; i < n; i += 1) {\n if (context && color) {\n context.fillStyle = color(i / (n - 1))\n context.fillRect(isReversed ? n - i : i, 0, 1, 1)\n }\n }\n return canvas\n }\n\n const duration = animationDuration ?? 1000\n\n let x: any\n let tickValues: any\n const tickAdjust = (g: any) =>\n g\n .selectAll('.tick line')\n .attr('y1', usedMargin.top + usedMargin.bottom - height)\n\n // let scaleType = \"unknown\"\n\n const canvas = select(canvasRef.current)\n const axis = select(axisRef.current)\n const ticks = select(ticksRef.current)\n const img = select(imageRef.current)\n\n const t = transition().duration(duration)\n\n if (scale?.interpolate) {\n // scaleType = \"continuous\"\n } else if (scale?.interpolator) {\n // scaleType = \"sequential\"\n x = Object.assign(\n scale\n .copy()\n .interpolator(\n interpolateRound(usedMargin.left, width - usedMargin.right),\n ),\n {\n range() {\n return [usedMargin.left, width - usedMargin.right]\n },\n },\n )\n\n img\n .attr('x', usedMargin.left)\n .attr('y', usedMargin.top)\n .attr('width', width - usedMargin.left - usedMargin.right)\n .attr('height', height - usedMargin.top - usedMargin.bottom)\n .attr('preserveAspectRatio', 'none')\n .attr(\n 'xlink:href',\n ramp(\n canvasRef.current as HTMLCanvasElement,\n scale.interpolator(),\n RAMP_N,\n ).toDataURL(),\n )\n\n if (firstRender) {\n img\n .style('opacity', 0)\n .transition(t)\n .style(\n 'opacity',\n ((scales?.fillScale && geoms?.label?.fillOpacity) ||\n (scales?.strokeScale && geoms?.label?.strokeOpacity) ||\n undefined) as number | string,\n )\n }\n\n if (!x.ticks) {\n if (tickValues === undefined) {\n const n = Math.round(numTicks + 1)\n tickValues = range(n).map((i: number) =>\n quantile(scale.domain(), i / (n - 1)),\n )\n }\n }\n\n canvas.remove()\n }\n\n if (isReversed) {\n x.domain(x.domain().reverse())\n }\n\n axis\n .attr('transform', `translate(0,${height - usedMargin.bottom})`)\n .transition(t)\n .call(\n axisBottom(x)\n .ticks(\n numTicks,\n typeof tickFormat === 'string' ? tickFormat : undefined,\n )\n .tickFormat(\n typeof tickFormat === 'function'\n ? (tickFormat as any)\n : undefined,\n )\n .tickSize(tickSize)\n .tickValues(tickValues),\n )\n\n axis\n .call((g) => g.select('.domain').remove())\n .selectAll('line')\n .attr('stroke', legend?.tickColor || 'currentColor')\n .style('opacity', legend?.tickColor ? 1 : 0.85)\n\n axis\n .selectAll('.tick')\n .select('text')\n .style('font-family', font || 'sans-serif')\n .style('font-size', fontSize)\n .attr('fill', legend?.labelColor || 'currentColor')\n .style('opacity', legend?.labelColor ? 1 : 0.85)\n\n // ticks whose color isn't depenedent on currentColor\n ticks\n .attr('transform', `translate(0,${height - usedMargin.bottom})`)\n .transition(t)\n .call(\n axisBottom(x)\n .ticks(\n numTicks,\n typeof tickFormat === 'string' ? tickFormat : undefined,\n )\n .tickSize(1)\n .tickFormat(() => ''),\n )\n .selectAll('line')\n .attr('stroke', '#111')\n\n ticks\n .call((g) => g.select('.domain').remove())\n .call((g) => g.selectAll('.tick').select('text').remove())\n .call(tickAdjust)\n }\n },\n [\n width,\n height,\n numTicks,\n tickFormat,\n tickSize,\n usedMargin,\n legend,\n geoms,\n scales,\n fontSize,\n firstRender,\n isReversed,\n animationDuration,\n ],\n )\n\n useEffect(() => {\n drawLegend(colorScale, themeFont?.family)\n }, [themeFont, colorScale, drawLegend])\n\n return (\n <div>\n {themeFont?.family && (\n <svg\n ref={legendRef}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n style={{\n overflow: 'visible',\n display: 'block',\n }}\n >\n <image ref={imageRef} />\n <g ref={axisRef} />\n <g ref={ticksRef} />\n </svg>\n )}\n <canvas ref={canvasRef} width={RAMP_N} height={1} />\n </div>\n )\n}\n"],"mappings":"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