@tscircuit/schematic-viewer 2.0.28 → 2.0.30
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.js +157 -72
- package/dist/index.js.map +1 -1
- package/examples/example10-groups-view-schematic-groups.fixture.tsx +49 -63
- package/examples/example11-automatic-grouping-view-schematic-groups.fixture.tsx +63 -53
- package/lib/components/SchematicViewer.tsx +1 -1
- package/lib/hooks/useSchematicGroupsOverlay.ts +207 -82
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -195,6 +195,28 @@ var useChangeSchematicTracesForMovedComponents = ({
|
|
|
195
195
|
// lib/hooks/useSchematicGroupsOverlay.ts
|
|
196
196
|
import { useEffect as useEffect3 } from "react";
|
|
197
197
|
import { su as su3 } from "@tscircuit/soup-util";
|
|
198
|
+
var GROUP_COLORS = [
|
|
199
|
+
"#8B0000",
|
|
200
|
+
// Dark Red
|
|
201
|
+
"#2F4F4F",
|
|
202
|
+
// Dark Slate Gray
|
|
203
|
+
"#191970",
|
|
204
|
+
// Midnight Blue
|
|
205
|
+
"#006400",
|
|
206
|
+
// Dark Green
|
|
207
|
+
"#FF4500",
|
|
208
|
+
// Dark Orange
|
|
209
|
+
"#800080",
|
|
210
|
+
// Purple
|
|
211
|
+
"#2E8B57",
|
|
212
|
+
// Sea Green
|
|
213
|
+
"#B8860B",
|
|
214
|
+
// Dark Goldenrod
|
|
215
|
+
"#C71585",
|
|
216
|
+
// Medium Violet Red
|
|
217
|
+
"#008B8B"
|
|
218
|
+
// Dark Cyan
|
|
219
|
+
];
|
|
198
220
|
var useSchematicGroupsOverlay = (options) => {
|
|
199
221
|
const { svgDivRef, circuitJson, circuitJsonKey, showGroups } = options;
|
|
200
222
|
useEffect3(() => {
|
|
@@ -214,10 +236,40 @@ var useSchematicGroupsOverlay = (options) => {
|
|
|
214
236
|
const existingOverlays = svg.querySelectorAll(".schematic-group-overlay");
|
|
215
237
|
existingOverlays.forEach((overlay) => overlay.remove());
|
|
216
238
|
try {
|
|
217
|
-
const sourceGroups = su3(circuitJson).source_group?.list() || [];
|
|
239
|
+
const sourceGroups = su3(circuitJson).source_group?.list().filter((x) => !!!x.is_subcircuit) || [];
|
|
218
240
|
const schematicComponents = su3(circuitJson).schematic_component?.list() || [];
|
|
219
|
-
|
|
241
|
+
const sourceGroupHierarchy = /* @__PURE__ */ new Map();
|
|
242
|
+
sourceGroups.forEach((group) => {
|
|
243
|
+
const groupWithParent = group;
|
|
244
|
+
if (groupWithParent.parent_source_group_id) {
|
|
245
|
+
const children = sourceGroupHierarchy.get(groupWithParent.parent_source_group_id) || [];
|
|
246
|
+
children.push(group.source_group_id);
|
|
247
|
+
sourceGroupHierarchy.set(
|
|
248
|
+
groupWithParent.parent_source_group_id,
|
|
249
|
+
children
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const getAllDescendantSourceGroups = (sourceGroupId) => {
|
|
254
|
+
const descendants = [];
|
|
255
|
+
const children = sourceGroupHierarchy.get(sourceGroupId) || [];
|
|
256
|
+
for (const child of children) {
|
|
257
|
+
descendants.push(child);
|
|
258
|
+
descendants.push(...getAllDescendantSourceGroups(child));
|
|
259
|
+
}
|
|
260
|
+
return descendants;
|
|
261
|
+
};
|
|
262
|
+
const getGroupDepthLevel = (sourceGroupId) => {
|
|
263
|
+
const groupWithParent = sourceGroups.find(
|
|
264
|
+
(g) => g.source_group_id === sourceGroupId
|
|
265
|
+
);
|
|
266
|
+
if (!groupWithParent?.parent_source_group_id) {
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
return 1 + getGroupDepthLevel(groupWithParent.parent_source_group_id);
|
|
270
|
+
};
|
|
220
271
|
const hasMeaningfulGroups = sourceGroups.length > 0 && sourceGroups.some((group) => group.name && group.name.trim() !== "");
|
|
272
|
+
let groupsToRender = [];
|
|
221
273
|
if (hasMeaningfulGroups) {
|
|
222
274
|
const groupMap = /* @__PURE__ */ new Map();
|
|
223
275
|
for (const comp of schematicComponents) {
|
|
@@ -231,84 +283,138 @@ var useSchematicGroupsOverlay = (options) => {
|
|
|
231
283
|
groupMap.get(sourceComp.source_group_id).push(comp);
|
|
232
284
|
}
|
|
233
285
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
239
|
-
return {
|
|
240
|
-
id: groupId,
|
|
241
|
-
name: group?.name || `Group ${index + 1}`,
|
|
242
|
-
components,
|
|
243
|
-
color: getGroupColor(index)
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
);
|
|
247
|
-
} else {
|
|
248
|
-
const componentTypeGroups = /* @__PURE__ */ new Map();
|
|
249
|
-
for (const comp of schematicComponents) {
|
|
250
|
-
const sourceComp = su3(circuitJson).source_component.get(
|
|
251
|
-
comp.source_component_id
|
|
286
|
+
sourceGroups.forEach((group, index) => {
|
|
287
|
+
let groupComponents = groupMap.get(group.source_group_id) || [];
|
|
288
|
+
const descendantGroups = getAllDescendantSourceGroups(
|
|
289
|
+
group.source_group_id
|
|
252
290
|
);
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
componentTypeGroups.set(componentType, []);
|
|
257
|
-
}
|
|
258
|
-
componentTypeGroups.get(componentType).push(comp);
|
|
291
|
+
for (const descendantGroupId of descendantGroups) {
|
|
292
|
+
const descendantComponents = groupMap.get(descendantGroupId) || [];
|
|
293
|
+
groupComponents = [...groupComponents, ...descendantComponents];
|
|
259
294
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
295
|
+
if (groupComponents.length > 0) {
|
|
296
|
+
const depthLevel = getGroupDepthLevel(group.source_group_id);
|
|
297
|
+
const hasChildren = getAllDescendantSourceGroups(group.source_group_id).length > 0;
|
|
298
|
+
if (group.name?.startsWith("unnamed_board")) return;
|
|
299
|
+
groupsToRender.push({
|
|
300
|
+
id: group.source_group_id,
|
|
301
|
+
name: group.name || `Group ${index + 1}`,
|
|
302
|
+
components: groupComponents,
|
|
303
|
+
color: GROUP_COLORS[index % GROUP_COLORS.length],
|
|
304
|
+
depthLevel,
|
|
305
|
+
hasChildren,
|
|
306
|
+
sourceGroupId: group.source_group_id
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
});
|
|
269
310
|
}
|
|
270
|
-
|
|
311
|
+
const viewBox = svg.viewBox.baseVal;
|
|
312
|
+
const svgRect = svg.getBoundingClientRect();
|
|
313
|
+
const scale = Math.min(
|
|
314
|
+
svgRect.width / viewBox.width,
|
|
315
|
+
svgRect.height / viewBox.height
|
|
316
|
+
) || 1;
|
|
317
|
+
groupsToRender.sort((a, b) => a.depthLevel - b.depthLevel);
|
|
318
|
+
groupsToRender.forEach((group) => {
|
|
271
319
|
if (group.components.length === 0) return;
|
|
272
320
|
const groupBounds = calculateGroupBounds(group.components, svg);
|
|
273
321
|
if (!groupBounds) return;
|
|
322
|
+
const basePadding = Math.max(8, Math.min(25, 15 / Math.max(scale, 0.3)));
|
|
323
|
+
const hierarchyPadding = group.hasChildren ? basePadding * 0.6 : 0;
|
|
324
|
+
const totalPadding = basePadding + hierarchyPadding;
|
|
325
|
+
const baseStrokeWidth = Math.max(1, 2 / Math.max(scale, 0.5));
|
|
326
|
+
const strokeWidth = group.depthLevel === 0 ? baseStrokeWidth : baseStrokeWidth * 0.7;
|
|
327
|
+
const baseDashSize = Math.max(4, 8 / Math.max(scale, 0.5));
|
|
328
|
+
const dashMultiplier = group.hasChildren ? 1.3 : 1;
|
|
329
|
+
const dashSize = baseDashSize * dashMultiplier;
|
|
330
|
+
const gapSize = dashSize * 0.5;
|
|
274
331
|
const groupOverlay = document.createElementNS(
|
|
275
332
|
"http://www.w3.org/2000/svg",
|
|
276
333
|
"rect"
|
|
277
334
|
);
|
|
278
335
|
groupOverlay.setAttribute("class", "schematic-group-overlay");
|
|
279
|
-
groupOverlay.setAttribute(
|
|
280
|
-
|
|
336
|
+
groupOverlay.setAttribute(
|
|
337
|
+
"x",
|
|
338
|
+
(groupBounds.minX - totalPadding).toString()
|
|
339
|
+
);
|
|
340
|
+
groupOverlay.setAttribute(
|
|
341
|
+
"y",
|
|
342
|
+
(groupBounds.minY - totalPadding).toString()
|
|
343
|
+
);
|
|
281
344
|
groupOverlay.setAttribute(
|
|
282
345
|
"width",
|
|
283
|
-
(groupBounds.maxX - groupBounds.minX +
|
|
346
|
+
(groupBounds.maxX - groupBounds.minX + totalPadding * 2).toString()
|
|
284
347
|
);
|
|
285
348
|
groupOverlay.setAttribute(
|
|
286
349
|
"height",
|
|
287
|
-
(groupBounds.maxY - groupBounds.minY +
|
|
350
|
+
(groupBounds.maxY - groupBounds.minY + totalPadding * 2).toString()
|
|
288
351
|
);
|
|
289
352
|
groupOverlay.setAttribute("fill", "none");
|
|
290
353
|
groupOverlay.setAttribute("stroke", group.color);
|
|
291
|
-
groupOverlay.setAttribute("stroke-width",
|
|
292
|
-
groupOverlay.setAttribute("stroke-dasharray",
|
|
354
|
+
groupOverlay.setAttribute("stroke-width", strokeWidth.toString());
|
|
355
|
+
groupOverlay.setAttribute("stroke-dasharray", `${dashSize},${gapSize}`);
|
|
293
356
|
groupOverlay.setAttribute("opacity", "0.8");
|
|
294
357
|
groupOverlay.setAttribute("rx", "4");
|
|
295
358
|
groupOverlay.setAttribute("ry", "4");
|
|
359
|
+
const baseFontSize = Math.max(
|
|
360
|
+
6,
|
|
361
|
+
Math.min(20, 14 / Math.max(scale, 0.2))
|
|
362
|
+
);
|
|
363
|
+
const fontSizeReduction = group.depthLevel === 0 || group.depthLevel === 1 ? 0 : group.depthLevel * 0.2;
|
|
364
|
+
const fontSize = baseFontSize * (1 - fontSizeReduction);
|
|
365
|
+
const labelPadding = Math.max(1, fontSize * 0.2);
|
|
366
|
+
const labelText = group.name;
|
|
367
|
+
const tempText = document.createElementNS(
|
|
368
|
+
"http://www.w3.org/2000/svg",
|
|
369
|
+
"text"
|
|
370
|
+
);
|
|
371
|
+
tempText.setAttribute("font-size", fontSize.toString());
|
|
372
|
+
tempText.setAttribute("font-family", "Arial, sans-serif");
|
|
373
|
+
tempText.textContent = labelText;
|
|
374
|
+
svg.appendChild(tempText);
|
|
375
|
+
const textBBox = tempText.getBBox();
|
|
376
|
+
svg.removeChild(tempText);
|
|
377
|
+
const labelWidth = textBBox.width + labelPadding * 2;
|
|
378
|
+
const labelHeight = fontSize + labelPadding * 2;
|
|
379
|
+
const labelX = groupBounds.minX - totalPadding;
|
|
380
|
+
const labelY = groupBounds.minY - totalPadding - labelHeight;
|
|
381
|
+
const labelBg = document.createElementNS(
|
|
382
|
+
"http://www.w3.org/2000/svg",
|
|
383
|
+
"rect"
|
|
384
|
+
);
|
|
385
|
+
labelBg.setAttribute("class", "schematic-group-overlay");
|
|
386
|
+
labelBg.setAttribute("x", labelX.toString());
|
|
387
|
+
labelBg.setAttribute("y", (labelY - labelHeight).toString());
|
|
388
|
+
labelBg.setAttribute("width", labelWidth.toString());
|
|
389
|
+
labelBg.setAttribute("height", labelHeight.toString());
|
|
390
|
+
labelBg.setAttribute("fill", "transparent");
|
|
391
|
+
labelBg.setAttribute("rx", "3");
|
|
392
|
+
labelBg.setAttribute("ry", "3");
|
|
296
393
|
const groupLabel = document.createElementNS(
|
|
297
394
|
"http://www.w3.org/2000/svg",
|
|
298
395
|
"text"
|
|
299
396
|
);
|
|
300
397
|
groupLabel.setAttribute("class", "schematic-group-overlay");
|
|
301
|
-
groupLabel.setAttribute("x", (
|
|
302
|
-
groupLabel.setAttribute(
|
|
398
|
+
groupLabel.setAttribute("x", (labelX + labelPadding).toString());
|
|
399
|
+
groupLabel.setAttribute(
|
|
400
|
+
"y",
|
|
401
|
+
(labelY + labelHeight - labelPadding).toString()
|
|
402
|
+
);
|
|
303
403
|
groupLabel.setAttribute("fill", group.color);
|
|
304
|
-
groupLabel.setAttribute("font-size",
|
|
404
|
+
groupLabel.setAttribute("font-size", fontSize.toString());
|
|
305
405
|
groupLabel.setAttribute("font-family", "Arial, sans-serif");
|
|
306
|
-
groupLabel.setAttribute(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
groupLabel.
|
|
406
|
+
groupLabel.setAttribute(
|
|
407
|
+
"font-weight",
|
|
408
|
+
group.depthLevel === 0 ? "600" : "500"
|
|
409
|
+
);
|
|
410
|
+
groupLabel.setAttribute("stroke", group.color);
|
|
411
|
+
groupLabel.setAttribute(
|
|
412
|
+
"stroke-width",
|
|
413
|
+
Math.max(0.2, fontSize * 0.02).toString()
|
|
414
|
+
);
|
|
415
|
+
groupLabel.textContent = labelText;
|
|
311
416
|
svg.appendChild(groupOverlay);
|
|
417
|
+
svg.appendChild(labelBg);
|
|
312
418
|
svg.appendChild(groupLabel);
|
|
313
419
|
});
|
|
314
420
|
} catch (error) {
|
|
@@ -316,27 +422,6 @@ var useSchematicGroupsOverlay = (options) => {
|
|
|
316
422
|
}
|
|
317
423
|
}, [svgDivRef, circuitJsonKey, showGroups]);
|
|
318
424
|
};
|
|
319
|
-
function getGroupColor(index) {
|
|
320
|
-
const colors2 = [
|
|
321
|
-
"#FF6B6B",
|
|
322
|
-
// Red
|
|
323
|
-
"#4ECDC4",
|
|
324
|
-
// Teal
|
|
325
|
-
"#45B7D1",
|
|
326
|
-
// Blue
|
|
327
|
-
"#96CEB4",
|
|
328
|
-
// Green
|
|
329
|
-
"#FF8C42",
|
|
330
|
-
// Orange
|
|
331
|
-
"#DDA0DD",
|
|
332
|
-
// Plum
|
|
333
|
-
"#98D8C8",
|
|
334
|
-
// Mint
|
|
335
|
-
"#F7DC6F"
|
|
336
|
-
// Light Yellow
|
|
337
|
-
];
|
|
338
|
-
return colors2[index % colors2.length];
|
|
339
|
-
}
|
|
340
425
|
function calculateGroupBounds(components, svg) {
|
|
341
426
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
342
427
|
for (const component of components) {
|
|
@@ -1448,7 +1533,7 @@ var SchematicViewer = ({
|
|
|
1448
1533
|
},
|
|
1449
1534
|
colorOverrides
|
|
1450
1535
|
});
|
|
1451
|
-
}, [
|
|
1536
|
+
}, [circuitJsonKey, containerWidth, containerHeight]);
|
|
1452
1537
|
const containerBackgroundColor = useMemo2(() => {
|
|
1453
1538
|
const match = svgString.match(
|
|
1454
1539
|
/<svg[^>]*style="[^"]*background-color:\s*([^;\"]+)/i
|