diff --git a/packages/circle-packing/src/CirclePacking.tsx b/packages/circle-packing/src/CirclePacking.tsx index b8d79f278..43482300f 100644 --- a/packages/circle-packing/src/CirclePacking.tsx +++ b/packages/circle-packing/src/CirclePacking.tsx @@ -8,7 +8,7 @@ import { } from '@nivo/core' import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors' import { CirclePackingLayerId, CirclePackingSvgProps, ComputedDatum } from './types' -import { useCirclePacking, useCirclePackingLayerContext } from './hooks' +import { useCirclePacking, useCirclePackingZoom, useCirclePackingLayerContext } from './hooks' import { defaultProps } from './props' import { Circles } from './Circles' import { CircleSvg } from './CircleSvg' @@ -74,7 +74,9 @@ const InnerCirclePacking = ({ childColor, }) - const layerById: Record = { + const zoomedNodes = useCirclePackingZoom(nodes, 'ppie', innerWidth, innerHeight) + + const layerById: Record = { circles: null, labels: null, } @@ -83,7 +85,7 @@ const InnerCirclePacking = ({ layerById.circles = ( key="circles" - nodes={nodes} + nodes={zoomedNodes} isInteractive={isInteractive} onMouseEnter={onMouseEnter} onMouseMove={onMouseMove} @@ -99,7 +101,7 @@ const InnerCirclePacking = ({ layerById.labels = ( key="labels" - nodes={nodes} + nodes={zoomedNodes} label={label} filter={labelsFilter} skipRadius={labelsSkipRadius} diff --git a/packages/circle-packing/src/CirclePackingCanvas.tsx b/packages/circle-packing/src/CirclePackingCanvas.tsx index d9eb0a250..e787f1735 100644 --- a/packages/circle-packing/src/CirclePackingCanvas.tsx +++ b/packages/circle-packing/src/CirclePackingCanvas.tsx @@ -3,7 +3,7 @@ import { useDimensions, useTheme, Container } from '@nivo/core' import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors' import { CirclePackingCanvasProps, ComputedDatum } from './types' import { defaultProps } from './props' -import { useCirclePacking, useCirclePackingLabels } from './hooks' +import { useCirclePacking, useCirclePackingZoom, useCirclePackingLabels } from './hooks' type InnerCirclePackingCanvasProps = Partial< Omit< @@ -60,8 +60,10 @@ const InnerCirclePackingCanvas = ({ childColor, }) + const zoomedNodes = useCirclePackingZoom(nodes, 'node.64', innerWidth, innerHeight) + const labels = useCirclePackingLabels({ - nodes, + nodes: zoomedNodes, label, filter: labelsFilter, skipRadius: labelsSkipRadius, @@ -86,7 +88,7 @@ const InnerCirclePackingCanvas = ({ ctx.save() ctx.translate(margin.left, margin.top) - nodes.forEach(node => { + zoomedNodes.forEach(node => { //if (borderWidth > 0) { // this.ctx.strokeStyle = getBorderColor(node) // this.ctx.lineWidth = borderWidth @@ -122,7 +124,7 @@ const InnerCirclePackingCanvas = ({ margin.left, theme, pixelRatio, - nodes, + zoomedNodes, enableLabels, labels, ]) diff --git a/packages/circle-packing/src/CirclePackingHtml.tsx b/packages/circle-packing/src/CirclePackingHtml.tsx index 0b82c3491..938ad8a7b 100644 --- a/packages/circle-packing/src/CirclePackingHtml.tsx +++ b/packages/circle-packing/src/CirclePackingHtml.tsx @@ -2,7 +2,7 @@ import React, { createElement, Fragment, ReactNode } from 'react' import { Container, useDimensions } from '@nivo/core' import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors' import { CirclePackingHtmlProps, CirclePackingLayerId, ComputedDatum } from './types' -import { useCirclePacking, useCirclePackingLayerContext } from './hooks' +import { useCirclePacking, useCirclePackingLayerContext, useCirclePackingZoom } from './hooks' import { Circles } from './Circles' import { CircleHtml } from './CircleHtml' import { defaultProps } from './props' @@ -68,6 +68,8 @@ export const InnerCirclePackingHtml = ({ childColor, }) + const zoomedNodes = useCirclePackingZoom(nodes, 'ppie', innerWidth, innerHeight) + const layerById: Record = { circles: null, labels: null, @@ -77,7 +79,7 @@ export const InnerCirclePackingHtml = ({ layerById.circles = ( key="circles" - nodes={nodes} + nodes={zoomedNodes} isInteractive={isInteractive} onMouseEnter={onMouseEnter} onMouseMove={onMouseMove} @@ -93,7 +95,7 @@ export const InnerCirclePackingHtml = ({ layerById.labels = ( key="labels" - nodes={nodes} + nodes={zoomedNodes} label={label} filter={labelsFilter} skipRadius={labelsSkipRadius} diff --git a/packages/circle-packing/src/hooks.ts b/packages/circle-packing/src/hooks.ts index 8e638e059..dcdf439d6 100644 --- a/packages/circle-packing/src/hooks.ts +++ b/packages/circle-packing/src/hooks.ts @@ -34,9 +34,7 @@ export const useCirclePacking = >({ colorBy: CirclePackingCommonProps['colorBy'] childColor: CirclePackingCommonProps['childColor'] }): ComputedDatum[] => { - console.log('compute nodes') - - const getId = usePropertyAccessor(id) + const getId = usePropertyAccessor(id) const getValue = usePropertyAccessor(value) const getColor = useOrdinalColorScale, 'color' | 'fill'>>( @@ -105,6 +103,29 @@ export const useCirclePacking = >({ return computedNodes } +export const useCirclePackingZoom = ( + nodes: ComputedDatum[], + zoomedId: string, + width: number, + height: number +) => + useMemo(() => { + const zoomedNode = nodes.find(({ id }) => id === zoomedId) + + if (!zoomedNode) return nodes + + const ratio = Math.min(width, height) / (zoomedNode.radius * 2) + const offsetX = width / 2 - zoomedNode.x * ratio + const offsetY = height / 2 - zoomedNode.y * ratio + + return nodes.map(node => ({ + ...node, + x: node.x * ratio + offsetX, + y: node.y * ratio + offsetY, + radius: node.radius * ratio, + })) + }, [nodes, zoomedId, width, height]) + export const useCirclePackingLabels = ({ nodes, label, diff --git a/packages/circle-packing/src/types.ts b/packages/circle-packing/src/types.ts index 8d1ea7327..4b4ff4fc5 100644 --- a/packages/circle-packing/src/types.ts +++ b/packages/circle-packing/src/types.ts @@ -9,14 +9,10 @@ import { } from '@nivo/core' import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors' -export interface DatumWithChildren> { - children?: RawDatum[] -} - -export interface ComputedDatum> { - id: string | number +export interface ComputedDatum { + id: string // contain own id plus all ancestor ids - path: (string | number)[] + path: string[] value: number percentage: number formattedValue: string @@ -55,15 +51,15 @@ export type MouseHandlers = { export interface CirclePackingCommonProps { data: RawDatum - - id: PropertyAccessor + id: PropertyAccessor value: PropertyAccessor valueFormat?: ValueFormat - + width: number + height: number + margin?: Box padding: number leavesOnly: boolean - - theme: Theme + theme?: Theme colors: OrdinalColorScaleConfig, 'color' | 'fill'>> colorBy: 'id' | 'depth' // if specified, will determine the node's color @@ -72,22 +68,17 @@ export interface CirclePackingCommonProps { borderWidth: number borderColor: InheritedColorConfig> circleComponent: CircleComponent - enableLabels: boolean - label: PropertyAccessor, string | number> + label: PropertyAccessor, string> labelsFilter?: (label: ComputedLabel) => boolean labelsSkipRadius: number labelsTextColor: InheritedColorConfig> labelsComponent: LabelComponent - layers: CirclePackingLayer[] - isInteractive: boolean tooltip: (props: ComputedDatum) => JSX.Element - animate: boolean motionConfig: ModernMotionProps['motionConfig'] - role: string }