mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
sankey (#3286)
* sankey * sanki2 * numine data example * remove showcase * address PR review comments for sankey chart - move @types/d3-sankey to devDependencies - use targeted d3-selection/d3-transition imports in SankeyLink - add explicit return type to useSankeyController - extract LABEL_LINE_HEIGHT/LABEL_LINE_GAP module-level constants - add readonly modifiers to SankeyNodeDatum, SankeyLinkDatum, SankeyData - add comment explaining d3-sankey runtime type assertions in SankeyNode Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * address PR review comments for sankey chart Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * review fixes --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Claude Sonnet 4.6
parent
18bc3e0806
commit
d8bc35d94c
@@ -93,6 +93,7 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"cspell": "9.6.4",
|
||||
"d3": "^7.6.1",
|
||||
"d3-sankey": "^0.12.3",
|
||||
"dappscout-iframe": "0.4.0",
|
||||
"dayjs": "^1.11.5",
|
||||
"dom-to-image": "^2.6.0",
|
||||
@@ -152,6 +153,7 @@
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/csp-dev": "^1.0.0",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-sankey": "^0.12.5",
|
||||
"@types/dom-to-image": "^2.6.4",
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@types/mixpanel-browser": "^2.38.1",
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { useToken } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ChartMargin } from '../types';
|
||||
import type { SankeyData, SankeyNodeExtended } from './types';
|
||||
|
||||
import { useColorModeValue } from 'toolkit/chakra/color-mode';
|
||||
|
||||
import {
|
||||
DEFAULT_SANKEY_LINK_HOVER_OPACITY,
|
||||
DEFAULT_SANKEY_LINK_OPACITY,
|
||||
SANKEY_NODE_COLOR_TOKENS_DARK,
|
||||
SANKEY_NODE_COLOR_TOKENS_LIGHT,
|
||||
} from './constants';
|
||||
import { SankeyLink } from './parts/SankeyLink';
|
||||
import { SankeyNode } from './parts/SankeyNode';
|
||||
import { useSankeyController } from './useSankeyController';
|
||||
|
||||
export type SankeyLinkColorMode = 'source' | 'target';
|
||||
|
||||
export interface SankeyChartProps {
|
||||
data: SankeyData;
|
||||
margin?: ChartMargin;
|
||||
nodeWidth?: number;
|
||||
nodePadding?: number;
|
||||
linkOpacity?: number;
|
||||
linkHoverOpacity?: number;
|
||||
linkColorMode?: SankeyLinkColorMode;
|
||||
colors?: ReadonlyArray<string>;
|
||||
valueFormatter?: (value: number) => string;
|
||||
}
|
||||
|
||||
const DEFAULT_CHART_MARGIN: ChartMargin = { top: 40, right: 8, bottom: 8, left: 8 };
|
||||
const LABEL_Y_GAP = 4;
|
||||
const LABEL_LINE_HEIGHT = 16;
|
||||
const LABEL_LINE_GAP = 2;
|
||||
|
||||
export const SankeyChart = React.memo(({
|
||||
data,
|
||||
margin: marginProps,
|
||||
nodeWidth,
|
||||
nodePadding,
|
||||
linkOpacity = DEFAULT_SANKEY_LINK_OPACITY,
|
||||
linkHoverOpacity = DEFAULT_SANKEY_LINK_HOVER_OPACITY,
|
||||
linkColorMode = 'source',
|
||||
colors: colorsProp,
|
||||
valueFormatter,
|
||||
}: SankeyChartProps) => {
|
||||
const margin: ChartMargin = React.useMemo(() => ({ ...DEFAULT_CHART_MARGIN, ...marginProps }), [ marginProps ]);
|
||||
|
||||
const colorTokens = useColorModeValue(SANKEY_NODE_COLOR_TOKENS_LIGHT, SANKEY_NODE_COLOR_TOKENS_DARK);
|
||||
const resolvedDefaultColors = useToken('colors', colorTokens);
|
||||
const colors = colorsProp && colorsProp.length > 0 ? colorsProp : resolvedDefaultColors;
|
||||
|
||||
const [ labelColor ] = useToken('colors', 'text.secondary');
|
||||
const [ labelFontSize ] = useToken('fontSizes', 'xs');
|
||||
|
||||
const {
|
||||
ref,
|
||||
rect,
|
||||
innerWidth,
|
||||
nodes,
|
||||
links,
|
||||
linkPathGenerator,
|
||||
} = useSankeyController({
|
||||
data,
|
||||
margin,
|
||||
nodeWidth,
|
||||
nodePadding,
|
||||
});
|
||||
|
||||
const nodeColorMap = React.useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
nodes.forEach((node, index) => {
|
||||
map.set(node.id, colors[index % colors.length]);
|
||||
});
|
||||
return map;
|
||||
}, [ nodes, colors ]);
|
||||
|
||||
const getNodeColor = React.useCallback((node: SankeyNodeExtended) => {
|
||||
return nodeColorMap.get(node.id) || colors[0];
|
||||
}, [ nodeColorMap, colors ]);
|
||||
|
||||
const formatValue = React.useCallback((value: number | undefined) => {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
return valueFormatter ? valueFormatter(value) : String(value);
|
||||
}, [ valueFormatter ]);
|
||||
|
||||
return (
|
||||
<svg width="100%" height="100%" ref={ ref } opacity={ rect ? 1 : 0 }>
|
||||
<g transform={ `translate(${ margin.left || 0 },${ margin.top || 0 })` }>
|
||||
{ links.map((link, index) => (
|
||||
<SankeyLink
|
||||
key={ `link-${ index }` }
|
||||
link={ link }
|
||||
color={ getNodeColor(linkColorMode === 'target' ? link.target : link.source) }
|
||||
sourceColor={ getNodeColor(link.source) }
|
||||
targetColor={ getNodeColor(link.target) }
|
||||
gradientIdSuffix={ index }
|
||||
opacity={ linkOpacity }
|
||||
hoverOpacity={ linkHoverOpacity }
|
||||
pathGenerator={ linkPathGenerator }
|
||||
/>
|
||||
)) }
|
||||
|
||||
{ nodes.map((node) => (
|
||||
<SankeyNode
|
||||
key={ node.id }
|
||||
node={ node }
|
||||
color={ getNodeColor(node) }
|
||||
/>
|
||||
)) }
|
||||
|
||||
{ nodes.map((node) => {
|
||||
const { x0 = 0, x1 = 0, y0 = 0 } = node;
|
||||
const isSource = (x0 || 0) <= innerWidth / 2;
|
||||
const nodeId = node.id;
|
||||
const nodeName = node.name;
|
||||
const valueStr = node.value !== undefined ? formatValue(node.value) : '';
|
||||
|
||||
const blockHeight = LABEL_LINE_HEIGHT + (valueStr ? LABEL_LINE_GAP + LABEL_LINE_HEIGHT : 0);
|
||||
const blockTop = (y0 || 0) - LABEL_Y_GAP - blockHeight;
|
||||
const nameCenterY = blockTop + LABEL_LINE_HEIGHT / 2;
|
||||
const valueCenterY = blockTop + LABEL_LINE_HEIGHT + LABEL_LINE_GAP + LABEL_LINE_HEIGHT / 2;
|
||||
|
||||
const labelX = isSource ? (x0 || 0) : (x1 || 0);
|
||||
const anchor = isSource ? 'start' : 'end';
|
||||
|
||||
return (
|
||||
<g key={ `label-${ nodeId }` }>
|
||||
<text
|
||||
x={ labelX }
|
||||
y={ nameCenterY }
|
||||
textAnchor={ anchor }
|
||||
dominantBaseline="central"
|
||||
fill={ labelColor }
|
||||
style={{ fontSize: labelFontSize, fontWeight: 600 }}
|
||||
>
|
||||
{ nodeName }
|
||||
</text>
|
||||
{ valueStr && (
|
||||
<text
|
||||
x={ labelX }
|
||||
y={ valueCenterY }
|
||||
textAnchor={ anchor }
|
||||
dominantBaseline="central"
|
||||
fill={ labelColor }
|
||||
style={{ fontSize: labelFontSize, fontWeight: 400 }}
|
||||
>
|
||||
{ valueStr }
|
||||
</text>
|
||||
) }
|
||||
</g>
|
||||
);
|
||||
}) }
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Sankey node color tokens from Figma (Universal Bridge Indexer).
|
||||
* Light theme: Chakra 300 palette. Resolved via useToken in the chart.
|
||||
* @see https://www.figma.com/design/3mGpZEE65cSuLnhtJBfKkO?node-id=6867-20752
|
||||
*/
|
||||
export const SANKEY_NODE_COLOR_TOKENS_LIGHT: Array<string> = [
|
||||
'blue.300',
|
||||
'purple.300',
|
||||
'pink.300',
|
||||
'green.300',
|
||||
'teal.300',
|
||||
'cyan.300',
|
||||
'orange.300',
|
||||
'yellow.300',
|
||||
'red.300',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sankey node color tokens from Figma (Universal Bridge Indexer).
|
||||
* Dark theme: Chakra 700 palette. Resolved via useToken in the chart.
|
||||
* @see https://www.figma.com/design/3mGpZEE65cSuLnhtJBfKkO?node-id=6867-21363
|
||||
*/
|
||||
export const SANKEY_NODE_COLOR_TOKENS_DARK: Array<string> = [
|
||||
'blue.700',
|
||||
'purple.700',
|
||||
'pink.700',
|
||||
'green.700',
|
||||
'teal.700',
|
||||
'cyan.700',
|
||||
'orange.700',
|
||||
'yellow.700',
|
||||
'red.700',
|
||||
];
|
||||
|
||||
export const DEFAULT_SANKEY_NODE_WIDTH = 16;
|
||||
export const DEFAULT_SANKEY_NODE_PADDING = 40;
|
||||
export const DEFAULT_SANKEY_LINK_OPACITY = 0.35;
|
||||
export const DEFAULT_SANKEY_LINK_HOVER_OPACITY = 0.6;
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from './SankeyChart';
|
||||
export * from './parts/SankeyNode';
|
||||
export * from './parts/SankeyLink';
|
||||
export * from './useSankeyController';
|
||||
export * from './types';
|
||||
export * from './constants';
|
||||
@@ -0,0 +1,109 @@
|
||||
import { select } from 'd3-selection';
|
||||
import 'd3-transition';
|
||||
import React from 'react';
|
||||
|
||||
import type { SankeyLinkExtended } from '../types';
|
||||
|
||||
export interface SankeyLinkProps {
|
||||
link: SankeyLinkExtended;
|
||||
|
||||
/** Single color when gradient is not used */
|
||||
color?: string;
|
||||
|
||||
/** When set with targetColor, link stroke is a gradient from source to target */
|
||||
sourceColor?: string;
|
||||
targetColor?: string;
|
||||
|
||||
/** Unique suffix for gradient id (e.g. link index) when multiple links share same source/target */
|
||||
gradientIdSuffix?: string | number;
|
||||
opacity: number;
|
||||
hoverOpacity: number;
|
||||
|
||||
pathGenerator: (link: SankeyLinkExtended, ...args: Array<any>) => string | null;
|
||||
onMouseEnter?: (link: SankeyLinkExtended, event: React.MouseEvent) => void;
|
||||
onMouseLeave?: () => void;
|
||||
}
|
||||
|
||||
export const SankeyLink = React.memo(({
|
||||
link,
|
||||
color,
|
||||
sourceColor,
|
||||
targetColor,
|
||||
gradientIdSuffix,
|
||||
opacity,
|
||||
hoverOpacity,
|
||||
pathGenerator,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
}: SankeyLinkProps) => {
|
||||
const ref = React.useRef<SVGPathElement>(null);
|
||||
|
||||
const handleMouseEnter = React.useCallback((event: React.MouseEvent) => {
|
||||
if (ref.current) {
|
||||
select(ref.current).transition().duration(200).attr('stroke-opacity', hoverOpacity);
|
||||
}
|
||||
onMouseEnter?.(link, event);
|
||||
}, [ link, hoverOpacity, onMouseEnter ]);
|
||||
|
||||
const handleMouseLeave = React.useCallback(() => {
|
||||
if (ref.current) {
|
||||
select(ref.current).transition().duration(200).attr('stroke-opacity', opacity);
|
||||
}
|
||||
onMouseLeave?.();
|
||||
}, [ opacity, onMouseLeave ]);
|
||||
|
||||
const source = link.source;
|
||||
const target = link.target;
|
||||
|
||||
const d = pathGenerator(link);
|
||||
if (!d) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sanitizeSvgId = (value: string | number): string => String(value).replace(/[^\w-]/g, '_');
|
||||
const useGradient = sourceColor != null && targetColor != null && sourceColor !== targetColor;
|
||||
const gradientId = useGradient ?
|
||||
`sankey-link-${ sanitizeSvgId(source.id) }-${ sanitizeSvgId(target.id) }-${ sanitizeSvgId(gradientIdSuffix ?? '') }` :
|
||||
undefined;
|
||||
|
||||
const strokeColor = useGradient && gradientId ?
|
||||
`url(#${ gradientId })` :
|
||||
color;
|
||||
|
||||
const sourceX = source.x1 ?? 0;
|
||||
const sourceY = ((source.y0 ?? 0) + (source.y1 ?? 0)) / 2;
|
||||
const targetX = target.x0 ?? 0;
|
||||
const targetY = ((target.y0 ?? 0) + (target.y1 ?? 0)) / 2;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ useGradient && gradientId && (
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={ gradientId }
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1={ sourceX }
|
||||
y1={ sourceY }
|
||||
x2={ targetX }
|
||||
y2={ targetY }
|
||||
>
|
||||
<stop offset="0%" stopColor={ sourceColor }/>
|
||||
<stop offset="100%" stopColor={ targetColor }/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
) }
|
||||
<path
|
||||
ref={ ref }
|
||||
d={ d }
|
||||
fill="none"
|
||||
stroke={ strokeColor }
|
||||
strokeOpacity={ opacity }
|
||||
strokeWidth={ Math.max(1, link.width || 0) }
|
||||
onMouseEnter={ handleMouseEnter }
|
||||
onMouseLeave={ handleMouseLeave }
|
||||
>
|
||||
<title>{ `${ source.name } → ${ target.name }: ${ link.value }` }</title>
|
||||
</path>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { SankeyNodeExtended } from '../types';
|
||||
|
||||
const OUTER_CORNER_RADIUS = 4;
|
||||
|
||||
export interface SankeyNodeProps {
|
||||
node: SankeyNodeExtended;
|
||||
color: string;
|
||||
onMouseEnter?: (node: SankeyNodeExtended, event: React.MouseEvent) => void;
|
||||
onMouseLeave?: () => void;
|
||||
}
|
||||
|
||||
export const SankeyNode = React.memo(({ node, color, onMouseEnter, onMouseLeave }: SankeyNodeProps) => {
|
||||
const { x0 = 0, x1 = 0, y0 = 0, y1 = 0 } = node;
|
||||
const width = x1 - x0;
|
||||
const height = y1 - y0;
|
||||
|
||||
const handleMouseEnter = React.useCallback((event: React.MouseEvent) => {
|
||||
onMouseEnter?.(node, event);
|
||||
}, [ node, onMouseEnter ]);
|
||||
|
||||
const hasIncoming = node.targetLinks?.length;
|
||||
const hasOutgoing = node.sourceLinks?.length;
|
||||
const isMiddle = Boolean(hasIncoming && hasOutgoing);
|
||||
|
||||
const path = React.useMemo(() => {
|
||||
if (width <= 0 || height <= 0 || isMiddle) {
|
||||
return null;
|
||||
}
|
||||
const r = Math.min(OUTER_CORNER_RADIUS, width / 2, height / 2);
|
||||
|
||||
if (hasIncoming) {
|
||||
return (
|
||||
`M ${ x0 },${ y0 } L ${ x1 - r },${ y0 } Q ${ x1 },${ y0 } ${ x1 },${ y0 + r } ` +
|
||||
`L ${ x1 },${ y1 - r } Q ${ x1 },${ y1 } ${ x1 - r },${ y1 } L ${ x0 },${ y1 } L ${ x0 },${ y0 } Z`
|
||||
);
|
||||
}
|
||||
return (
|
||||
`M ${ x1 },${ y0 } L ${ x0 + r },${ y0 } Q ${ x0 },${ y0 } ${ x0 },${ y0 + r } ` +
|
||||
`L ${ x0 },${ y1 - r } Q ${ x0 },${ y1 } ${ x0 + r },${ y1 } L ${ x1 },${ y1 } L ${ x1 },${ y0 } Z`
|
||||
);
|
||||
}, [ x0, x1, y0, y1, width, height, isMiddle, hasIncoming ]);
|
||||
|
||||
if (!path) {
|
||||
if (isMiddle) {
|
||||
return (
|
||||
<rect
|
||||
x={ x0 }
|
||||
y={ y0 }
|
||||
width={ width }
|
||||
height={ height }
|
||||
fill={ color }
|
||||
onMouseEnter={ handleMouseEnter }
|
||||
onMouseLeave={ onMouseLeave }
|
||||
>
|
||||
<title>{ `${ node.name }: ${ node.value }` }</title>
|
||||
</rect>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<path
|
||||
d={ path }
|
||||
fill={ color }
|
||||
onMouseEnter={ handleMouseEnter }
|
||||
onMouseLeave={ onMouseLeave }
|
||||
>
|
||||
<title>{ `${ node.name }: ${ node.value }` }</title>
|
||||
</path>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { SankeyNode, SankeyLink } from 'd3-sankey';
|
||||
|
||||
export interface SankeyNodeDatum {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export interface SankeyLinkDatum {
|
||||
readonly source: string;
|
||||
readonly target: string;
|
||||
readonly value: number;
|
||||
}
|
||||
|
||||
export interface SankeyData {
|
||||
readonly nodes: ReadonlyArray<SankeyNodeDatum>;
|
||||
readonly links: ReadonlyArray<SankeyLinkDatum>;
|
||||
}
|
||||
|
||||
// After d3-sankey layout, nodes are the input datum merged with the layout properties.
|
||||
export type SankeyNodeExtended = SankeyNodeDatum & SankeyNode<SankeyNodeDatum, SankeyLinkDatum>;
|
||||
|
||||
// After d3-sankey layout, source/target are always resolved node objects, not ids.
|
||||
export type SankeyLinkExtended = Omit<SankeyLink<SankeyNodeDatum, SankeyLinkDatum>, 'source' | 'target'> & {
|
||||
source: SankeyNodeExtended;
|
||||
target: SankeyNodeExtended;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { sankey, sankeyLinkHorizontal, sankeyJustify } from 'd3-sankey';
|
||||
import React from 'react';
|
||||
|
||||
import type { ChartMargin } from '../types';
|
||||
import type { SankeyData, SankeyLinkExtended, SankeyNodeExtended } from './types';
|
||||
|
||||
import { calculateInnerSize } from 'toolkit/components/charts/utils/calculateInnerSize';
|
||||
import { useClientRect } from 'toolkit/hooks/useClientRect';
|
||||
|
||||
import { DEFAULT_SANKEY_NODE_PADDING, DEFAULT_SANKEY_NODE_WIDTH } from './constants';
|
||||
|
||||
interface Props {
|
||||
data: SankeyData;
|
||||
margin?: ChartMargin;
|
||||
nodeWidth?: number;
|
||||
nodePadding?: number;
|
||||
}
|
||||
|
||||
interface SankeyLayout {
|
||||
nodes: Array<SankeyNodeExtended>;
|
||||
links: Array<SankeyLinkExtended>;
|
||||
}
|
||||
|
||||
interface UseSankeyControllerResult {
|
||||
ref: React.Ref<SVGSVGElement> | undefined;
|
||||
readonly rect: DOMRect | null;
|
||||
readonly innerWidth: number;
|
||||
readonly innerHeight: number;
|
||||
readonly nodes: Array<SankeyNodeExtended>;
|
||||
readonly links: Array<SankeyLinkExtended>;
|
||||
readonly linkPathGenerator: (link: SankeyLinkExtended) => string | null;
|
||||
}
|
||||
|
||||
export function useSankeyController({ data, margin, nodeWidth, nodePadding }: Props): UseSankeyControllerResult {
|
||||
const [ rect, ref ] = useClientRect<SVGSVGElement>();
|
||||
|
||||
const { innerWidth, innerHeight } = calculateInnerSize(rect, margin);
|
||||
|
||||
const layout: SankeyLayout = React.useMemo(() => {
|
||||
if (innerWidth <= 0 || innerHeight <= 0) {
|
||||
return { nodes: [], links: [] };
|
||||
}
|
||||
|
||||
const sankeyGenerator = sankey<SankeyData['nodes'][number], SankeyData['links'][number]>()
|
||||
.nodeId((d) => d.id)
|
||||
.nodeWidth(nodeWidth ?? DEFAULT_SANKEY_NODE_WIDTH)
|
||||
.nodePadding(nodePadding ?? DEFAULT_SANKEY_NODE_PADDING)
|
||||
.nodeAlign(sankeyJustify)
|
||||
.extent([ [ 0, 0 ], [ innerWidth, innerHeight ] ]);
|
||||
|
||||
const { nodes, links } = sankeyGenerator({
|
||||
nodes: data.nodes.map((d) => ({ ...d })),
|
||||
links: data.links.map((d) => ({ ...d })),
|
||||
});
|
||||
|
||||
// After layout, source/target on each link are resolved node objects, not ids.
|
||||
return { nodes: nodes as Array<SankeyNodeExtended>, links: links as unknown as Array<SankeyLinkExtended> };
|
||||
}, [ data, innerWidth, innerHeight, nodeWidth, nodePadding ]);
|
||||
|
||||
const linkPathGenerator: (link: SankeyLinkExtended) => string | null = React.useMemo(() => sankeyLinkHorizontal(), []);
|
||||
|
||||
return React.useMemo(() => ({
|
||||
ref,
|
||||
rect,
|
||||
innerWidth,
|
||||
innerHeight,
|
||||
nodes: layout.nodes,
|
||||
links: layout.links,
|
||||
linkPathGenerator,
|
||||
}), [ ref, rect, innerWidth, innerHeight, layout, linkPathGenerator ]);
|
||||
}
|
||||
@@ -9655,6 +9655,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.0.tgz#939e3a784ae4f80b1fde8098b91af1776ff1312b"
|
||||
integrity sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==
|
||||
|
||||
"@types/d3-path@^1":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.11.tgz#45420fee2d93387083b34eae4fe6d996edf482bc"
|
||||
integrity sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==
|
||||
|
||||
"@types/d3-polygon@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.0.tgz#5200a3fa793d7736fa104285fa19b0dbc2424b93"
|
||||
@@ -9670,6 +9675,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.1.tgz#5c8d42b36cd4c80b92e5626a252f994ca6bfc953"
|
||||
integrity sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==
|
||||
|
||||
"@types/d3-sankey@^0.12.5":
|
||||
version "0.12.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-sankey/-/d3-sankey-0.12.5.tgz#9b79ff5768250cfd8c6340b167c409a0374f40bd"
|
||||
integrity sha512-/3RZSew0cLAtzGQ+C89hq/Rp3H20QJuVRSqFy6RKLe7E0B8kd2iOS1oBsodrgds4PcNVpqWhdUEng/SHvBcJ6Q==
|
||||
dependencies:
|
||||
"@types/d3-shape" "^1"
|
||||
|
||||
"@types/d3-scale-chromatic@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#103124777e8cdec85b20b51fd3397c682ee1e954"
|
||||
@@ -9694,6 +9706,13 @@
|
||||
dependencies:
|
||||
"@types/d3-path" "*"
|
||||
|
||||
"@types/d3-shape@^1":
|
||||
version "1.3.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.12.tgz#8f2f9f7a12e631ce6700d6d55b84795ce2c8b259"
|
||||
integrity sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==
|
||||
dependencies:
|
||||
"@types/d3-path" "^1"
|
||||
|
||||
"@types/d3-time-format@*":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.0.tgz#ee7b6e798f8deb2d9640675f8811d0253aaa1946"
|
||||
@@ -13943,6 +13962,13 @@ csstype@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
||||
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
||||
|
||||
"d3-array@1 - 2":
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
|
||||
integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
|
||||
dependencies:
|
||||
internmap "^1.0.0"
|
||||
|
||||
"d3-array@2 - 3", "[email protected] - 3", "[email protected] - 3", d3-array@3, d3-array@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14"
|
||||
@@ -14059,6 +14085,11 @@ d3-hierarchy@3:
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
d3-path@1:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
||||
|
||||
"d3-path@1 - 3", d3-path@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e"
|
||||
@@ -14079,6 +14110,14 @@ d3-random@3:
|
||||
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4"
|
||||
integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
|
||||
|
||||
d3-sankey@^0.12.3:
|
||||
version "0.12.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-sankey/-/d3-sankey-0.12.3.tgz#b3c268627bd72e5d80336e8de6acbfec9d15d01d"
|
||||
integrity sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==
|
||||
dependencies:
|
||||
d3-array "1 - 2"
|
||||
d3-shape "^1.2.0"
|
||||
|
||||
d3-scale-chromatic@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a"
|
||||
@@ -14110,6 +14149,13 @@ d3-shape@3:
|
||||
dependencies:
|
||||
d3-path "1 - 3"
|
||||
|
||||
d3-shape@^1.2.0:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
|
||||
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
|
||||
dependencies:
|
||||
d3-path "1"
|
||||
|
||||
"d3-time-format@2 - 4", d3-time-format@4:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||
@@ -17084,6 +17130,11 @@ internal-slot@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||
|
||||
internmap@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
|
||||
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
|
||||
|
||||
invariant@^2.2.2:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
|
||||
Reference in New Issue
Block a user