@crazyhappyone/auto-graph 0.2.2 → 0.2.4

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/dist/index.cjs CHANGED
@@ -4054,6 +4054,45 @@ function indentLines(values) {
4054
4054
  return values.map(indent);
4055
4055
  }
4056
4056
 
4057
+ // src/solver/pipeline/pipeline.ts
4058
+ var LayoutPipeline = class {
4059
+ phases = [];
4060
+ addPhase(phase) {
4061
+ this.phases.push(phase);
4062
+ return this;
4063
+ }
4064
+ addBefore(refName, phase) {
4065
+ const idx = this.phases.findIndex((p) => p.name === refName);
4066
+ if (idx === -1) throw new Error(`Phase "${refName}" not found`);
4067
+ this.phases.splice(idx, 0, phase);
4068
+ return this;
4069
+ }
4070
+ addAfter(refName, phase) {
4071
+ const idx = this.phases.findIndex((p) => p.name === refName);
4072
+ if (idx === -1) throw new Error(`Phase "${refName}" not found`);
4073
+ this.phases.splice(idx + 1, 0, phase);
4074
+ return this;
4075
+ }
4076
+ replacePhase(name, phase) {
4077
+ const idx = this.phases.findIndex((p) => p.name === name);
4078
+ if (idx === -1) throw new Error(`Phase "${name}" not found`);
4079
+ this.phases[idx] = phase;
4080
+ return this;
4081
+ }
4082
+ run(state) {
4083
+ for (const phase of this.phases) {
4084
+ const before = state.diagnostics.length;
4085
+ const start = performance.now();
4086
+ phase.run(state);
4087
+ state.phaseTrace.push({
4088
+ phase: phase.name,
4089
+ durationMs: performance.now() - start,
4090
+ diagnosticsAdded: state.diagnostics.length - before
4091
+ });
4092
+ }
4093
+ }
4094
+ };
4095
+
4057
4096
  // src/ir/diagnostics.ts
4058
4097
  var DELIVERABILITY_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
4059
4098
  "constraints.locked-target-not-moved",
@@ -5439,6 +5478,217 @@ function areCollinear2(a, b, c) {
5439
5478
  return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
5440
5479
  }
5441
5480
 
5481
+ // src/solver/pipeline/quality.ts
5482
+ function scoreLayoutQuality(nodes, edges) {
5483
+ const diagnostics = [];
5484
+ const metrics = [];
5485
+ const overlapCount = countNodeOverlaps(nodes);
5486
+ const overlapScore = Math.max(0, 20 - overlapCount * 5);
5487
+ metrics.push({
5488
+ kind: "node-overlap",
5489
+ value: overlapCount,
5490
+ label: `${overlapCount} overlaps`
5491
+ });
5492
+ if (overlapCount > 0) {
5493
+ diagnostics.push({
5494
+ severity: "warning",
5495
+ code: "quality.node_overlap",
5496
+ message: `${overlapCount} node pair(s) overlap.`,
5497
+ detail: { overlapCount }
5498
+ });
5499
+ }
5500
+ const crossingCount = countEdgeCrossings(edges);
5501
+ const crossingScore = Math.max(0, 20 - crossingCount * 2);
5502
+ metrics.push({
5503
+ kind: "edge-crossing",
5504
+ value: crossingCount,
5505
+ label: `${crossingCount} crossings`
5506
+ });
5507
+ if (crossingCount > 0) {
5508
+ diagnostics.push({
5509
+ severity: "warning",
5510
+ code: "quality.edge_crossing",
5511
+ message: `${crossingCount} edge segment pair(s) cross.`,
5512
+ detail: { crossingCount }
5513
+ });
5514
+ }
5515
+ const totalBends = countTotalBends(edges);
5516
+ const bendScore = Math.max(0, 20 - totalBends * 0.5);
5517
+ metrics.push({
5518
+ kind: "bend-count",
5519
+ value: totalBends,
5520
+ label: `${totalBends} bends`
5521
+ });
5522
+ const backtrackCount = countBacktrackingEdges(edges);
5523
+ const backtrackScore = Math.max(0, 20 - backtrackCount * 5);
5524
+ metrics.push({
5525
+ kind: "route-backtrack",
5526
+ value: backtrackCount,
5527
+ label: `${backtrackCount} backtracking`
5528
+ });
5529
+ if (backtrackCount > 0) {
5530
+ diagnostics.push({
5531
+ severity: "warning",
5532
+ code: "quality.route_backtrack",
5533
+ message: `${backtrackCount} edge(s) are excessively long (>3\xD7 direct).`,
5534
+ detail: { backtrackCount }
5535
+ });
5536
+ }
5537
+ const labelCollisions = countLabelCollisions(nodes, edges);
5538
+ const labelScore = Math.max(0, 20 - labelCollisions * 3);
5539
+ metrics.push({
5540
+ kind: "label-collision",
5541
+ value: labelCollisions,
5542
+ label: `${labelCollisions} label collisions`
5543
+ });
5544
+ const score = Math.max(
5545
+ 0,
5546
+ Math.min(
5547
+ 100,
5548
+ overlapScore + crossingScore + bendScore + backtrackScore + labelScore
5549
+ )
5550
+ );
5551
+ return { metrics, score, diagnostics };
5552
+ }
5553
+ function countNodeOverlaps(nodes) {
5554
+ let count = 0;
5555
+ for (let i = 0; i < nodes.length; i++) {
5556
+ for (let j = i + 1; j < nodes.length; j++) {
5557
+ if (intersectsAabb(nodes[i].box, nodes[j].box)) {
5558
+ count++;
5559
+ }
5560
+ }
5561
+ }
5562
+ return count;
5563
+ }
5564
+ function countEdgeCrossings(edges) {
5565
+ let count = 0;
5566
+ for (let i = 0; i < edges.length; i++) {
5567
+ const aPts = edges[i].points;
5568
+ for (let j = i + 1; j < edges.length; j++) {
5569
+ const bPts = edges[j].points;
5570
+ for (let ai = 0; ai < aPts.length - 1; ai++) {
5571
+ for (let bi = 0; bi < bPts.length - 1; bi++) {
5572
+ if (segmentsIntersect(
5573
+ aPts[ai],
5574
+ aPts[ai + 1],
5575
+ bPts[bi],
5576
+ bPts[bi + 1]
5577
+ )) {
5578
+ count++;
5579
+ }
5580
+ }
5581
+ }
5582
+ }
5583
+ }
5584
+ return count;
5585
+ }
5586
+ function segmentsIntersect(a, b, c, d) {
5587
+ const d1 = cross(c, d, a);
5588
+ const d2 = cross(c, d, b);
5589
+ const d3 = cross(a, b, c);
5590
+ const d4 = cross(a, b, d);
5591
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
5592
+ }
5593
+ function cross(o, a, b) {
5594
+ return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
5595
+ }
5596
+ function countTotalBends(edges) {
5597
+ const sign = (n) => n > 0 ? 1 : n < 0 ? -1 : 0;
5598
+ let bends = 0;
5599
+ for (const e of edges) {
5600
+ if (e.points.length < 3) continue;
5601
+ for (let i = 1; i < e.points.length - 1; i++) {
5602
+ const prev = e.points[i - 1];
5603
+ const curr = e.points[i];
5604
+ const next = e.points[i + 1];
5605
+ const dx1 = curr.x - prev.x;
5606
+ const dy1 = curr.y - prev.y;
5607
+ const dx2 = next.x - curr.x;
5608
+ const dy2 = next.y - curr.y;
5609
+ if (sign(dx1) !== sign(dx2) || sign(dy1) !== sign(dy2)) {
5610
+ bends++;
5611
+ }
5612
+ }
5613
+ }
5614
+ return bends;
5615
+ }
5616
+ function countBacktrackingEdges(edges) {
5617
+ let count = 0;
5618
+ for (const e of edges) {
5619
+ if (e.points.length < 2) continue;
5620
+ const first = e.points[0];
5621
+ const last = e.points[e.points.length - 1];
5622
+ const direct = Math.hypot(last.x - first.x, last.y - first.y);
5623
+ if (direct <= 0) continue;
5624
+ let routeLen = 0;
5625
+ for (let i = 0; i < e.points.length - 1; i++) {
5626
+ routeLen += Math.hypot(
5627
+ e.points[i + 1].x - e.points[i].x,
5628
+ e.points[i + 1].y - e.points[i].y
5629
+ );
5630
+ }
5631
+ if (routeLen > direct * 3) count++;
5632
+ }
5633
+ return count;
5634
+ }
5635
+ function countLabelCollisions(nodes, edges) {
5636
+ let count = 0;
5637
+ const nodeBoxes = /* @__PURE__ */ new Map();
5638
+ for (const n of nodes) {
5639
+ nodeBoxes.set(n.id, n.box);
5640
+ if (n.label?.text !== void 0) {
5641
+ const lw = n.label.text.length * 8;
5642
+ const labelBox = {
5643
+ x: n.box.x + n.box.width / 2 - lw / 2,
5644
+ y: n.box.y - 8,
5645
+ width: lw,
5646
+ height: 14
5647
+ };
5648
+ for (const [id, box] of nodeBoxes) {
5649
+ if (id === n.id) continue;
5650
+ if (intersectsAabb(labelBox, box)) count++;
5651
+ }
5652
+ for (const e of edges) {
5653
+ for (let i = 0; i < e.points.length - 1; i++) {
5654
+ if (segmentIntersectsBox2(e.points[i], e.points[i + 1], labelBox)) {
5655
+ count++;
5656
+ break;
5657
+ }
5658
+ }
5659
+ }
5660
+ }
5661
+ }
5662
+ return count;
5663
+ }
5664
+ function segmentIntersectsBox2(start, end, box) {
5665
+ const left = box.x;
5666
+ const right = box.x + box.width;
5667
+ const top = box.y;
5668
+ const bottom = box.y + box.height;
5669
+ if (start.x > left && start.x < right && start.y > top && start.y < bottom || end.x > left && end.x < right && end.y > top && end.y < bottom)
5670
+ return true;
5671
+ if (start.x === end.x) {
5672
+ return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
5673
+ }
5674
+ if (start.y === end.y) {
5675
+ return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
5676
+ }
5677
+ return edgeIntersect(start, end, left, top, right, top) || edgeIntersect(start, end, right, top, right, bottom) || edgeIntersect(start, end, right, bottom, left, bottom) || edgeIntersect(start, end, left, bottom, left, top);
5678
+ }
5679
+ function rangesOverlap3(a, b, min, max) {
5680
+ const lo = Math.min(a, b);
5681
+ const hi = Math.max(a, b);
5682
+ return hi > min && lo < max;
5683
+ }
5684
+ function edgeIntersect(start, end, x1, y1, x2, y2) {
5685
+ const denom = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
5686
+ if (denom === 0) return false;
5687
+ const t = ((start.x - x1) * (y2 - y1) - (start.y - y1) * (x2 - x1)) / denom;
5688
+ const u = ((start.x - x1) * (end.y - start.y) - (start.y - y1) * (end.x - start.x)) / denom;
5689
+ return t > 0 && t < 1 && u > 0 && u < 1;
5690
+ }
5691
+
5442
5692
  // src/solver/solve.ts
5443
5693
  var DEFAULT_MATRIX_CELL_SIZE2 = { width: 120, height: 36 };
5444
5694
  var DEFAULT_TABLE_CELL_SIZE2 = { width: 128, height: 34 };
@@ -8126,13 +8376,13 @@ function routeIntersectsTextBox(points, box) {
8126
8376
  if (start === void 0 || end === void 0) {
8127
8377
  continue;
8128
8378
  }
8129
- if (segmentIntersectsBox2(start, end, box)) {
8379
+ if (segmentIntersectsBox3(start, end, box)) {
8130
8380
  return true;
8131
8381
  }
8132
8382
  }
8133
8383
  return false;
8134
8384
  }
8135
- function segmentIntersectsBox2(start, end, box) {
8385
+ function segmentIntersectsBox3(start, end, box) {
8136
8386
  const left = box.x;
8137
8387
  const right = box.x + box.width;
8138
8388
  const top = box.y;
@@ -8141,17 +8391,17 @@ function segmentIntersectsBox2(start, end, box) {
8141
8391
  return true;
8142
8392
  }
8143
8393
  if (start.x === end.x) {
8144
- return start.x > left && start.x < right && rangesOverlap3(start.y, end.y, top, bottom);
8394
+ return start.x > left && start.x < right && rangesOverlap4(start.y, end.y, top, bottom);
8145
8395
  }
8146
8396
  if (start.y === end.y) {
8147
- return start.y > top && start.y < bottom && rangesOverlap3(start.x, end.x, left, right);
8397
+ return start.y > top && start.y < bottom && rangesOverlap4(start.x, end.x, left, right);
8148
8398
  }
8149
8399
  return segmentIntersectsBoxEdge2(start, end, left, top, right, top) || segmentIntersectsBoxEdge2(start, end, right, top, right, bottom) || segmentIntersectsBoxEdge2(start, end, right, bottom, left, bottom) || segmentIntersectsBoxEdge2(start, end, left, bottom, left, top);
8150
8400
  }
8151
8401
  function pointInsideBox2(point2, box) {
8152
8402
  return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
8153
8403
  }
8154
- function rangesOverlap3(a, b, min, max) {
8404
+ function rangesOverlap4(a, b, min, max) {
8155
8405
  const low = Math.min(a, b);
8156
8406
  const high = Math.max(a, b);
8157
8407
  return high > min && low < max;
@@ -8514,6 +8764,30 @@ function groupReferenceMissing(groupId, referenceKind, id) {
8514
8764
  detail: id === void 0 ? { groupId } : { groupId, id }
8515
8765
  };
8516
8766
  }
8767
+ function createDefaultPipeline() {
8768
+ return new LayoutPipeline().addPhase({
8769
+ name: "solve-diagram",
8770
+ run(state) {
8771
+ const result = solveDiagram(state.diagram, state.options);
8772
+ state.diagnostics.push(...result.diagnostics);
8773
+ state.bounds = result.bounds;
8774
+ state.degraded = result.degraded ?? false;
8775
+ state.coordinatedNodes = result.nodes;
8776
+ state.coordinatedEdges = result.edges;
8777
+ }
8778
+ }).addPhase({
8779
+ name: "quality-score",
8780
+ run(state) {
8781
+ if (!state.options.qualityScore) return;
8782
+ const report = scoreLayoutQuality(
8783
+ state.coordinatedNodes,
8784
+ state.coordinatedEdges
8785
+ );
8786
+ state.qualityReport = report;
8787
+ state.diagnostics.push(...report.diagnostics);
8788
+ }
8789
+ });
8790
+ }
8517
8791
 
8518
8792
  // src/dsl/render.ts
8519
8793
  function resolveOutputFormat(cliFormat, dslFormat) {
@@ -8756,6 +9030,7 @@ exports.DEFAULT_DSL_MAX_BYTES = DEFAULT_DSL_MAX_BYTES;
8756
9030
  exports.DELIVERABILITY_DIAGNOSTIC_CODES = DELIVERABILITY_DIAGNOSTIC_CODES;
8757
9031
  exports.DeterministicTextMeasurer = DeterministicTextMeasurer;
8758
9032
  exports.LabelFitter = LabelFitter;
9033
+ exports.LayoutPipeline = LayoutPipeline;
8759
9034
  exports.PretextTextMeasurer = PretextTextMeasurer;
8760
9035
  exports.applyLayoutConstraints = applyLayoutConstraints;
8761
9036
  exports.assertFiniteNonNegative = assertFiniteNonNegative;
@@ -8766,6 +9041,7 @@ exports.computeArrowhead = computeArrowhead;
8766
9041
  exports.computeContainerGeometry = computeContainerGeometry;
8767
9042
  exports.computeShapeGeometry = computeShapeGeometry;
8768
9043
  exports.createBoxSpatialIndex = createBoxSpatialIndex;
9044
+ exports.createDefaultPipeline = createDefaultPipeline;
8769
9045
  exports.createDefaultTextMeasurer = createDefaultTextMeasurer;
8770
9046
  exports.expandBox = expandBox;
8771
9047
  exports.expandBoxForQuery = expandBoxForQuery;