From 458ba58b2b01245284970c2a7740cee78b17862a Mon Sep 17 00:00:00 2001 From: plouc Date: Sat, 1 Jan 2022 15:22:16 +0900 Subject: [PATCH] feat(chord): migrate ribbons and arcs transitions to react-spring --- packages/chord/_old_index.d.ts | 108 ----------- packages/chord/package.json | 1 + packages/chord/src/Chord.tsx | 6 +- packages/chord/src/ChordArc.tsx | 6 +- packages/chord/src/ChordArcs.tsx | 4 +- packages/chord/src/ChordCanvas.tsx | 11 +- packages/chord/src/ChordLabels.tsx | 168 +++++++++------- packages/chord/src/ChordRibbon.tsx | 54 +++--- packages/chord/src/ChordRibbons.tsx | 215 ++++++++------------- packages/chord/src/compute.ts | 123 +++++++++--- packages/chord/src/defaults.ts | 4 - packages/chord/src/hooks.ts | 13 +- packages/chord/src/types.ts | 55 ++++-- website/src/data/components/chord/props.ts | 16 +- website/src/pages/chord/index.tsx | 8 +- 15 files changed, 382 insertions(+), 410 deletions(-) delete mode 100644 packages/chord/_old_index.d.ts diff --git a/packages/chord/_old_index.d.ts b/packages/chord/_old_index.d.ts deleted file mode 100644 index 8993dba401..0000000000 --- a/packages/chord/_old_index.d.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaƫl Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { Component } from 'react' -import { Box, MotionProps, Dimensions, Theme } from '@nivo/core' -import { OrdinalColorScaleConfig, InheritedColorConfig } from '@nivo/colors' -import { LegendProps } from '@nivo/legends' - -type Omit = Pick> - -declare module '@nivo/chord' { - export interface ArcData { - id: string - index: number - label: string - value: number - formattedValue: number | string - startAngle: number - endAngle: number - color: string - } - - export interface RibbonSubject extends ArcData { - subindex: number - } - - export interface RibbonData { - id: string - source: RibbonSubject - target: RibbonSubject - } - - export type ChordArcMouseHandler = (arc: ArcData, event: React.MouseEvent) => void - - export type ChordRibbonMouseHandler = (ribbon: RibbonData, event: React.MouseEvent) => void - - type LabelAccessor = (datum: Omit) => string - type ValueFormatter = (datum: Omit) => string | number - - interface CommonChordProps { - keys: string[] - matrix: number[][] - label?: string | LabelAccessor - valueFormat?: string | ValueFormatter - - margin?: Box - - padAngle?: number - innerRadiusRatio?: number - innerRadiusOffset?: number - - layers: any[] - - colors?: OrdinalColorScaleConfig - theme?: Theme - - arcOpacity?: number - arcHoverOpacity?: number - arcHoverOthersOpacity?: number - arcBorderWidth?: number - arcBorderColor?: InheritedColorConfig - onArcMouseEnter?: ChordArcMouseHandler - onArcMouseMove?: ChordArcMouseHandler - onArcMouseLeave?: ChordArcMouseHandler - onArcClick?: ChordArcMouseHandler - arcTooltip?: any - - ribbonOpacity?: number - ribbonHoverOpacity?: number - ribbonHoverOthersOpacity?: number - ribbonBorderWidth?: number - ribbonBorderColor?: InheritedColorConfig - - enableLabel?: boolean - labelOffset?: number - labelRotation?: number - labelTextColor?: InheritedColorConfig - - isInteractive?: boolean - - legends?: LegendProps[] - } - - export type ChordProps = CommonChordProps & - MotionProps & { - onRibbonMouseEnter?: ChordRibbonMouseHandler - onRibbonMouseMove?: ChordRibbonMouseHandler - onRibbonMouseLeave?: ChordRibbonMouseHandler - onRibbonClick?: ChordRibbonMouseHandler - ribbonTooltip?: any - role?: string - } - - export class Chord extends Component {} - export class ResponsiveChord extends Component {} - - export type ChordCanvasProps = CommonChordProps & { - pixelRatio?: number - } - - export class ChordCanvas extends Component {} - export class ResponsiveChordCanvas extends Component {} -} diff --git a/packages/chord/package.json b/packages/chord/package.json index 7623a38d6e..865ec52129 100644 --- a/packages/chord/package.json +++ b/packages/chord/package.json @@ -33,6 +33,7 @@ "@nivo/colors": "0.77.0", "@nivo/legends": "0.77.0", "@nivo/tooltip": "0.77.0", + "@react-spring/web": "9.3.1", "d3-chord": "^1.0.6", "d3-shape": "^1.3.5", "lodash": "^4.17.21", diff --git a/packages/chord/src/Chord.tsx b/packages/chord/src/Chord.tsx index ba09631b6e..47b8eab875 100644 --- a/packages/chord/src/Chord.tsx +++ b/packages/chord/src/Chord.tsx @@ -97,9 +97,7 @@ const InnerChord = ({ }) const theme = useTheme() - const getLabelTextColor = useInheritedColor(labelTextColor, theme) const getArcBorderColor = useInheritedColor(arcBorderColor, theme) - const getRibbonBorderColor = useInheritedColor(ribbonBorderColor, theme) const customLayerProps = useCustomLayerProps({ center, @@ -132,7 +130,7 @@ const InnerChord = ({ ribbons={ribbons} ribbonGenerator={ribbonGenerator} borderWidth={ribbonBorderWidth} - getBorderColor={getRibbonBorderColor} + borderColor={ribbonBorderColor} getOpacity={getRibbonOpacity} blendMode={ribbonBlendMode} setCurrent={setCurrentRibbon} @@ -175,7 +173,7 @@ const InnerChord = ({ arcs={arcs} radius={radius + labelOffset} rotation={labelRotation} - getColor={getLabelTextColor} + color={labelTextColor} /> ) diff --git a/packages/chord/src/ChordArc.tsx b/packages/chord/src/ChordArc.tsx index b85bea1e05..36064e9fbe 100644 --- a/packages/chord/src/ChordArc.tsx +++ b/packages/chord/src/ChordArc.tsx @@ -1,12 +1,12 @@ import { createElement, memo, useMemo, MouseEvent } from 'react' import { useTooltip } from '@nivo/tooltip' -import { ArcDatum, ChordCommonProps } from './types' +import { ArcDatum, ArcGenerator, ChordCommonProps } from './types' interface ChordArcProps { arc: ArcDatum startAngle: number endAngle: number - arcGenerator: any + arcGenerator: ArcGenerator borderWidth: number getBorderColor: (arc: ArcDatum) => string opacity: number @@ -75,7 +75,7 @@ export const ChordArc = memo( return ( string getOpacity: (arc: ArcDatum) => number diff --git a/packages/chord/src/ChordCanvas.tsx b/packages/chord/src/ChordCanvas.tsx index 190450d4b1..6583128372 100644 --- a/packages/chord/src/ChordCanvas.tsx +++ b/packages/chord/src/ChordCanvas.tsx @@ -2,7 +2,9 @@ import { createElement, useRef, useEffect, useCallback, MouseEvent } from 'react import { useDimensions, useTheme, + // @ts-ignore midAngle, + // @ts-ignore getPolarLabelProps, degreesToRadians, getRelativeCursor, @@ -38,7 +40,7 @@ const getArcFromMouseEvent = ({ const centerX = margin.left + center[0] const centerY = margin.top + center[1] - return findArcUnderCursor(centerX, centerY, radius, innerRadius, arcs, x, y) + return findArcUnderCursor(centerX, centerY, radius, innerRadius, arcs as any[], x, y) } type InnerChordCanvasProps = Omit @@ -156,10 +158,7 @@ const InnerChordCanvas = ({ ctx.fill() if (ribbonBorderWidth > 0) { - ctx.strokeStyle = getRibbonBorderColor({ - ...ribbon, - color: ribbon.source.color, - }) + ctx.strokeStyle = getRibbonBorderColor(ribbon.source) ctx.lineWidth = ribbonBorderWidth ctx.stroke() } @@ -214,7 +213,7 @@ const InnerChordCanvas = ({ ctx.textAlign = props.align ctx.textBaseline = props.baseline - ctx.fillStyle = getLabelTextColor(arc, theme) + ctx.fillStyle = getLabelTextColor(arc) ctx.fillText(arc.label, 0, 0) ctx.restore() diff --git a/packages/chord/src/ChordLabels.tsx b/packages/chord/src/ChordLabels.tsx index ae7194e20d..f7aba47a52 100644 --- a/packages/chord/src/ChordLabels.tsx +++ b/packages/chord/src/ChordLabels.tsx @@ -1,86 +1,116 @@ -import { memo } from 'react' -import { TransitionMotion, spring } from 'react-motion' -import { midAngle, getPolarLabelProps, useTheme } from '@nivo/core' +import { memo, useMemo } from 'react' +import { useTransition, animated, to } from '@react-spring/web' +import { + // @ts-ignore + midAngle, + // @ts-ignore + getPolarLabelProps, + useTheme, +} from '@nivo/core' import { useMotionConfig } from '@nivo/core' -import { ArcDatum } from './types' +import { ArcDatum, ChordCommonProps } from './types' +import { useInheritedColor } from '@nivo/colors' interface ChordLabelsProps { arcs: ArcDatum[] radius: number rotation: number - getColor: (arc: ArcDatum) => string + color: ChordCommonProps['labelTextColor'] } -export const ChordLabels = memo(({ arcs, radius, rotation, getColor }: ChordLabelsProps) => { - const theme = useTheme() - const { animate, springConfig } = useMotionConfig() - - if (animate) { - return ( - <> - {arcs.map(arc => { - const color = getColor(arc, theme) - const angle = midAngle(arc) - const textProps = getPolarLabelProps(radius, angle, rotation) +export const ChordLabels = memo(({ arcs, radius, rotation, color }: ChordLabelsProps) => { + const { animate, config: springConfig } = useMotionConfig() - return ( - - {arc.label} - - ) - })} - - ) - } + const theme = useTheme() + const getColor = useInheritedColor(color, theme) - return ( - { + const labels = useMemo( + () => + arcs.map(arc => { const angle = midAngle(arc) + const textProps = getPolarLabelProps(radius, angle, rotation) return { - key: arc.id, - data: arc, - style: { - angle: spring(angle, springConfig), - }, + id: arc.id, + label: arc.label, + x: textProps.x, + y: textProps.y, + rotation: textProps.rotate, + color: getColor(arc), + textAnchor: textProps.align, + dominantBaseline: textProps.baseline, } - })} - > - {interpolatedStyles => ( - <> - {interpolatedStyles.map(({ key, style, data: arc }) => { - const color = getColor(arc, theme) - const textProps = getPolarLabelProps(radius, style.angle, rotation) + }), + [arcs, radius, radius, rotation, getColor] + ) + + const transition = useTransition< + typeof labels[number], + { + x: number + y: number + rotation: number + color: string + } + >(labels, { + keys: label => label.id, + initial: label => { + return { + x: label.x, + y: label.y, + rotation: label.rotation, + color: label.color, + } + }, + from: label => { + return { + x: label.x, + y: label.y, + rotation: label.rotation, + color: label.color, + } + }, + enter: label => { + return { + x: label.x, + y: label.y, + rotation: label.rotation, + color: label.color, + } + }, + update: label => { + return { + x: label.x, + y: label.y, + rotation: label.rotation, + color: label.color, + } + }, + expires: true, + config: springConfig, + immediate: !animate, + }) - return ( - - {arc.label} - - ) - })} - - )} - + return ( + <> + {transition((transitionProps, label) => ( + `translate(${x}, ${y}) rotate(${rotation})` + )} + textAnchor={label.textAnchor} + dominantBaseline={label.dominantBaseline} + > + {label.label} + + ))} + ) }) diff --git a/packages/chord/src/ChordRibbon.tsx b/packages/chord/src/ChordRibbon.tsx index 0ea6644826..e434191d50 100644 --- a/packages/chord/src/ChordRibbon.tsx +++ b/packages/chord/src/ChordRibbon.tsx @@ -1,19 +1,21 @@ import { createElement, memo, useMemo, MouseEvent } from 'react' +import { SpringValues, animated } from '@react-spring/web' import { useTooltip } from '@nivo/tooltip' -import { ChordCommonProps, ChordSvgProps, RibbonDatum } from './types' +import { + ChordCommonProps, + ChordSvgProps, + RibbonAnimatedProps, + RibbonDatum, + RibbonGenerator, +} from './types' +import { computeRibbonPath } from './compute' interface ChordRibbonProps { ribbon: RibbonDatum - ribbonGenerator: any - sourceStartAngle: number - sourceEndAngle: number - targetStartAngle: number - targetEndAngle: number - color: string + ribbonGenerator: RibbonGenerator + animatedProps: SpringValues + borderWidth: ChordCommonProps['ribbonBorderWidth'] blendMode: NonNullable - opacity: number - borderWidth: number - getBorderColor: (ribbon: RibbonDatum) => string setCurrent: (ribbon: RibbonDatum | null) => void isInteractive: ChordCommonProps['isInteractive'] tooltip: NonNullable @@ -27,14 +29,8 @@ export const ChordRibbon = memo( ({ ribbon, ribbonGenerator, - sourceStartAngle, - sourceEndAngle, - targetStartAngle, - targetEndAngle, - color, - opacity, + animatedProps, borderWidth, - getBorderColor, blendMode, isInteractive, setCurrent, @@ -82,22 +78,18 @@ export const ChordRibbon = memo( }, [isInteractive, ribbon, onClick]) return ( - { - let firstArc - let secondArc +const getRibbonAngles = ({ source, target }: RibbonDatum, useMiddleAngle: boolean) => { + let firstArc: ArcDatum + let secondArc: ArcDatum if (source.startAngle < target.startAngle) { firstArc = source secondArc = target @@ -29,50 +34,31 @@ const getRibbonAngles = ({ source, target }, useMiddleAngle, springConfig) => { secondArc = source } - let angles - if (useMiddleAngle === true) { + if (useMiddleAngle) { const firstMiddleAngle = midAngle(firstArc) const secondMiddleAngle = midAngle(secondArc) - angles = { + return { sourceStartAngle: firstMiddleAngle, sourceEndAngle: firstMiddleAngle, targetStartAngle: secondMiddleAngle, targetEndAngle: secondMiddleAngle, } - } else { - angles = { - sourceStartAngle: firstArc.startAngle, - sourceEndAngle: firstArc.endAngle, - targetStartAngle: secondArc.startAngle, - targetEndAngle: secondArc.endAngle, - } } - if (!springConfig) return angles - - return mapValues(angles, angle => spring(angle, springConfig)) + return { + sourceStartAngle: firstArc.startAngle, + sourceEndAngle: firstArc.endAngle, + targetStartAngle: secondArc.startAngle, + targetEndAngle: secondArc.endAngle, + } } -const ribbonWillEnter = ({ data: ribbon }) => ({ - ...getRibbonAngles(ribbon, true), - opacity: 0, - ...interpolateColor(ribbon.source.color), -}) - -const ribbonWillLeave = - springConfig => - ({ data: ribbon }) => ({ - ...getRibbonAngles(ribbon, true, springConfig), - opacity: 0, - ...interpolateColor(ribbon.source.color, springConfig), - }) - interface ChordRibbonsProps { ribbons: RibbonDatum[] - ribbonGenerator: any + ribbonGenerator: RibbonGenerator borderWidth: ChordCommonProps['ribbonBorderWidth'] - getBorderColor: (ribbon: RibbonDatum) => string + borderColor: ChordCommonProps['ribbonBorderColor'] getOpacity: (ribbon: RibbonDatum) => number blendMode: NonNullable isInteractive: ChordCommonProps['isInteractive'] @@ -89,7 +75,7 @@ export const ChordRibbons = memo( ribbons, ribbonGenerator, borderWidth, - getBorderColor, + borderColor, getOpacity, blendMode, isInteractive, @@ -100,99 +86,62 @@ export const ChordRibbons = memo( onClick, tooltip, }: ChordRibbonsProps) => { - const { animate, springConfig: _springConfig } = useMotionConfig() + const { animate, config: springConfig } = useMotionConfig() - if (animate !== true) { - return ( - - {ribbons.map(ribbon => { - return ( - - ) - })} - - ) - } + const theme = useTheme() + const getBorderColor = useInheritedColor(borderColor, theme) - const springConfig = { - ..._springConfig, - precision: 0.001, - } + const transition = useTransition(ribbons, { + keys: ribbon => ribbon.id, + initial: ribbon => ({ + ...getRibbonAngles(ribbon, false), + color: ribbon.source.color, + opacity: getOpacity(ribbon), + borderColor: getBorderColor(ribbon.source), + }), + from: ribbon => ({ + ...getRibbonAngles(ribbon, false), + color: ribbon.source.color, + opacity: 0, + borderColor: getBorderColor(ribbon.source), + }), + update: ribbon => ({ + ...getRibbonAngles(ribbon, false), + color: ribbon.source.color, + opacity: getOpacity(ribbon), + borderColor: getBorderColor(ribbon.source), + }), + leave: ribbon => ({ + ...getRibbonAngles(ribbon, false), + color: ribbon.source.color, + opacity: 0, + borderColor: getBorderColor(ribbon.source), + }), + expires: true, + config: springConfig, + immediate: !animate, + }) return ( - { - return { - key: ribbon.id, - data: ribbon, - style: { - ...getRibbonAngles(ribbon, false, springConfig), - opacity: spring(getOpacity(ribbon), springConfig), - ...interpolateColor(ribbon.source.color, springConfig), - }, - } - })} - > - {interpolatedStyles => ( - <> - {interpolatedStyles.map(({ key, style, data: ribbon }) => { - const color = getInterpolatedColor(style) - - return ( - - ) - })} - - )} - + <> + {transition((animatedProps, ribbon) => ( + + ))} + ) } ) diff --git a/packages/chord/src/compute.ts b/packages/chord/src/compute.ts index a571b73bf1..f8466e0dff 100644 --- a/packages/chord/src/compute.ts +++ b/packages/chord/src/compute.ts @@ -1,6 +1,15 @@ +import { to, SpringValues } from '@react-spring/web' import { arc as d3Arc } from 'd3-shape' import { chord as d3Chord, ChordLayout, ribbon as d3Ribbon } from 'd3-chord' -import { ArcDatum, ChordCommonProps, ChordDataProps } from './types' +import { + ArcDatum, + ChordCommonProps, + ChordDataProps, + RibbonAnimatedProps, + RibbonDatum, + RibbonGenerator, + ArcGenerator, +} from './types' import { OrdinalColorScale } from '@nivo/colors' export const computeChordLayout = ({ padAngle }: { padAngle: ChordCommonProps['padAngle'] }) => @@ -17,16 +26,20 @@ export const computeChordGenerators = ({ innerRadiusRatio: ChordCommonProps['innerRadiusRatio'] innerRadiusOffset: ChordCommonProps['innerRadiusOffset'] }) => { - const center = [width / 2, height / 2] + const center: [number, number] = [width / 2, height / 2] const radius = Math.min(width, height) / 2 const innerRadius = radius * innerRadiusRatio const ribbonRadius = radius * (innerRadiusRatio - innerRadiusOffset) - const arcGenerator = d3Arc().outerRadius(radius).innerRadius(innerRadius) - - const ribbonGenerator = d3Ribbon().radius(ribbonRadius) - - return { center, radius, innerRadius, arcGenerator, ribbonGenerator } + return { + center, + radius, + innerRadius, + arcGenerator: d3Arc() + .outerRadius(radius) + .innerRadius(innerRadius) as unknown as ArcGenerator, + ribbonGenerator: d3Ribbon().radius(ribbonRadius) as unknown as RibbonGenerator, + } } export const computeChordArcsAndRibbons = ({ @@ -40,35 +53,87 @@ export const computeChordArcsAndRibbons = ({ chord: ChordLayout data: ChordDataProps['data'] keys: ChordDataProps['keys'] - getLabel: (arc: ArcDatum) => string - formatValue: (valuee: number) => string - getColor: OrdinalColorScale -}) => { - const ribbons = chord(data) + getLabel: (arc: Omit) => string + formatValue: (value: number) => string + getColor: OrdinalColorScale> +}): { + arcs: ArcDatum[] + ribbons: RibbonDatum[] +} => { + const _ribbons = chord(data) - const arcs = ribbons.groups.map(arc => { - arc.id = keys[arc.index] - arc.color = getColor(arc) - arc.formattedValue = formatValue(arc.value) - arc.label = getLabel(arc) + const arcs: ArcDatum[] = _ribbons.groups.map(chordGroup => { + const arc: Omit = { + ...chordGroup, + id: keys[chordGroup.index], + formattedValue: formatValue(chordGroup.value), + } - return arc + return { + ...arc, + label: getLabel(arc), + color: getColor(arc), + } }) - ribbons.forEach(ribbon => { - ribbon.source.id = keys[ribbon.source.index] - ribbon.source.color = getColor(ribbon.source) - ribbon.source.formattedValue = formatValue(ribbon.source.value) - ribbon.source.label = getLabel(ribbon.source) + const ribbons: RibbonDatum[] = _ribbons.map(_ribbon => { + const source = { + ..._ribbon.source, + id: keys[_ribbon.source.index], + formattedValue: formatValue(_ribbon.source.value), + } - ribbon.target.id = keys[ribbon.target.index] - ribbon.target.color = getColor(ribbon.target) - ribbon.target.formattedValue = formatValue(ribbon.target.value) - ribbon.target.label = getLabel(ribbon.target) + const target = { + ..._ribbon.target, + id: keys[_ribbon.target.index], + formattedValue: formatValue(_ribbon.target.value), + } - // ensure id remains the same even if source/target are reversed - ribbon.id = [ribbon.source.id, ribbon.target.id].sort().join('.') + return { + ..._ribbon, + // ensure id remains the same even if source/target are reversed + id: [source.id, target.id].sort().join('.'), + source: { + ...source, + label: getLabel(source), + color: getColor(source), + }, + target: { + ...target, + label: getLabel(target), + color: getColor(target), + }, + } }) return { arcs, ribbons } } + +export const computeRibbonPath = ({ + sourceStartAngle, + sourceEndAngle, + targetStartAngle, + targetEndAngle, + ribbonGenerator, +}: SpringValues< + Pick< + RibbonAnimatedProps, + 'sourceStartAngle' | 'sourceEndAngle' | 'targetStartAngle' | 'targetEndAngle' + > +> & { + ribbonGenerator: RibbonGenerator +}) => + to( + [sourceStartAngle, sourceEndAngle, targetStartAngle, targetEndAngle], + (sourceStartAngle, sourceEndAngle, targetStartAngle, targetEndAngle) => + ribbonGenerator({ + source: { + startAngle: Math.min(sourceStartAngle, sourceEndAngle), + endAngle: Math.max(sourceEndAngle, sourceStartAngle), + }, + target: { + startAngle: Math.min(targetStartAngle, targetEndAngle), + endAngle: Math.max(targetEndAngle, targetStartAngle), + }, + }) + ) diff --git a/packages/chord/src/defaults.ts b/packages/chord/src/defaults.ts index f642d6c175..271dfcc22b 100644 --- a/packages/chord/src/defaults.ts +++ b/packages/chord/src/defaults.ts @@ -72,15 +72,11 @@ export const commonDefaultProps: Omit< export const svgDefaultProps = { ...commonDefaultProps, - // arcComponent: - // ribbonComponent ribbonBlendMode: 'normal' as NonNullable, ribbonTooltip: ChordRibbonTooltip, } export const canvasDefaultProps = { ...commonDefaultProps, - // renderArc - // renderRibbon pixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, } diff --git a/packages/chord/src/hooks.ts b/packages/chord/src/hooks.ts index 2b54429a6f..47fc591251 100644 --- a/packages/chord/src/hooks.ts +++ b/packages/chord/src/hooks.ts @@ -1,9 +1,14 @@ import { useMemo, useState } from 'react' -import { useValueFormatter, getLabelGenerator } from '@nivo/core' +import { + useValueFormatter, + // @ts-ignore + getLabelGenerator, +} from '@nivo/core' import { OrdinalColorScale, useOrdinalColorScale } from '@nivo/colors' import { computeChordLayout, computeChordGenerators, computeChordArcsAndRibbons } from './compute' import { ArcDatum, ChordCommonProps, ChordDataProps, CustomLayerProps, RibbonDatum } from './types' import { commonDefaultProps } from './defaults' +import { ChordLayout } from 'd3-chord' export const useChordLayout = ({ padAngle }: { padAngle: ChordCommonProps['padAngle'] }) => useMemo(() => computeChordLayout({ padAngle }), [padAngle]) @@ -38,12 +43,12 @@ export const useChordArcsAndRibbons = ({ getLabel, formatValue, }: { - chord: any + chord: ChordLayout data: ChordDataProps['data'] keys: ChordDataProps['keys'] - getLabel: (arc: ArcDatum) => string + getLabel: (arc: Omit) => string formatValue: (value: number) => string - getColor: OrdinalColorScale + getColor: OrdinalColorScale> }) => useMemo( () => diff --git a/packages/chord/src/types.ts b/packages/chord/src/types.ts index debb50b716..2e00df60cb 100644 --- a/packages/chord/src/types.ts +++ b/packages/chord/src/types.ts @@ -1,4 +1,6 @@ import { AriaAttributes, MouseEvent, FunctionComponent } from 'react' +import { RibbonGenerator as D3RibbonGenerator } from 'd3-chord' +import { Arc as D3Arc } from 'd3-shape' import { Box, Theme, @@ -39,16 +41,47 @@ export interface ArcDatum { color: string } -export interface RibbonSubject extends ArcDatum { - subindex: number -} - export interface RibbonDatum { id: string - source: RibbonSubject - target: RibbonSubject + source: ArcDatum + target: ArcDatum +} + +export interface RibbonAnimatedProps { + sourceStartAngle: number + sourceEndAngle: number + targetStartAngle: number + targetEndAngle: number + color: string + opacity: number + borderColor: string } +export type RibbonGenerator = D3RibbonGenerator< + any, + | RibbonDatum + | { + source: { + startAngle: number + endAngle: number + } + target: { + startAngle: number + endAngle: number + } + }, + RibbonDatum +> + +export type ArcGenerator = D3Arc< + any, + | ArcDatum + | { + startAngle: number + endAngle: number + } +> + export interface ArcTooltipComponentProps { arc: ArcDatum } @@ -66,7 +99,7 @@ export type ChordRibbonMouseHandler = (ribbon: any, event: MouseEvent) => void export type ChordCommonProps = { margin: Box - label: PropertyAccessor + label: PropertyAccessor, string> valueFormat: ValueFormat padAngle: number @@ -74,13 +107,13 @@ export type ChordCommonProps = { innerRadiusOffset: number theme: Theme - colors: OrdinalColorScaleConfig + colors: OrdinalColorScaleConfig> arcOpacity: number arcHoverOpacity: number arcHoverOthersOpacity: number arcBorderWidth: number - arcBorderColor: InheritedColorConfig + arcBorderColor: InheritedColorConfig onArcMouseEnter: ChordArcMouseHandler onArcMouseMove: ChordArcMouseHandler onArcMouseLeave: ChordArcMouseHandler @@ -92,12 +125,12 @@ export type ChordCommonProps = { ribbonHoverOpacity: number ribbonHoverOthersOpacity: number ribbonBorderWidth: number - ribbonBorderColor: InheritedColorConfig + ribbonBorderColor: InheritedColorConfig enableLabel: boolean labelOffset: number labelRotation: number - labelTextColor: InheritedColorConfig + labelTextColor: InheritedColorConfig isInteractive: boolean defaultActiveNodeIds: string[] diff --git a/website/src/data/components/chord/props.ts b/website/src/data/components/chord/props.ts index 2e3e23afb9..9b4b962846 100644 --- a/website/src/data/components/chord/props.ts +++ b/website/src/data/components/chord/props.ts @@ -5,7 +5,12 @@ import { groupProperties, getLegendsProps, } from '../../../lib/componentProperties' -import { chartDimensions, ordinalColors, isInteractive } from '../../../lib/chart-properties' +import { + chartDimensions, + ordinalColors, + isInteractive, + blendMode, +} from '../../../lib/chart-properties' import { ChartProperty, Flavor } from '../../../types' const allFlavors: Flavor[] = ['svg', 'canvas', 'api'] @@ -143,6 +148,13 @@ const props: ChartProperty[] = [ control: { type: 'inheritedColor' }, group: 'Style', }, + blendMode({ + key: 'ribbonBlendMode', + target: 'ribbons', + group: 'Style', + flavors: ['svg'], + defaultValue: defaults.ribbonBlendMode, + }), { key: 'ribbonOpacity', help: 'Ribbons opacity.', @@ -436,7 +448,7 @@ const props: ChartProperty[] = [ }, }, }, - ...motionProperties(['svg'], defaults), + ...motionProperties(['svg'], defaults, 'react-spring'), ] export const groups = groupProperties(props) diff --git a/website/src/pages/chord/index.tsx b/website/src/pages/chord/index.tsx index c46ffdfd45..5dbe17cd3c 100644 --- a/website/src/pages/chord/index.tsx +++ b/website/src/pages/chord/index.tsx @@ -27,14 +27,15 @@ const initialProperties = { arcBorderWidth: 1, arcBorderColor: { from: 'color', - modifiers: [['darker', 0.4]], + modifiers: [['darker', 0.6]], }, + ribbonBlendMode: 'normal', ribbonOpacity: 0.5, ribbonBorderWidth: 1, ribbonBorderColor: { from: 'color', - modifiers: [['darker', 0.4]], + modifiers: [['darker', 0.6]], }, enableLabel: true, @@ -55,8 +56,7 @@ const initialProperties = { ribbonHoverOthersOpacity: 0.25, animate: true, - motionStiffness: 90, - motionDamping: 7, + motionConfig: 'stiff', legends: [ {