From b13b20b0922889f8d2457c4497ae2e1056dd40c4 Mon Sep 17 00:00:00 2001 From: "Serhii.Tsybulskyi" Date: Thu, 21 Nov 2024 16:23:50 +0200 Subject: [PATCH] add ability to render custom cluster --- docs/demos/Cluster.story.tsx | 50 +++++++++++---- src/GraphScene.tsx | 12 ++-- src/symbols/Cluster.tsx | 121 +++++++++++++++++++---------------- src/types.ts | 22 ++++--- 4 files changed, 122 insertions(+), 83 deletions(-) diff --git a/docs/demos/Cluster.story.tsx b/docs/demos/Cluster.story.tsx index 0972c83a..e1cc2d53 100644 --- a/docs/demos/Cluster.story.tsx +++ b/docs/demos/Cluster.story.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useState } from 'react'; import { Billboard, Svg, Text } from 'glodrei'; -import { Color } from 'three'; +import { Color, DoubleSide } from 'three'; +import { a } from '@react-spring/three'; import { GraphCanvas, Icon, lightTheme, Sphere } from '../../src'; import { clusterNodes, @@ -13,7 +14,6 @@ import { import demonSvg from '../../docs/assets/twitter.svg'; - export default { title: 'Demos/Cluster', component: GraphCanvas @@ -467,18 +467,40 @@ export const CustomLabel = () => ( draggable edges={clusterEdges} clusterAttribute="type" - renderClusterLabel={({ label, opacity, fontUrl }) => ( - - - - Custom {label} - - + onRenderCluster={({ + label, + opacity, + offset, + rad, + padding, + }) => ( + <> + + + + + + + + + Custom {label.text} + + + + )} /> ); diff --git a/src/GraphScene.tsx b/src/GraphScene.tsx index 731b1bd1..4e6ffe8d 100644 --- a/src/GraphScene.tsx +++ b/src/GraphScene.tsx @@ -19,7 +19,7 @@ import { InternalGraphNode, NodeRenderer, CollapseProps, - ClusterLabelRenderer + ClusterRenderer } from './types'; import { SizingType } from './sizing'; import { @@ -161,9 +161,9 @@ export interface GraphSceneProps { renderNode?: NodeRenderer; /** - * Render a custom cluster label + * Render a custom cluster */ - renderClusterLabel?: ClusterLabelRenderer; + onRenderCluster?: ClusterRenderer; /** * Advanced overrides for the layout. @@ -346,7 +346,7 @@ export const GraphScene: FC }> = edgeInterpolation = 'linear', labelFontUrl, renderNode, - renderClusterLabel, + onRenderCluster, ...rest }, ref @@ -512,7 +512,7 @@ export const GraphScene: FC }> = onPointerOver={onClusterPointerOver} onPointerOut={onClusterPointerOut} onDragged={onClusterDragged} - renderLabel={renderClusterLabel} + onRender={onRenderCluster} {...c} /> )), @@ -526,7 +526,7 @@ export const GraphScene: FC }> = onClusterPointerOut, onClusterPointerOver, onClusterDragged, - renderClusterLabel + onRenderCluster ] ); diff --git a/src/symbols/Cluster.tsx b/src/symbols/Cluster.tsx index 04380625..709f230b 100644 --- a/src/symbols/Cluster.tsx +++ b/src/symbols/Cluster.tsx @@ -1,6 +1,6 @@ import React, { FC, useMemo, useState } from 'react'; import { ClusterGroup, animationConfig, useHoverIntent } from '../utils'; -import { useSpring, a } from '@react-spring/three'; +import { useSpring, a, SpringValue } from '@react-spring/three'; import { Color, DoubleSide } from 'three'; import { useStore } from '../store'; import { Label } from './Label'; @@ -9,7 +9,7 @@ import { ThreeEvent } from '@react-three/fiber'; import { useDrag } from '../utils/useDrag'; import { Vector3 } from 'three'; import { useCameraControls } from '../CameraControls'; -import { ClusterLabelRenderer } from '../types'; +import { ClusterRenderer } from '../types'; export type ClusterEventArgs = Omit; @@ -73,7 +73,7 @@ export interface ClusterProps extends ClusterGroup { /** * Render a custom cluster label */ - renderLabel?: ClusterLabelRenderer; + onRender?: ClusterRenderer; } export const Cluster: FC = ({ @@ -90,7 +90,7 @@ export const Cluster: FC = ({ onPointerOut, draggable = false, onDragged, - renderLabel + onRender }) => { const theme = useStore(state => state.theme); const rad = Math.max(position.width, position.height) / 2; @@ -136,13 +136,15 @@ export const Cluster: FC = ({ const { circleOpacity, circlePosition, labelPosition } = useSpring({ from: { - circlePosition: [center.x, center.y, -1], + circlePosition: [center.x, center.y, -1] as [number, number, number], circleOpacity: 0, - labelPosition: labelPositionOffset + labelPosition: labelPositionOffset as [number, number, number] }, to: { - labelPosition: labelPositionOffset, - circlePosition: position ? [position.x, position.y, -1] : [0, 0, -1], + labelPosition: labelPositionOffset as [number, number, number], + circlePosition: position + ? ([position.x, position.y, -1] as [number, number, number]) + : ([0, 0, -1] as [number, number, number]), circleOpacity: opacity }, config: { @@ -238,54 +240,63 @@ export const Cluster: FC = ({ }} {...(bind() as any)} > - - - - - - - - - {theme.cluster?.label && ( - - {renderLabel ? ( - renderLabel({ - label, - opacity, - fontUrl: labelFontUrl, - theme - }) - ) : ( - + )} ), @@ -309,7 +320,7 @@ export const Cluster: FC = ({ nodes, bind, isDraggingCurrent, - renderLabel + onRender ] ); diff --git a/src/types.ts b/src/types.ts index 85a35b58..cba6c386 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ import { ReactNode } from 'react'; import { Theme } from './themes'; -import { ColorRepresentation } from 'three'; +import { ColorRepresentation, Vector3 } from 'three'; +import { SpringValue } from '@react-spring/three'; export interface GraphElementBaseAttributes { /** @@ -266,13 +267,18 @@ export interface NodeRendererProps { export type NodeRenderer = (args: NodeRendererProps) => ReactNode; -export interface ClusterLabelRendererProps { - label: string; - opacity?: number; - fontUrl?: string; +export interface ClusterRendererProps { + offset: number; + rad: number; + padding: number; + opacity: SpringValue; + label: { + position: SpringValue<[number, number, number]>; + text: string; + opacity?: number; + fontUrl?: string; + }; theme?: Theme; } -export type ClusterLabelRenderer = ( - args: ClusterLabelRendererProps -) => ReactNode; +export type ClusterRenderer = (args: ClusterRendererProps) => ReactNode;