@ai-gui/plugin-figure 0.11.0 → 0.12.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @ai-gui/plugin-figure
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [32f827c]
8
+ - @ai-gui/core@0.12.0
9
+
10
+ ## 0.11.1
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [f84cb1d]
15
+ - @ai-gui/core@0.11.1
16
+
3
17
  ## 0.11.0
4
18
 
5
19
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -199,6 +199,10 @@ function figureBounds(diagram) {
199
199
  * The side has to follow the part, not its turn in the list. Alternating sides sends the leader for
200
200
  * a part on the left out to the right, straight across everything drawn in between, and a figure
201
201
  * whose lines cross the thing they are labelling cannot be read.
202
+ *
203
+ * Concentric parts are the exception, and a common one — a cell drawn as membrane, cytoplasm and
204
+ * nucleus shares one centre, so following the part would pile every label into one gutter and leave
205
+ * the other empty. A part with no side of its own alternates instead.
202
206
  */
203
207
  function layOutCallouts(diagram, bounds) {
204
208
  const [minX, minY, maxX, maxY] = bounds;
@@ -206,7 +210,9 @@ function layOutCallouts(diagram, bounds) {
206
210
  const labelled = diagram.parts.filter((part) => part.label !== void 0);
207
211
  const placed = [];
208
212
  const gutter = 56;
213
+ const offCentre = Math.max(1, maxX - minX) * .06;
209
214
  const automatic = [];
215
+ let alternated = 0;
210
216
  for (const part of labelled) {
211
217
  if (part.labelAt) {
212
218
  const { at: at$1 } = boundsOf(part);
@@ -218,9 +224,16 @@ function layOutCallouts(diagram, bounds) {
218
224
  continue;
219
225
  }
220
226
  const { at } = boundsOf(part);
227
+ const offset = at[0] - centreX;
228
+ let side;
229
+ if (Math.abs(offset) > offCentre) side = offset < 0 ? "left" : "right";
230
+ else {
231
+ side = alternated % 2 === 0 ? "left" : "right";
232
+ alternated += 1;
233
+ }
221
234
  automatic.push({
222
235
  part,
223
- side: at[0] <= centreX ? "left" : "right",
236
+ side,
224
237
  y: at[1]
225
238
  });
226
239
  }
@@ -312,14 +325,30 @@ function renderFigureSVG(diagram, options = {}) {
312
325
  "</g></svg>"
313
326
  ].join("");
314
327
  }
328
+ /**
329
+ * How wide a string will draw, near enough to reserve space for.
330
+ *
331
+ * The browser lays the text out, so this cannot be measured here — and a fixed allowance is what
332
+ * clipped "control center containing DNA" to "trol center containing DNA" against the left edge. A
333
+ * CJK glyph is about as wide as the font size; Latin runs a little over half.
334
+ */
335
+ function textWidth(text, fontSize) {
336
+ let width = 0;
337
+ for (const character of text) {
338
+ const code = character.codePointAt(0) ?? 0;
339
+ const wide = code > 11903 && code < 40960;
340
+ width += wide ? fontSize : fontSize * .58;
341
+ }
342
+ return width;
343
+ }
315
344
  /** Widen the drawing box until every callout, and the caption, is inside it. */
316
345
  function viewWithCallouts(bounds, callouts, hasCaption) {
317
- const textRoom = 120;
318
346
  const xs = [bounds[0], bounds[2]];
319
347
  const ys = [bounds[1], bounds[3]];
320
348
  for (const callout of callouts) {
321
- xs.push(callout.side === "left" ? callout.at[0] - textRoom : callout.at[0] + textRoom);
322
- ys.push(callout.at[1] - 20, callout.at[1] + 12);
349
+ const room = Math.max(textWidth(callout.part.label ?? "", 13), textWidth(callout.part.note ?? "", 11));
350
+ xs.push(callout.side === "left" ? callout.at[0] - room : callout.at[0] + room);
351
+ ys.push(callout.at[1] - (callout.part.note ? 26 : 12), callout.at[1] + 12);
323
352
  }
324
353
  const pad = 12;
325
354
  return [
package/dist/index.js CHANGED
@@ -197,6 +197,10 @@ function figureBounds(diagram) {
197
197
  * The side has to follow the part, not its turn in the list. Alternating sides sends the leader for
198
198
  * a part on the left out to the right, straight across everything drawn in between, and a figure
199
199
  * whose lines cross the thing they are labelling cannot be read.
200
+ *
201
+ * Concentric parts are the exception, and a common one — a cell drawn as membrane, cytoplasm and
202
+ * nucleus shares one centre, so following the part would pile every label into one gutter and leave
203
+ * the other empty. A part with no side of its own alternates instead.
200
204
  */
201
205
  function layOutCallouts(diagram, bounds) {
202
206
  const [minX, minY, maxX, maxY] = bounds;
@@ -204,7 +208,9 @@ function layOutCallouts(diagram, bounds) {
204
208
  const labelled = diagram.parts.filter((part) => part.label !== void 0);
205
209
  const placed = [];
206
210
  const gutter = 56;
211
+ const offCentre = Math.max(1, maxX - minX) * .06;
207
212
  const automatic = [];
213
+ let alternated = 0;
208
214
  for (const part of labelled) {
209
215
  if (part.labelAt) {
210
216
  const { at: at$1 } = boundsOf(part);
@@ -216,9 +222,16 @@ function layOutCallouts(diagram, bounds) {
216
222
  continue;
217
223
  }
218
224
  const { at } = boundsOf(part);
225
+ const offset = at[0] - centreX;
226
+ let side;
227
+ if (Math.abs(offset) > offCentre) side = offset < 0 ? "left" : "right";
228
+ else {
229
+ side = alternated % 2 === 0 ? "left" : "right";
230
+ alternated += 1;
231
+ }
219
232
  automatic.push({
220
233
  part,
221
- side: at[0] <= centreX ? "left" : "right",
234
+ side,
222
235
  y: at[1]
223
236
  });
224
237
  }
@@ -310,14 +323,30 @@ function renderFigureSVG(diagram, options = {}) {
310
323
  "</g></svg>"
311
324
  ].join("");
312
325
  }
326
+ /**
327
+ * How wide a string will draw, near enough to reserve space for.
328
+ *
329
+ * The browser lays the text out, so this cannot be measured here — and a fixed allowance is what
330
+ * clipped "control center containing DNA" to "trol center containing DNA" against the left edge. A
331
+ * CJK glyph is about as wide as the font size; Latin runs a little over half.
332
+ */
333
+ function textWidth(text, fontSize) {
334
+ let width = 0;
335
+ for (const character of text) {
336
+ const code = character.codePointAt(0) ?? 0;
337
+ const wide = code > 11903 && code < 40960;
338
+ width += wide ? fontSize : fontSize * .58;
339
+ }
340
+ return width;
341
+ }
313
342
  /** Widen the drawing box until every callout, and the caption, is inside it. */
314
343
  function viewWithCallouts(bounds, callouts, hasCaption) {
315
- const textRoom = 120;
316
344
  const xs = [bounds[0], bounds[2]];
317
345
  const ys = [bounds[1], bounds[3]];
318
346
  for (const callout of callouts) {
319
- xs.push(callout.side === "left" ? callout.at[0] - textRoom : callout.at[0] + textRoom);
320
- ys.push(callout.at[1] - 20, callout.at[1] + 12);
347
+ const room = Math.max(textWidth(callout.part.label ?? "", 13), textWidth(callout.part.note ?? "", 11));
348
+ xs.push(callout.side === "left" ? callout.at[0] - room : callout.at[0] + room);
349
+ ys.push(callout.at[1] - (callout.part.note ? 26 : 12), callout.at[1] + 12);
321
350
  }
322
351
  const pad = 12;
323
352
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-gui/plugin-figure",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Labelled figures — regions with leader-line callouts — rendered as SVG from a declarative block.",
5
5
  "keywords": [
6
6
  "llm",
@@ -54,7 +54,7 @@
54
54
  "access": "public"
55
55
  },
56
56
  "dependencies": {
57
- "@ai-gui/core": "0.11.0"
57
+ "@ai-gui/core": "0.12.0"
58
58
  },
59
59
  "scripts": {
60
60
  "build": "tsdown",