capdag 0.187.479 → 0.188.482

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.
@@ -379,8 +379,12 @@ function layoutForMode(mode) {
379
379
  };
380
380
  if (mode === 'browse') {
381
381
  return Object.assign({}, base, {
382
- 'elk.layered.spacing.nodeNodeBetweenLayers': 180,
383
- 'elk.spacing.nodeNode': 90,
382
+ 'elk.layered.spacing.nodeNodeBetweenLayers': 220,
383
+ 'elk.layered.spacing.edgeEdgeBetweenLayers': 44,
384
+ 'elk.layered.spacing.edgeNodeBetweenLayers': 52,
385
+ 'elk.spacing.edgeEdge': 34,
386
+ 'elk.spacing.edgeNode': 42,
387
+ 'elk.spacing.nodeNode': 118,
384
388
  });
385
389
  }
386
390
  if (mode === 'strand') {
@@ -536,8 +540,10 @@ function buildStylesheet() {
536
540
  // text no longer reads as floating metadata.
537
541
  'text-rotation': 'autorotate',
538
542
  'text-margin-y': -6,
539
- 'curve-style': 'bezier',
540
- 'control-point-step-size': 40,
543
+ 'curve-style': 'data(curveStyle)',
544
+ 'control-point-step-size': 'data(controlPointStepSize)',
545
+ 'control-point-distances': 'data(controlPointDistances)',
546
+ 'control-point-weights': 'data(controlPointWeights)',
541
547
  'width': 1.5,
542
548
  'line-color': 'data(color)',
543
549
  'target-arrow-color': 'data(color)',
@@ -690,6 +696,12 @@ function assertArray(value, path) {
690
696
  }
691
697
  }
692
698
 
699
+ function assertObject(value, path) {
700
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
701
+ throw new Error(`CapFabRenderer: ${path} must be an object`);
702
+ }
703
+ }
704
+
693
705
  function validateBrowseData(data) {
694
706
  assertArray(data, 'browse mode data');
695
707
  data.forEach((cap, idx) => {
@@ -961,6 +973,81 @@ function edgeHueColor(edgeIdx) {
961
973
  return `hsl(${hue}, 60%, 55%)`;
962
974
  }
963
975
 
976
+ function centeredOrdinal(index, total) {
977
+ if (!Number.isInteger(index) || !Number.isInteger(total) || total <= 0) {
978
+ throw new Error('CapFabRenderer: centeredOrdinal requires integer index/total');
979
+ }
980
+ return index - ((total - 1) / 2);
981
+ }
982
+
983
+ function crowdOffsets(index, total, step, maxAbs) {
984
+ if (total <= 1) return 0;
985
+ const raw = centeredOrdinal(index, total) * step;
986
+ if (maxAbs === undefined) return raw;
987
+ return Math.max(-maxAbs, Math.min(maxAbs, raw));
988
+ }
989
+
990
+ function annotateCrowdedBrowseEdges(edges) {
991
+ const bySource = new Map();
992
+ const byTarget = new Map();
993
+
994
+ for (const edge of edges) {
995
+ if (!bySource.has(edge.source)) bySource.set(edge.source, []);
996
+ bySource.get(edge.source).push(edge);
997
+ if (!byTarget.has(edge.target)) byTarget.set(edge.target, []);
998
+ byTarget.get(edge.target).push(edge);
999
+ }
1000
+
1001
+ const stableSort = (a, b) => {
1002
+ const targetCmp = a.target.localeCompare(b.target);
1003
+ if (targetCmp !== 0) return targetCmp;
1004
+ const titleCmp = a.title.localeCompare(b.title);
1005
+ if (titleCmp !== 0) return titleCmp;
1006
+ return a.id.localeCompare(b.id);
1007
+ };
1008
+ const reverseStableSort = (a, b) => {
1009
+ const sourceCmp = a.source.localeCompare(b.source);
1010
+ if (sourceCmp !== 0) return sourceCmp;
1011
+ const titleCmp = a.title.localeCompare(b.title);
1012
+ if (titleCmp !== 0) return titleCmp;
1013
+ return a.id.localeCompare(b.id);
1014
+ };
1015
+
1016
+ for (const group of bySource.values()) group.sort(stableSort);
1017
+ for (const group of byTarget.values()) group.sort(reverseStableSort);
1018
+
1019
+ const sourceIndex = new Map();
1020
+ const targetIndex = new Map();
1021
+ for (const group of bySource.values()) {
1022
+ group.forEach((edge, idx) => sourceIndex.set(edge.id, idx));
1023
+ }
1024
+ for (const group of byTarget.values()) {
1025
+ group.forEach((edge, idx) => targetIndex.set(edge.id, idx));
1026
+ }
1027
+
1028
+ for (const edge of edges) {
1029
+ const sourceGroup = bySource.get(edge.source) || [edge];
1030
+ const targetGroup = byTarget.get(edge.target) || [edge];
1031
+ const sourceCount = sourceGroup.length;
1032
+ const targetCount = targetGroup.length;
1033
+ const crowdCount = Math.max(sourceCount, targetCount);
1034
+
1035
+ if (crowdCount <= 2) {
1036
+ edge.curveStyle = 'bezier';
1037
+ edge.controlPointStepSize = 40;
1038
+ continue;
1039
+ }
1040
+
1041
+ const sourceOffset = crowdOffsets(sourceIndex.get(edge.id), sourceCount, 18, 64);
1042
+ const targetOffset = crowdOffsets(targetIndex.get(edge.id), targetCount, 18, 64);
1043
+
1044
+ edge.curveStyle = 'unbundled-bezier';
1045
+ edge.controlPointDistances = `${sourceOffset} ${targetOffset}`;
1046
+ edge.controlPointWeights = '0.22 0.78';
1047
+ edge.controlPointStepSize = 56;
1048
+ }
1049
+ }
1050
+
964
1051
  // --------- Browse mode builder ----------------------------------------------
965
1052
 
966
1053
  function buildBrowseGraphData(capabilities) {
@@ -1014,6 +1101,7 @@ function buildBrowseGraphData(capabilities) {
1014
1101
  edges.forEach((edge, i) => {
1015
1102
  edge.color = edgeHueColor(i);
1016
1103
  });
1104
+ annotateCrowdedBrowseEdges(edges);
1017
1105
 
1018
1106
  const nodes = Array.from(nodesMap.values());
1019
1107
  for (const node of nodes) {
@@ -1061,6 +1149,10 @@ function browseCytoscapeElements(built) {
1061
1149
  fullUrn: edge.capability.urn,
1062
1150
  capFabEdgeIndex: edge.capFabEdgeIndex,
1063
1151
  color: edge.color,
1152
+ curveStyle: edge.curveStyle || 'bezier',
1153
+ controlPointStepSize: edge.controlPointStepSize || 40,
1154
+ controlPointDistances: edge.controlPointDistances || '',
1155
+ controlPointWeights: edge.controlPointWeights || '',
1064
1156
  },
1065
1157
  };
1066
1158
  });
package/package.json CHANGED
@@ -40,5 +40,5 @@
40
40
  "pretest": "npm run build:parser",
41
41
  "test": "node capdag.test.js"
42
42
  },
43
- "version": "0.187.479"
43
+ "version": "0.188.482"
44
44
  }