@fusion.dev/helix-graph 0.128.0 → 0.128.1

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.
@@ -38,6 +38,24 @@ export declare const signalsOver: (container: HTMLElement, onScreen: OnScreen) =
38
38
  } | undefined, animate: boolean) => void;
39
39
  stop: () => void;
40
40
  };
41
+ /**
42
+ * Where the frame's own controls lie over the drawing, in the drawing's
43
+ * pixels: its zoom buttons, and its list where it opens over the drawing.
44
+ * Names keep clear of them, since a name under a button can be read by
45
+ * nobody. Read from the frame the drawing is in, where there is one.
46
+ */
47
+ export declare const chromeOver: (container: HTMLElement) => Box[];
48
+ /** Whether a box overlaps none of `others`. */
49
+ export declare const clearOf: (box: Box, others: readonly Box[]) => boolean;
50
+ /**
51
+ * Where a note's name starts, along the line: after the note, or before it
52
+ * where after it would run under the frame's controls or off the drawing.
53
+ * `box` gives the name's box for a start.
54
+ */
55
+ export declare const nameStart: (at: {
56
+ radius: number;
57
+ x: number;
58
+ }, width: number, box: (start: number) => Box, avoid: readonly Box[], room: number) => number;
41
59
  /** A note to name over the drawing: its name, and whether it is the one the reader is on. */
42
60
  export interface Named {
43
61
  id: string;
@@ -266,6 +266,40 @@ export const signalsOver = (container, onScreen) => {
266
266
  },
267
267
  };
268
268
  };
269
+ export const chromeOver = (container) => {
270
+ const frame = container.closest('helix-graph, helix-headless-graph');
271
+ const parts = frame?.shadowRoot?.querySelectorAll('[part="controls"], [part="panel"]') ?? [];
272
+ const origin = container.getBoundingClientRect();
273
+ const boxes = [];
274
+ for (const part of parts) {
275
+ const at = part.getBoundingClientRect();
276
+ if (at.width > 0 && at.height > 0) {
277
+ boxes.push({
278
+ bottom: at.bottom - origin.top,
279
+ left: at.left - origin.left,
280
+ right: at.right - origin.left,
281
+ top: at.top - origin.top,
282
+ });
283
+ }
284
+ }
285
+ return boxes;
286
+ };
287
+ export const clearOf = (box, others) => others.every((other) => box.right < other.left ||
288
+ box.left > other.right ||
289
+ box.bottom < other.top ||
290
+ box.top > other.bottom);
291
+ export const nameStart = (at, width, box, avoid, room) => {
292
+ const after = at.x + at.radius + 4;
293
+ const fits = (start) => {
294
+ const placed = box(start);
295
+ return placed.left >= 0 && placed.right <= room && clearOf(placed, avoid);
296
+ };
297
+ if (fits(after)) {
298
+ return after;
299
+ }
300
+ const before = at.x - at.radius - 4 - width;
301
+ return fits(before) ? before : after;
302
+ };
269
303
  export const namesOver = (container, onScreen) => {
270
304
  const canvas = layer(container, 'names');
271
305
  let taken = [];
@@ -276,26 +310,24 @@ export const namesOver = (container, onScreen) => {
276
310
  if (!context || !looks) {
277
311
  return;
278
312
  }
279
- taken = [];
313
+ taken = chromeOver(container);
280
314
  for (const { id, name, strong } of named) {
281
315
  const at = onScreen(id);
282
316
  if (!at || looks.node(id).hidden) {
283
317
  continue;
284
318
  }
285
319
  context.font = `${strong ? 600 : 500} ${strong ? 13 : 12}px ${looks.font}`;
286
- const x = at.x + at.radius + 4;
320
+ const width = context.measureText(name).width;
287
321
  const y = at.y + 4;
288
- const box = {
322
+ const boxAt = (start) => ({
289
323
  bottom: y + 4,
290
- left: x - 2,
291
- right: x + context.measureText(name).width + 2,
324
+ left: start - 2,
325
+ right: start + width + 2,
292
326
  top: y - 13,
293
- };
294
- const free = taken.every((other) => box.right < other.left ||
295
- box.left > other.right ||
296
- box.bottom < other.top ||
297
- box.top > other.bottom);
298
- if (!free && !strong) {
327
+ });
328
+ const x = nameStart(at, width, boxAt, taken, container.clientWidth);
329
+ const box = boxAt(x);
330
+ if (!clearOf(box, taken) && !strong) {
299
331
  continue;
300
332
  }
301
333
  taken.push(box);
@@ -1,4 +1,4 @@
1
- import { ease, gesturesHeld, groupsOver, signalsOver } from '../overlay/overlay.js';
1
+ import { chromeOver, clearOf, ease, gesturesHeld, groupsOver, nameStart, signalsOver, } from '../overlay/overlay.js';
2
2
  import { EMPHASES, HelixNodeProgram, MATERIALS, SHAPES } from './node-program.js';
3
3
  const SETTINGS = [
4
4
  'defaultDrawNodeHover',
@@ -43,21 +43,23 @@ const hoverOf = (looks) => (context, data, settings) => {
43
43
  context.fillText(data.label, x, data.y + size / 3);
44
44
  };
45
45
  const GROW = 300;
46
- const labelOf = (looks, claim) => (context, data, settings) => {
46
+ const labelOf = (looks, claim, start) => (context, data, settings) => {
47
47
  if (!data.label) {
48
48
  return;
49
49
  }
50
50
  const strong = data.strong === true;
51
51
  const size = strong ? settings.labelSize : settings.labelSize - 1;
52
52
  context.font = `${strong ? 600 : 500} ${size}px ${settings.labelFont}`;
53
- const x = data.x + data.size + 4;
53
+ const width = context.measureText(data.label).width;
54
54
  const y = data.y + size / 3;
55
- const box = {
55
+ const boxAt = (from) => ({
56
56
  bottom: y + 4,
57
- left: x - 2,
58
- right: x + context.measureText(data.label).width + 2,
57
+ left: from - 2,
58
+ right: from + width + 2,
59
59
  top: y - size,
60
- };
60
+ });
61
+ const x = start({ radius: data.size, x: data.x }, width, boxAt);
62
+ const box = boxAt(x);
61
63
  if (!claim(box, data.forceLabel === true)) {
62
64
  return;
63
65
  }
@@ -113,17 +115,14 @@ export const sigmaDriver = (sigma, options = {}) => {
113
115
  const naming = options.labels ?? 'hover';
114
116
  let taken = [];
115
117
  const claim = (box, must) => {
116
- const free = taken.every((other) => box.right < other.left ||
117
- box.left > other.right ||
118
- box.bottom < other.top ||
119
- box.top > other.bottom);
118
+ const free = clearOf(box, taken);
120
119
  if (free || must) {
121
120
  taken.push(box);
122
121
  }
123
122
  return free || must;
124
123
  };
125
124
  const clear = () => {
126
- taken = [];
125
+ taken = chromeOver(container);
127
126
  };
128
127
  sigma.on('beforeRender', clear);
129
128
  const held = gesturesHeld(container);
@@ -240,7 +239,7 @@ export const sigmaDriver = (sigma, options = {}) => {
240
239
  sigma.setSetting('labelFont', looks.font);
241
240
  sigma.setSetting('labelRenderedSizeThreshold', 0);
242
241
  sigma.setSetting('labelGridCellSize', 130);
243
- sigma.setSetting('defaultDrawNodeLabel', labelOf(looks, claim));
242
+ sigma.setSetting('defaultDrawNodeLabel', labelOf(looks, claim, (at, width, box) => nameStart(at, width, box, taken, container.clientWidth)));
244
243
  sigma.setSetting('defaultDrawNodeHover', hoverOf(looks));
245
244
  sigma.setSetting('nodeReducer', (node, data) => {
246
245
  const look = looks.node(node);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-graph",
3
3
  "description": "Connects the Helix Graph frame to a graph renderer: Sigma.js, cosmos.gl, three.js, or a driver of your own.",
4
- "version": "0.128.0",
4
+ "version": "0.128.1",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -51,12 +51,12 @@
51
51
  },
52
52
  "bugs": "https://github.com/project-fusion/fusion/issues",
53
53
  "dependencies": {
54
- "@fusion.dev/helix-core": "0.128.0"
54
+ "@fusion.dev/helix-core": "0.128.1"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@cosmos.gl/graph": "^3.4.2",
58
- "@fusion.dev/helix-headless": "0.128.0",
59
- "@fusion.dev/helix-testing": "0.128.0",
58
+ "@fusion.dev/helix-headless": "0.128.1",
59
+ "@fusion.dev/helix-testing": "0.128.1",
60
60
  "@types/bun": "^1.3.0",
61
61
  "@types/three": "^0.186.0",
62
62
  "d3-force-3d": "^3.0.6",