@kgdiff/g6 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -2
  3. package/dist/color.d.ts +24 -0
  4. package/dist/color.d.ts.map +1 -0
  5. package/dist/color.js +95 -0
  6. package/dist/color.js.map +1 -0
  7. package/dist/constants.d.ts +81 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +81 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/controller.d.ts +126 -0
  12. package/dist/controller.d.ts.map +1 -0
  13. package/dist/controller.js +422 -0
  14. package/dist/controller.js.map +1 -0
  15. package/dist/geometry.d.ts +19 -0
  16. package/dist/geometry.d.ts.map +1 -0
  17. package/dist/geometry.js +43 -0
  18. package/dist/geometry.js.map +1 -0
  19. package/dist/index.d.ts +101 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +99 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/locator.d.ts +24 -0
  24. package/dist/locator.d.ts.map +1 -0
  25. package/dist/locator.js +69 -0
  26. package/dist/locator.js.map +1 -0
  27. package/dist/map-context.d.ts +70 -0
  28. package/dist/map-context.d.ts.map +1 -0
  29. package/dist/map-context.js +44 -0
  30. package/dist/map-context.js.map +1 -0
  31. package/dist/map-edge.d.ts +19 -0
  32. package/dist/map-edge.d.ts.map +1 -0
  33. package/dist/map-edge.js +399 -0
  34. package/dist/map-edge.js.map +1 -0
  35. package/dist/map-node.d.ts +10 -0
  36. package/dist/map-node.d.ts.map +1 -0
  37. package/dist/map-node.js +243 -0
  38. package/dist/map-node.js.map +1 -0
  39. package/dist/map.d.ts +47 -0
  40. package/dist/map.d.ts.map +1 -0
  41. package/dist/map.js +92 -0
  42. package/dist/map.js.map +1 -0
  43. package/dist/node.d.ts +68 -0
  44. package/dist/node.d.ts.map +1 -0
  45. package/dist/node.js +189 -0
  46. package/dist/node.js.map +1 -0
  47. package/dist/options.d.ts +33 -0
  48. package/dist/options.d.ts.map +1 -0
  49. package/dist/options.js +172 -0
  50. package/dist/options.js.map +1 -0
  51. package/dist/patch.d.ts +45 -0
  52. package/dist/patch.d.ts.map +1 -0
  53. package/dist/patch.js +120 -0
  54. package/dist/patch.js.map +1 -0
  55. package/dist/replay.d.ts +20 -0
  56. package/dist/replay.d.ts.map +1 -0
  57. package/dist/replay.js +36 -0
  58. package/dist/replay.js.map +1 -0
  59. package/package.json +54 -3
  60. package/src/color.ts +101 -0
  61. package/src/constants.ts +96 -0
  62. package/src/controller.ts +517 -0
  63. package/src/geometry.ts +44 -0
  64. package/src/index.ts +140 -0
  65. package/src/locator.ts +72 -0
  66. package/src/map-context.ts +119 -0
  67. package/src/map-edge.ts +481 -0
  68. package/src/map-node.ts +272 -0
  69. package/src/map.ts +130 -0
  70. package/src/node.ts +240 -0
  71. package/src/options.ts +215 -0
  72. package/src/patch.ts +133 -0
  73. package/src/replay.ts +59 -0
@@ -0,0 +1,481 @@
1
+ /**
2
+ * Scene edges and glyphs → G6 edge data (glyph arcs) or small nodes (pivot dots); see map.ts
3
+ * for the conventions.
4
+ */
5
+ import type { EdgeData, NodeData } from '@antv/g6';
6
+ import type { EdgeVisual, GlyphVisual, NodeVisual, SceneEdge, SceneNode } from '@kgdiff/scene';
7
+ import {
8
+ ANCHOR_ID_PREFIX,
9
+ ARROW_GAP,
10
+ ARROW_LENGTH,
11
+ ARROW_WIDTH,
12
+ EDGE_HIT_WIDTH,
13
+ GLYPH_ID_PREFIX,
14
+ LABEL_MAX_WIDTH,
15
+ MARK_RADIUS,
16
+ PIVOT_OUTLINE,
17
+ TEXT_HALO_ALPHA,
18
+ TEXT_HALO_WIDTH,
19
+ Z,
20
+ } from './constants.js';
21
+ import { band, finite, linkLength, unit } from './geometry.js';
22
+ import { NodeLocator } from './locator.js';
23
+ import {
24
+ type Ctx,
25
+ claim,
26
+ type EdgeStyle,
27
+ type KgdiffDatumData,
28
+ type NodeStyle,
29
+ noteLod,
30
+ shown,
31
+ type Text,
32
+ text,
33
+ } from './map-context.js';
34
+ import { nodeBase } from './map-node.js';
35
+
36
+ /** How a glyph kind is drawn; unknown kinds (newer scenes) degrade to a plain line. */
37
+ interface GlyphStyle {
38
+ readonly arrow: boolean;
39
+ /** Pattern used when the glyph has no `dash` of its own. */
40
+ readonly dash: readonly number[] | null;
41
+ /** `?` mark at the midpoint. */
42
+ readonly mark: boolean;
43
+ }
44
+
45
+ const ARC: GlyphStyle = { arrow: true, dash: null, mark: false };
46
+ const GHOST: GlyphStyle = { arrow: true, dash: [4, 3], mark: false };
47
+ const SUGGESTION: GlyphStyle = { arrow: false, dash: [1, 3], mark: true };
48
+ const PLAIN: GlyphStyle = { arrow: false, dash: null, mark: false };
49
+
50
+ function glyphStyle(kind: string): GlyphStyle {
51
+ switch (kind) {
52
+ case 'merge-arrow':
53
+ case 'split-arrow':
54
+ case 'replacement-arrow':
55
+ case 'move-arrow':
56
+ return ARC;
57
+ case 'edge-ghost':
58
+ return GHOST;
59
+ case 'suggestion':
60
+ return SUGGESTION;
61
+ default:
62
+ return PLAIN;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * G6 arrowhead size: [length along the line, width]. Shared by every edge so G6's shallow style
68
+ * comparison sees no change; G6 only reads it (`parseSize` copies), freezing guards the rest.
69
+ */
70
+ const ARROW_SIZE = Object.freeze([ARROW_LENGTH, ARROW_WIDTH]) as unknown as [number, number];
71
+ /** Gap long enough to hide the rest of a partially drawn line. */
72
+ const DASH_REST = 1e6;
73
+
74
+ function edgeBase(ctx: Ctx): Readonly<EdgeStyle> {
75
+ if (ctx.edgeBase === null) ctx.edgeBase = Object.freeze(buildEdgeBase(ctx));
76
+ return ctx.edgeBase;
77
+ }
78
+
79
+ function buildEdgeBase(ctx: Ctx): EdgeStyle {
80
+ const t = ctx.theme;
81
+ return {
82
+ zIndex: Z.edge,
83
+ visibility: 'visible',
84
+ cursor: 'pointer',
85
+ pointerEvents: 'auto',
86
+ increasedLineWidthForHitTesting: EDGE_HIT_WIDTH,
87
+ opacity: 1,
88
+ stroke: t.neutral.edge,
89
+ strokeOpacity: 1,
90
+ lineWidth: 1,
91
+ lineDash: 0,
92
+ lineDashOffset: 0,
93
+ lineCap: 'round',
94
+ curvePosition: 0.5,
95
+ curveOffset: 0,
96
+ loop: true,
97
+ loopPlacement: 'top',
98
+ halo: false,
99
+ startArrow: false,
100
+ endArrow: false,
101
+ endArrowType: 'triangle',
102
+ endArrowSize: ARROW_SIZE,
103
+ endArrowLineWidth: 0,
104
+ endArrowOffset: ARROW_LENGTH / 2 + ARROW_GAP,
105
+ label: false,
106
+ labelText: '',
107
+ labelFill: t.neutral.label,
108
+ labelFontSize: 10.5,
109
+ labelFontWeight: t.font.weightRegular,
110
+ labelFontFamily: t.font.family,
111
+ labelOpacity: 0,
112
+ labelPlacement: 'center',
113
+ labelAutoRotate: false,
114
+ labelOffsetX: 0,
115
+ labelOffsetY: 0,
116
+ labelTextAlign: 'center',
117
+ labelTextBaseline: 'middle',
118
+ labelTextDecorationLine: 'none',
119
+ labelTextDecorationColor: t.neutral.label,
120
+ labelStroke: ctx.bg,
121
+ labelLineWidth: TEXT_HALO_WIDTH,
122
+ labelStrokeOpacity: 0,
123
+ labelLineJoin: 'round',
124
+ labelMaxWidth: LABEL_MAX_WIDTH,
125
+ labelWordWrap: true,
126
+ labelMaxLines: 1,
127
+ labelTextOverflow: '…',
128
+ labelBackground: false,
129
+ labelBackgroundFill: ctx.bg,
130
+ labelBackgroundStroke: ctx.bg,
131
+ labelBackgroundLineWidth: 0,
132
+ labelBackgroundRadius: 0,
133
+ labelBackgroundOpacity: 1,
134
+ labelBackgroundWidth: 0,
135
+ labelBackgroundHeight: 0,
136
+ labelPadding: 0,
137
+ };
138
+ }
139
+
140
+ /** A link endpoint: a drawn node, or an anchor point (radius 0). */
141
+ interface End {
142
+ /** G6 node id. */
143
+ readonly id: string;
144
+ /** Identity used in glyph ids: the scene union id, or the anchor key. */
145
+ readonly key: string;
146
+ /** Scene node index, or -1 for an anchor without one. */
147
+ readonly index: number;
148
+ readonly x: number;
149
+ readonly y: number;
150
+ readonly r: number;
151
+ }
152
+
153
+ interface Link {
154
+ readonly a: End;
155
+ readonly b: End;
156
+ readonly curvature: number;
157
+ readonly progress: number;
158
+ readonly fromTarget: boolean;
159
+ readonly dash: readonly number[] | null;
160
+ readonly alpha: number;
161
+ /** Extra world units the arrow keeps away from the target outline (ring clearance). */
162
+ readonly clearance: number;
163
+ }
164
+
165
+ /**
166
+ * Geometry shared by edges and glyph arcs: G6 type, curve offset, partial drawing, arrow.
167
+ * G6 has no "draw a fraction of the path": a solid line shows `progress` with a dash pattern
168
+ * (visible part, then a gap longer than the path); a dashed one fades with it instead.
169
+ */
170
+ function linkStyle(link: Link, arrow: boolean): { type: 'line' | 'quadratic'; style: EdgeStyle } {
171
+ const { a, b } = link;
172
+ const loop = a.id === b.id;
173
+ const len = loop ? 0 : Math.hypot(b.x - a.x, b.y - a.y);
174
+ const c = loop || !(len > 0) ? 0 : finite(link.curvature, 0);
175
+ const p = unit(link.progress);
176
+ let alpha = link.alpha;
177
+ let lineDash: number | number[] = link.dash !== null && link.dash.length > 0 ? [...link.dash] : 0;
178
+ let lineDashOffset = 0;
179
+ if (p < 1) {
180
+ if (lineDash === 0) {
181
+ const drawn = Math.max(0, linkLength(len, c) - Math.max(0, a.r) - Math.max(0, b.r));
182
+ lineDash = [drawn * p, DASH_REST];
183
+ if (link.fromTarget) lineDashOffset = -drawn * (1 - p);
184
+ } else alpha *= p;
185
+ }
186
+ const dashed = Array.isArray(lineDash) && lineDash.length > 0 && (lineDash[0] as number) >= 0.5;
187
+ return {
188
+ type: c !== 0 ? 'quadratic' : 'line',
189
+ style: {
190
+ opacity: unit(alpha),
191
+ lineDash,
192
+ lineDashOffset,
193
+ lineCap: dashed ? 'butt' : 'round',
194
+ // Scene: control = midpoint + c·(−dy, dx). G6: midpoint + curveOffset·unit(dy, −dx).
195
+ curveOffset: c === 0 ? 0 : -c * len,
196
+ // The tip follows the drawn end, so a line growing from its source shows no arrow yet.
197
+ endArrow: arrow && !loop && (p >= 1 || link.fromTarget),
198
+ endArrowOffset: ARROW_LENGTH / 2 + ARROW_GAP + Math.max(0, link.clearance),
199
+ },
200
+ };
201
+ }
202
+
203
+ function edgeLabelKeys(l: Text, ctx: Ctx): EdgeStyle {
204
+ return {
205
+ label: l.show,
206
+ labelText: l.text,
207
+ labelFill: l.fill,
208
+ labelFontSize: l.size,
209
+ labelFontWeight: l.weight,
210
+ labelOpacity: l.opacity,
211
+ labelOffsetY: l.offset,
212
+ labelTextDecorationLine: l.strike ? 'line-through' : 'none',
213
+ labelTextDecorationColor: l.fill,
214
+ labelStroke: ctx.bg,
215
+ labelStrokeOpacity: TEXT_HALO_ALPHA * l.opacity,
216
+ };
217
+ }
218
+
219
+ /** The present node among the candidates nearest to (x, y), or -1. Ties keep `a`. */
220
+ function endpoint(ctx: Ctx, x: number, y: number, a: number, b: number): number {
221
+ const nodes = ctx.frame.nodes;
222
+ const ok = (i: number): boolean => i >= 0 && i < nodes.length && ctx.present[i] === 1;
223
+ if (a === b || !ok(b)) return ok(a) ? a : -1;
224
+ if (!ok(a)) return b;
225
+ const na = nodes[a] as NodeVisual;
226
+ const nb = nodes[b] as NodeVisual;
227
+ const da = Math.hypot(na.x - x, na.y - y);
228
+ const db = Math.hypot(nb.x - x, nb.y - y);
229
+ return db < da ? b : a;
230
+ }
231
+
232
+ function nodeEnd(ctx: Ctx, i: number): End {
233
+ const v = ctx.frame.nodes[i] as NodeVisual;
234
+ return {
235
+ id: ctx.nodeIds[i] as string,
236
+ key: (ctx.scene.nodes[i] as SceneNode).id,
237
+ index: i,
238
+ x: v.x,
239
+ y: v.y,
240
+ r: Math.max(0, finite(v.r, 0)),
241
+ };
242
+ }
243
+
244
+ export function mapEdge(se: SceneEdge, ev: EdgeVisual, ctx: Ctx): EdgeData | null {
245
+ const drawn = ev.visible && ev.progress > 0;
246
+ if (!drawn && !ctx.keepHidden) return null;
247
+ // Retargeted / carried edges are drawn at their before or after endpoints depending on the
248
+ // layer: attach to whichever candidate node the visual's endpoint sits on.
249
+ const source = endpoint(ctx, ev.x1, ev.y1, se.s, se.sb);
250
+ const target = endpoint(ctx, ev.x2, ev.y2, se.t, se.tb);
251
+ if (source < 0 || target < 0) return null;
252
+ if (ev.arrow) noteLod(ctx, ev.arrowMinZoom);
253
+ const { type, style: link } = linkStyle(
254
+ {
255
+ a: nodeEnd(ctx, source),
256
+ b: nodeEnd(ctx, target),
257
+ curvature: ev.curvature,
258
+ progress: ev.progress,
259
+ fromTarget: ev.fromTarget,
260
+ dash: ev.dash,
261
+ alpha: ev.alpha,
262
+ clearance: 0,
263
+ },
264
+ ev.arrow && shown(ev.arrowMinZoom, ctx.zoom),
265
+ );
266
+ const label = text(ev.label, unit(ev.alpha), ctx, 10.5, ctx.theme.neutral.label);
267
+ const style: EdgeStyle = {
268
+ ...edgeBase(ctx),
269
+ ...link,
270
+ ...edgeLabelKeys(label, ctx),
271
+ zIndex: band(Z.edge, ev.z, Z.span),
272
+ visibility: drawn ? 'visible' : 'hidden',
273
+ stroke: ev.color,
274
+ lineWidth: Math.max(0, finite(ev.width, 1)),
275
+ lineDashOffset: (link.lineDashOffset as number) + finite(ev.dashOffset, 0),
276
+ };
277
+ const data: KgdiffDatumData = {
278
+ kgdiff: 'edge',
279
+ index: se.index,
280
+ hunk: se.hunk,
281
+ family: se.family,
282
+ changed: se.family !== 'neutral',
283
+ label: label.text.length > 0 ? label.text : se.label,
284
+ glyph: '',
285
+ };
286
+ return {
287
+ id: claim(ctx, ctx.prefix + se.id),
288
+ source: ctx.nodeIds[source] as string,
289
+ target: ctx.nodeIds[target] as string,
290
+ type,
291
+ style,
292
+ data,
293
+ };
294
+ }
295
+
296
+ // ─── Glyphs ─────────────────────────────────────────────────────────────────
297
+
298
+ function locator(ctx: Ctx): NodeLocator {
299
+ if (ctx.locator === null) {
300
+ const nodes = ctx.frame.nodes;
301
+ ctx.locator = new NodeLocator(
302
+ nodes,
303
+ (i) => ctx.present[i] === 1 && (nodes[i] as NodeVisual).visible,
304
+ );
305
+ }
306
+ return ctx.locator;
307
+ }
308
+
309
+ /** Every scene node, drawn or not: names the anchor of a glyph end that has no drawn node. */
310
+ function anyLocator(ctx: Ctx): NodeLocator {
311
+ if (ctx.anyLocator === null) ctx.anyLocator = new NodeLocator(ctx.frame.nodes, () => true);
312
+ return ctx.anyLocator;
313
+ }
314
+
315
+ /**
316
+ * The end of a glyph line at (x, y): the drawn node whose disc holds it, else an anchor — an
317
+ * invisible, non-interactive point node — so arcs towards nodes that are not drawn yet (a split
318
+ * target before it emerges) are kept. Anchors are shared within a frame and named after the
319
+ * scene node at that point when there is one, so their ids are stable across frames.
320
+ */
321
+ function glyphEnd(ctx: Ctx, x: number, y: number, nodes: NodeData[]): End | null {
322
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
323
+ const at = locator(ctx).nearest(x, y, 0.5);
324
+ if (at >= 0) return nodeEnd(ctx, at);
325
+ const owner = anyLocator(ctx).nearest(x, y, 0.5);
326
+ const key =
327
+ owner >= 0 ? (ctx.scene.nodes[owner] as SceneNode).id : `@${Math.round(x)},${Math.round(y)}`;
328
+ let id = ctx.anchors.get(key);
329
+ if (id === undefined) {
330
+ id = claim(ctx, `${ctx.prefix}${ANCHOR_ID_PREFIX}${JSON.stringify(key)}`);
331
+ ctx.anchors.set(key, id);
332
+ const style: NodeStyle = {
333
+ ...nodeBase(ctx),
334
+ x,
335
+ y,
336
+ // Not 0: G measures an empty circle as an empty box, and edges need a real point.
337
+ size: 0.01,
338
+ zIndex: Z.pivot,
339
+ cursor: 'default',
340
+ pointerEvents: 'none',
341
+ fill: 'transparent',
342
+ stroke: 'transparent',
343
+ lineWidth: 0,
344
+ };
345
+ const data: KgdiffDatumData = {
346
+ kgdiff: 'anchor',
347
+ index: owner,
348
+ hunk: owner >= 0 ? (ctx.scene.nodes[owner] as SceneNode).hunk : -1,
349
+ family: 'neutral',
350
+ changed: false,
351
+ label: '',
352
+ glyph: '',
353
+ };
354
+ nodes.push({ id, style, data });
355
+ }
356
+ return { id, key, index: owner, x, y, r: 0 };
357
+ }
358
+
359
+ function glyphHunk(ctx: Ctx, a: End, b: End): number {
360
+ const ha = a.index >= 0 ? (ctx.scene.nodes[a.index] as SceneNode).hunk : -1;
361
+ return ha >= 0 || b.index < 0 ? ha : (ctx.scene.nodes[b.index] as SceneNode).hunk;
362
+ }
363
+
364
+ function glyphId(ctx: Ctx, kind: string, a: End, b: End): string {
365
+ // JSON keeps ids containing separators unambiguous.
366
+ return claim(ctx, `${ctx.prefix}${GLYPH_ID_PREFIX}${kind}:${JSON.stringify([a.key, b.key])}`);
367
+ }
368
+
369
+ function glyphData(ctx: Ctx, g: GlyphVisual, i: number, a: End, b: End): KgdiffDatumData {
370
+ const hunk = glyphHunk(ctx, a, b);
371
+ return {
372
+ kgdiff: 'glyph',
373
+ index: i,
374
+ hunk,
375
+ family: hunk >= 0 ? (ctx.scene.hunks[hunk]?.family ?? 'neutral') : 'neutral',
376
+ changed: hunk >= 0,
377
+ label: g.label?.text ?? '',
378
+ glyph: g.kind,
379
+ };
380
+ }
381
+
382
+ /** A pivot: a dot of diameter `width` with a background outline, as a non-interactive node. */
383
+ function mapPivot(g: GlyphVisual, i: number, ctx: Ctx): GlyphDatum | null {
384
+ const w = Math.max(3, finite(g.width, 5));
385
+ // The dot sits on its node's outline: attach its identity to that node.
386
+ const at = locator(ctx).nearest(g.x1, g.y1, w);
387
+ if (at < 0) return null;
388
+ const end = nodeEnd(ctx, at);
389
+ const style: NodeStyle = {
390
+ ...nodeBase(ctx),
391
+ x: g.x1,
392
+ y: g.y1,
393
+ size: w + PIVOT_OUTLINE,
394
+ zIndex: Z.pivot,
395
+ cursor: 'default',
396
+ pointerEvents: 'none',
397
+ opacity: unit(g.alpha),
398
+ fill: g.color,
399
+ stroke: ctx.bg,
400
+ lineWidth: PIVOT_OUTLINE,
401
+ };
402
+ return {
403
+ node: { id: glyphId(ctx, g.kind, end, end), style, data: glyphData(ctx, g, i, end, end) },
404
+ };
405
+ }
406
+
407
+ export type GlyphDatum = { readonly node: NodeData } | { readonly edge: EdgeData };
408
+
409
+ /**
410
+ * A glyph as G6 data: pivots become nodes, everything else an edge between the ends' nodes or
411
+ * anchors (anchor nodes are pushed to `nodes`). Null when the glyph is invisible or degenerate.
412
+ */
413
+ export function mapGlyph(
414
+ g: GlyphVisual,
415
+ i: number,
416
+ ctx: Ctx,
417
+ nodes: NodeData[],
418
+ ): GlyphDatum | null {
419
+ if (!(g.alpha > 0)) return null;
420
+ const kind = String(g.kind);
421
+ if (kind === 'pivot' || (g.x1 === g.x2 && g.y1 === g.y2)) return mapPivot(g, i, ctx);
422
+ const source = glyphEnd(ctx, g.x1, g.y1, nodes);
423
+ const target = glyphEnd(ctx, g.x2, g.y2, nodes);
424
+ if (source === null || target === null || source.id === target.id) return null;
425
+ const spec = glyphStyle(kind);
426
+ // A newer scene may carry its own arrow flag (the native renderer honours it too).
427
+ const own = (g as GlyphVisual & { readonly arrow?: unknown }).arrow;
428
+ const arrow = typeof own === 'boolean' ? own : spec.arrow;
429
+ const { type, style: link } = linkStyle(
430
+ {
431
+ a: source,
432
+ b: target,
433
+ curvature: g.curvature,
434
+ progress: g.progress,
435
+ fromTarget: false,
436
+ dash: g.dash !== null && g.dash.length > 0 ? g.dash : spec.dash,
437
+ alpha: g.alpha,
438
+ clearance: finite(g.r2, 0) - target.r,
439
+ },
440
+ arrow,
441
+ );
442
+ const label = text(g.label, unit(g.alpha), ctx, ctx.theme.font.size, g.color);
443
+ const chord = Math.hypot(target.x - source.x, target.y - source.y);
444
+ // Suggestions: the `?` sits in a small disc at the midpoint once the line is complete.
445
+ const mark = spec.mark && unit(g.progress) >= 1 && chord > 6 * MARK_RADIUS;
446
+ const style: EdgeStyle = {
447
+ ...edgeBase(ctx),
448
+ ...link,
449
+ ...edgeLabelKeys(label, ctx),
450
+ zIndex: Z.glyph,
451
+ stroke: g.color,
452
+ lineWidth: Math.max(0, finite(g.width, 1.5)),
453
+ ...(spec.mark
454
+ ? {
455
+ label: mark && label.show,
456
+ labelFill: g.color,
457
+ labelFontSize: 10,
458
+ labelFontWeight: 700,
459
+ labelStrokeOpacity: 0,
460
+ labelOffsetY: 0,
461
+ labelBackground: mark && label.show,
462
+ labelBackgroundFill: ctx.bg,
463
+ labelBackgroundStroke: g.color,
464
+ labelBackgroundLineWidth: 1.25,
465
+ labelBackgroundRadius: MARK_RADIUS,
466
+ labelBackgroundWidth: 2 * MARK_RADIUS,
467
+ labelBackgroundHeight: 2 * MARK_RADIUS,
468
+ }
469
+ : {}),
470
+ };
471
+ return {
472
+ edge: {
473
+ id: glyphId(ctx, kind, source, target),
474
+ source: source.id,
475
+ target: target.id,
476
+ type,
477
+ style,
478
+ data: glyphData(ctx, g, i, source, target),
479
+ },
480
+ };
481
+ }