Skip to content

Commit 48f4c22

Browse files
authored
Merge pull request #349 from reaviz/anton/dashed-edge
Dashed Edge
2 parents 6cb0d00 + ecf5420 commit 48f4c22

File tree

4 files changed

+145
-12
lines changed

4 files changed

+145
-12
lines changed

src/symbols/Edge.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ export const Edge: FC<EdgeProps> = ({
154154
subLabel,
155155
labelVisible = false,
156156
size = 1,
157-
fill
157+
fill,
158+
dashed = false
158159
} = edge;
159160

160161
// Use subLabelPlacement from edge data if available, otherwise use the prop value
@@ -456,6 +457,7 @@ export const Edge: FC<EdgeProps> = ({
456457
}
457458
curve={curve}
458459
curved={curved}
460+
dashed={dashed}
459461
id={id}
460462
opacity={selectionOpacity}
461463
size={size}

src/symbols/Line.tsx

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
TubeGeometry,
77
ColorRepresentation,
88
Color,
9-
Curve
9+
Curve,
10+
ShaderMaterial
1011
} from 'three';
1112
import { useStore } from '../store';
1213
import type { ThreeEvent } from '@react-three/fiber';
@@ -32,6 +33,11 @@ export interface LineProps {
3233
*/
3334
curve: Curve<Vector3>;
3435

36+
/**
37+
* Whether the line should be dashed.
38+
*/
39+
dashed?: boolean;
40+
3541
/**
3642
* The unique identifier of the line.
3743
*/
@@ -73,12 +79,41 @@ export interface LineProps {
7379
curveOffset?: number;
7480
}
7581

82+
// Dashed line shader for tube geometry
83+
const dashedVertexShader = `
84+
varying vec2 vUv;
85+
void main() {
86+
vUv = uv;
87+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
88+
}
89+
`;
90+
91+
const dashedFragmentShader = `
92+
uniform vec3 color;
93+
uniform float opacity;
94+
uniform float dashSize;
95+
uniform float gapSize;
96+
varying vec2 vUv;
97+
98+
void main() {
99+
float totalSize = dashSize + gapSize;
100+
float position = mod(vUv.x * 10.0, totalSize);
101+
102+
if (position > dashSize) {
103+
discard;
104+
}
105+
106+
gl_FragColor = vec4(color, opacity);
107+
}
108+
`;
109+
76110
export const Line: FC<LineProps> = ({
77111
curveOffset,
78112
animated,
79113
color = '#000',
80114
curve,
81115
curved = false,
116+
dashed = false,
82117
id,
83118
opacity = 1,
84119
size = 1,
@@ -93,6 +128,24 @@ export const Line: FC<LineProps> = ({
93128
const center = useStore(state => state.centerPosition);
94129
const mounted = useRef<boolean>(false);
95130

131+
// Create dashed material
132+
const dashedMaterial = useMemo(() => {
133+
if (!dashed) return null;
134+
135+
return new ShaderMaterial({
136+
uniforms: {
137+
color: { value: normalizedColor },
138+
opacity: { value: opacity },
139+
dashSize: { value: 0.5 },
140+
gapSize: { value: 0.3 }
141+
},
142+
vertexShader: dashedVertexShader,
143+
fragmentShader: dashedFragmentShader,
144+
transparent: true,
145+
depthTest: false
146+
});
147+
}, [dashed, normalizedColor, opacity]);
148+
96149
// Do opacity seperate from vertices for perf
97150
const { lineOpacity } = useSpring({
98151
from: {
@@ -128,14 +181,19 @@ export const Line: FC<LineProps> = ({
128181
const toVector = new Vector3(...toVertices);
129182

130183
const curve = getCurve(fromVector, 0, toVector, 0, curved, curveOffset);
131-
tubeRef.current.copy(new TubeGeometry(curve, 20, size / 2, 5, false));
184+
185+
if (tubeRef.current) {
186+
// Use slightly smaller radius for dashed lines for visual distinction
187+
const radius = dashed ? size * 0.4 : size / 2;
188+
tubeRef.current.copy(new TubeGeometry(curve, 20, radius, 5, false));
189+
}
132190
},
133191
config: {
134192
...animationConfig,
135193
duration: animated && !isDragging ? undefined : 0
136194
}
137195
};
138-
}, [animated, isDragging, curve, size]);
196+
}, [animated, isDragging, curve, size, dashed, curved, curveOffset]);
139197

140198
useEffect(() => {
141199
// Handle mount operation for initial render
@@ -158,14 +216,18 @@ export const Line: FC<LineProps> = ({
158216
}}
159217
>
160218
<tubeGeometry attach="geometry" ref={tubeRef} />
161-
<a.meshBasicMaterial
162-
attach="material"
163-
opacity={lineOpacity}
164-
fog={true}
165-
transparent={true}
166-
depthTest={false}
167-
color={normalizedColor}
168-
/>
219+
{dashed ? (
220+
<primitive attach="material" object={dashedMaterial} />
221+
) : (
222+
<a.meshBasicMaterial
223+
attach="material"
224+
opacity={lineOpacity}
225+
fog={true}
226+
transparent={true}
227+
depthTest={false}
228+
color={normalizedColor}
229+
/>
230+
)}
169231
</mesh>
170232
);
171233
};

src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ export interface GraphEdge extends GraphElementBaseAttributes {
7272
*/
7373
target: string;
7474

75+
/**
76+
* Whether the edge should be rendered with a dashed line pattern.
77+
* When true, the edge will display with alternating dash and gap segments.
78+
* Default is false (solid line).
79+
*/
80+
dashed?: boolean;
81+
7582
/**
7683
* Placement of the subLabel relative to the main label.
7784
* - 'below': Show subLabel below the main label (default)

stories/demos/Edges.story.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,65 @@ export const Events = () => (
7171
onEdgeClick={edge => alert(`Edge ${edge.id} clicked`)}
7272
/>
7373
);
74+
75+
export const Dashed = () => (
76+
<GraphCanvas
77+
edgeInterpolation="curved"
78+
labelType="all"
79+
nodes={[
80+
{
81+
id: '1',
82+
label: '1'
83+
},
84+
{
85+
id: '2',
86+
label: '2'
87+
},
88+
{
89+
id: '3',
90+
label: '3'
91+
},
92+
{
93+
id: '4',
94+
label: '4'
95+
},
96+
{
97+
id: '5',
98+
label: '5'
99+
}
100+
]}
101+
edges={[
102+
{
103+
source: '1',
104+
target: '2',
105+
id: '1-2',
106+
label: '1-2',
107+
dashed: true
108+
},
109+
{
110+
source: '2',
111+
target: '3',
112+
id: '2-3',
113+
label: '2-3',
114+
size: 5,
115+
dashed: true
116+
},
117+
{
118+
source: '3',
119+
target: '4',
120+
id: '3-4',
121+
label: '3-4',
122+
size: 3,
123+
dashed: true
124+
},
125+
{
126+
source: '4',
127+
target: '5',
128+
id: '4-5',
129+
label: '4-5',
130+
size: 10,
131+
dashed: true
132+
}
133+
]}
134+
/>
135+
);

0 commit comments

Comments
 (0)