@rse/gradia 0.9.8 → 1.0.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.
@@ -81,6 +81,9 @@ export const render = async (graph, config) => {
81
81
  const scale = config["size-node-height-scale"];
82
82
  /* the fixed three-column layout assignment */
83
83
  const colOf = (id) => id === center.id ? 1 : (inputSet.has(id) ? 0 : 2);
84
+ /* determine the attachment sides of every edge (east/west),
85
+ derived from the column relation of its endpoint nodes */
86
+ const sides = edges.map((edge) => colOf(edge.source) < colOf(edge.target) ? { s: "e", t: "w" } : { s: "w", t: "e" });
84
87
  /* determine node box sizes from their textual content (all boxes at
85
88
  half height scale), then grow every box whose edge attachments
86
89
  exceed the configured per-side maximum, step-wise by one port
@@ -88,12 +91,10 @@ export const render = async (graph, config) => {
88
91
  attachment room without a fixed height increase */
89
92
  const { boxW, boxH, contentH } = measureNodes(nodes, config, () => scale / 2);
90
93
  const portCnt = new Map();
91
- for (const edge of edges) {
92
- const sSide = colOf(edge.source) < colOf(edge.target) ? "e" : "w";
93
- const tSide = sSide === "e" ? "w" : "e";
94
- portCnt.set(`${sSide}:${edge.source}`, (portCnt.get(`${sSide}:${edge.source}`) ?? 0) + 1);
95
- portCnt.set(`${tSide}:${edge.target}`, (portCnt.get(`${tSide}:${edge.target}`) ?? 0) + 1);
96
- }
94
+ edges.forEach((edge, i) => {
95
+ portCnt.set(`${sides[i].s}:${edge.source}`, (portCnt.get(`${sides[i].s}:${edge.source}`) ?? 0) + 1);
96
+ portCnt.set(`${sides[i].t}:${edge.target}`, (portCnt.get(`${sides[i].t}:${edge.target}`) ?? 0) + 1);
97
+ });
97
98
  for (const node of nodes) {
98
99
  const cnt = Math.max(portCnt.get(`w:${node.id}`) ?? 0, portCnt.get(`e:${node.id}`) ?? 0);
99
100
  if (cnt > config["hub-node-degree-max"])
@@ -148,9 +149,6 @@ export const render = async (graph, config) => {
148
149
  return colLX[1] + colWidth[1] / 2;
149
150
  };
150
151
  const cy = (id) => nodeCY.get(id);
151
- /* determine the attachment sides of every edge (east/west),
152
- derived from the column relation of its endpoint nodes */
153
- const sides = edges.map((edge) => colOf(edge.source) < colOf(edge.target) ? { s: "e", t: "w" } : { s: "w", t: "e" });
154
152
  /* distribute the edge attachment ports along each node side */
155
153
  const portPos = assignPorts(edges, sides, cx, cy, boxW, boxH);
156
154
  /* assign the vertical tracks within each channel (see assignTracks
package/dst/gradia-api.js CHANGED
@@ -39,7 +39,7 @@ export const diagramFormatDefault = "svg:standalone";
39
39
  export class Gradia {
40
40
  /* the rendering configuration options and their default values
41
41
  (exposed so consumers can discover and validate the options) */
42
- static config = configDefaults;
42
+ static config = Object.freeze({ ...configDefaults });
43
43
  /* parse a graph description into the graph model */
44
44
  static parse(spec) {
45
45
  return parse(spec);
@@ -67,15 +67,31 @@ export class Gradia {
67
67
  const layout = parts === null ?
68
68
  await renderers[type](graph, config) :
69
69
  composeGroups(parts, await Promise.all(parts.map((part) => renderers[type](part.graph, config))), config);
70
+ /* derive the identifier seed of the SVG document from everything
71
+ which determines the rendered output, so that regenerating an
72
+ unchanged diagram yields a byte-identical SVG (the explicitly
73
+ configured options enter as their sorted key list only, as
74
+ their values are already part of the layered configuration and
75
+ merely their presence decides between a hard-coded CSS value
76
+ and a CSS custom property lookup) */
77
+ const seed = JSON.stringify({
78
+ type,
79
+ config,
80
+ explicit: Object.keys(explicit).sort(),
81
+ nodes: Array.from(graph.nodes.values()),
82
+ edges: graph.edges
83
+ });
70
84
  /* render the laid out graph into an SVG document */
71
- const svg = renderSVG(layout, config, explicit);
72
- /* convert the SVG document into the requested output format */
85
+ const svg = renderSVG(layout, config, explicit, seed);
86
+ /* convert the SVG document into the requested output format
87
+ (the Base64 encoding uses the platform-neutral "btoa", as the
88
+ API is also shipped as a browser bundle without "Buffer") */
73
89
  if (format === "svg:embedded")
74
90
  return svg.replace(/^<\?xml[^?]*\?>\n/, "");
75
91
  else if (format === "url:xml")
76
92
  return `data:image/svg+xml,${encodeURIComponent(svg)}`;
77
93
  else if (format === "url:base64")
78
- return `data:image/svg+xml;base64,${Buffer.from(svg, "utf8").toString("base64")}`;
94
+ return `data:image/svg+xml;base64,${btoa(Array.from(new TextEncoder().encode(svg), (byte) => String.fromCharCode(byte)).join(""))}`;
79
95
  else
80
96
  return svg;
81
97
  }