Skip to content

Commit

Permalink
add ability to render custom cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
SerhiiTsybulskyi committed Nov 21, 2024
1 parent 5499458 commit b13b20b
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 83 deletions.
50 changes: 36 additions & 14 deletions docs/demos/Cluster.story.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -13,7 +14,6 @@ import {

import demonSvg from '../../docs/assets/twitter.svg';


export default {
title: 'Demos/Cluster',
component: GraphCanvas
Expand Down Expand Up @@ -467,18 +467,40 @@ export const CustomLabel = () => (
draggable
edges={clusterEdges}
clusterAttribute="type"
renderClusterLabel={({ label, opacity, fontUrl }) => (
<Billboard position={[0, 0, 1]}>
<Svg src={demonSvg} />
<Text
font={fontUrl}
fontSize={12}
color={new Color('#2A6475')}
fillOpacity={opacity}
>
Custom {label}
</Text>
</Billboard>
onRenderCluster={({
label,
opacity,
offset,
rad,
padding,
}) => (
<>
<mesh>
<ringGeometry attach="geometry" args={[offset, rad + padding, 128]} />
<a.meshBasicMaterial
attach="material"
color="gray"
transparent={true}
depthTest={false}
opacity={opacity}
side={DoubleSide}
fog={true}
/>
</mesh>
<a.group position={label.position}>
<Billboard position={[0, 0, 1]}>
<Svg src={demonSvg} />
<Text
font={label.fontUrl}
fontSize={12}
color={new Color('#2A6475')}
fillOpacity={label.opacity}
>
Custom {label.text}
</Text>
</Billboard>
</a.group>
</>
)}
/>
);
Expand Down
12 changes: 6 additions & 6 deletions src/GraphScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
InternalGraphNode,
NodeRenderer,
CollapseProps,
ClusterLabelRenderer
ClusterRenderer
} from './types';
import { SizingType } from './sizing';
import {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -346,7 +346,7 @@ export const GraphScene: FC<GraphSceneProps & { ref?: Ref<GraphSceneRef> }> =
edgeInterpolation = 'linear',
labelFontUrl,
renderNode,
renderClusterLabel,
onRenderCluster,
...rest
},
ref
Expand Down Expand Up @@ -512,7 +512,7 @@ export const GraphScene: FC<GraphSceneProps & { ref?: Ref<GraphSceneRef> }> =
onPointerOver={onClusterPointerOver}
onPointerOut={onClusterPointerOut}
onDragged={onClusterDragged}
renderLabel={renderClusterLabel}
onRender={onRenderCluster}
{...c}
/>
)),
Expand All @@ -526,7 +526,7 @@ export const GraphScene: FC<GraphSceneProps & { ref?: Ref<GraphSceneRef> }> =
onClusterPointerOut,
onClusterPointerOver,
onClusterDragged,
renderClusterLabel
onRenderCluster
]
);

Expand Down
121 changes: 66 additions & 55 deletions src/symbols/Cluster.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<ClusterGroup, 'position'>;

Expand Down Expand Up @@ -73,7 +73,7 @@ export interface ClusterProps extends ClusterGroup {
/**
* Render a custom cluster label
*/
renderLabel?: ClusterLabelRenderer;
onRender?: ClusterRenderer;
}

export const Cluster: FC<ClusterProps> = ({
Expand All @@ -90,7 +90,7 @@ export const Cluster: FC<ClusterProps> = ({
onPointerOut,
draggable = false,
onDragged,
renderLabel
onRender
}) => {
const theme = useStore(state => state.theme);
const rad = Math.max(position.width, position.height) / 2;
Expand Down Expand Up @@ -136,13 +136,15 @@ export const Cluster: FC<ClusterProps> = ({

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: {
Expand Down Expand Up @@ -238,54 +240,63 @@ export const Cluster: FC<ClusterProps> = ({
}}
{...(bind() as any)}
>
<mesh>
<ringGeometry attach="geometry" args={[offset, 0, 128]} />
<a.meshBasicMaterial
attach="material"
color={normalizedFill}
transparent={true}
depthTest={false}
opacity={theme.cluster?.fill ? circleOpacity : 0}
side={DoubleSide}
fog={true}
/>
</mesh>
<mesh>
<ringGeometry
attach="geometry"
args={[offset, rad + padding, 128]}
/>
<a.meshBasicMaterial
attach="material"
color={normalizedStroke}
transparent={true}
depthTest={false}
opacity={circleOpacity}
side={DoubleSide}
fog={true}
/>
</mesh>
{theme.cluster?.label && (
<a.group position={labelPosition as any}>
{renderLabel ? (
renderLabel({
label,
opacity,
fontUrl: labelFontUrl,
theme
})
) : (
<Label
text={label}
opacity={opacity}
fontUrl={labelFontUrl}
stroke={theme.cluster.label.stroke}
active={false}
color={theme.cluster?.label.color}
fontSize={theme.cluster?.label.fontSize ?? 12}
{onRender ? (
onRender({
label: {
position: labelPosition,
text: label,
opacity: opacity,
fontUrl: labelFontUrl
},
opacity: circleOpacity,
offset,
rad,
padding,
theme
})
) : (
<>
<mesh>
<ringGeometry attach="geometry" args={[offset, 0, 128]} />
<a.meshBasicMaterial
attach="material"
color={normalizedFill}
transparent={true}
depthTest={false}
opacity={theme.cluster?.fill ? circleOpacity : 0}
side={DoubleSide}
fog={true}
/>
</mesh>
<mesh>
<ringGeometry
attach="geometry"
args={[offset, rad + padding, 128]}
/>
<a.meshBasicMaterial
attach="material"
color={normalizedStroke}
transparent={true}
depthTest={false}
opacity={circleOpacity}
side={DoubleSide}
fog={true}
/>
</mesh>
{theme.cluster?.label && (
<a.group position={labelPosition as any}>
<Label
text={label}
opacity={opacity}
fontUrl={labelFontUrl}
stroke={theme.cluster.label.stroke}
active={false}
color={theme.cluster?.label.color}
fontSize={theme.cluster?.label.fontSize ?? 12}
/>
</a.group>
)}
</a.group>
</>
)}
</a.group>
),
Expand All @@ -309,7 +320,7 @@ export const Cluster: FC<ClusterProps> = ({
nodes,
bind,
isDraggingCurrent,
renderLabel
onRender
]
);

Expand Down
22 changes: 14 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -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<T = any> {
/**
Expand Down Expand Up @@ -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<number>;
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;

0 comments on commit b13b20b

Please sign in to comment.