@tscircuit/schematic-viewer 2.0.27 → 2.0.29
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 +164 -73
- 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 +6 -1
- package/lib/hooks/useSchematicGroupsOverlay.ts +216 -85
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -195,7 +195,30 @@ 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
|
|
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
|
+
];
|
|
220
|
+
var useSchematicGroupsOverlay = (options) => {
|
|
221
|
+
const { svgDivRef, circuitJson, circuitJsonKey, showGroups } = options;
|
|
199
222
|
useEffect3(() => {
|
|
200
223
|
if (!svgDivRef.current || !showGroups || !circuitJson || circuitJson.length === 0) {
|
|
201
224
|
if (svgDivRef.current) {
|
|
@@ -213,10 +236,40 @@ var useSchematicGroupsOverlay = (svgDivRef, circuitJson, circuitJsonKey, showGro
|
|
|
213
236
|
const existingOverlays = svg.querySelectorAll(".schematic-group-overlay");
|
|
214
237
|
existingOverlays.forEach((overlay) => overlay.remove());
|
|
215
238
|
try {
|
|
216
|
-
const sourceGroups = su3(circuitJson).source_group?.list() || [];
|
|
239
|
+
const sourceGroups = su3(circuitJson).source_group?.list().filter((x) => !!!x.is_subcircuit) || [];
|
|
217
240
|
const schematicComponents = su3(circuitJson).schematic_component?.list() || [];
|
|
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
|
+
};
|
|
271
|
+
const hasMeaningfulGroups = sourceGroups.length > 0 && sourceGroups.some((group) => group.name && group.name.trim() !== "");
|
|
218
272
|
let groupsToRender = [];
|
|
219
|
-
const hasMeaningfulGroups = sourceGroups.length > 0 && sourceGroups.some((group) => group.name && group.name !== "default" && group.name !== "");
|
|
220
273
|
if (hasMeaningfulGroups) {
|
|
221
274
|
const groupMap = /* @__PURE__ */ new Map();
|
|
222
275
|
for (const comp of schematicComponents) {
|
|
@@ -230,84 +283,138 @@ var useSchematicGroupsOverlay = (svgDivRef, circuitJson, circuitJsonKey, showGro
|
|
|
230
283
|
groupMap.get(sourceComp.source_group_id).push(comp);
|
|
231
284
|
}
|
|
232
285
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
);
|
|
238
|
-
return {
|
|
239
|
-
id: groupId,
|
|
240
|
-
name: group?.name || `Group ${index + 1}`,
|
|
241
|
-
components,
|
|
242
|
-
color: getGroupColor(index)
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
);
|
|
246
|
-
} else {
|
|
247
|
-
const componentTypeGroups = /* @__PURE__ */ new Map();
|
|
248
|
-
for (const comp of schematicComponents) {
|
|
249
|
-
const sourceComp = su3(circuitJson).source_component.get(
|
|
250
|
-
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
|
|
251
290
|
);
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
componentTypeGroups.set(componentType, []);
|
|
256
|
-
}
|
|
257
|
-
componentTypeGroups.get(componentType).push(comp);
|
|
291
|
+
for (const descendantGroupId of descendantGroups) {
|
|
292
|
+
const descendantComponents = groupMap.get(descendantGroupId) || [];
|
|
293
|
+
groupComponents = [...groupComponents, ...descendantComponents];
|
|
258
294
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
+
});
|
|
268
310
|
}
|
|
269
|
-
|
|
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) => {
|
|
270
319
|
if (group.components.length === 0) return;
|
|
271
320
|
const groupBounds = calculateGroupBounds(group.components, svg);
|
|
272
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;
|
|
273
331
|
const groupOverlay = document.createElementNS(
|
|
274
332
|
"http://www.w3.org/2000/svg",
|
|
275
333
|
"rect"
|
|
276
334
|
);
|
|
277
335
|
groupOverlay.setAttribute("class", "schematic-group-overlay");
|
|
278
|
-
groupOverlay.setAttribute(
|
|
279
|
-
|
|
336
|
+
groupOverlay.setAttribute(
|
|
337
|
+
"x",
|
|
338
|
+
(groupBounds.minX - totalPadding).toString()
|
|
339
|
+
);
|
|
340
|
+
groupOverlay.setAttribute(
|
|
341
|
+
"y",
|
|
342
|
+
(groupBounds.minY - totalPadding).toString()
|
|
343
|
+
);
|
|
280
344
|
groupOverlay.setAttribute(
|
|
281
345
|
"width",
|
|
282
|
-
(groupBounds.maxX - groupBounds.minX +
|
|
346
|
+
(groupBounds.maxX - groupBounds.minX + totalPadding * 2).toString()
|
|
283
347
|
);
|
|
284
348
|
groupOverlay.setAttribute(
|
|
285
349
|
"height",
|
|
286
|
-
(groupBounds.maxY - groupBounds.minY +
|
|
350
|
+
(groupBounds.maxY - groupBounds.minY + totalPadding * 2).toString()
|
|
287
351
|
);
|
|
288
352
|
groupOverlay.setAttribute("fill", "none");
|
|
289
353
|
groupOverlay.setAttribute("stroke", group.color);
|
|
290
|
-
groupOverlay.setAttribute("stroke-width",
|
|
291
|
-
groupOverlay.setAttribute("stroke-dasharray",
|
|
354
|
+
groupOverlay.setAttribute("stroke-width", strokeWidth.toString());
|
|
355
|
+
groupOverlay.setAttribute("stroke-dasharray", `${dashSize},${gapSize}`);
|
|
292
356
|
groupOverlay.setAttribute("opacity", "0.8");
|
|
293
357
|
groupOverlay.setAttribute("rx", "4");
|
|
294
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");
|
|
295
393
|
const groupLabel = document.createElementNS(
|
|
296
394
|
"http://www.w3.org/2000/svg",
|
|
297
395
|
"text"
|
|
298
396
|
);
|
|
299
397
|
groupLabel.setAttribute("class", "schematic-group-overlay");
|
|
300
|
-
groupLabel.setAttribute("x", (
|
|
301
|
-
groupLabel.setAttribute(
|
|
398
|
+
groupLabel.setAttribute("x", (labelX + labelPadding).toString());
|
|
399
|
+
groupLabel.setAttribute(
|
|
400
|
+
"y",
|
|
401
|
+
(labelY + labelHeight - labelPadding).toString()
|
|
402
|
+
);
|
|
302
403
|
groupLabel.setAttribute("fill", group.color);
|
|
303
|
-
groupLabel.setAttribute("font-size",
|
|
404
|
+
groupLabel.setAttribute("font-size", fontSize.toString());
|
|
304
405
|
groupLabel.setAttribute("font-family", "Arial, sans-serif");
|
|
305
|
-
groupLabel.setAttribute(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
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;
|
|
310
416
|
svg.appendChild(groupOverlay);
|
|
417
|
+
svg.appendChild(labelBg);
|
|
311
418
|
svg.appendChild(groupLabel);
|
|
312
419
|
});
|
|
313
420
|
} catch (error) {
|
|
@@ -315,27 +422,6 @@ var useSchematicGroupsOverlay = (svgDivRef, circuitJson, circuitJsonKey, showGro
|
|
|
315
422
|
}
|
|
316
423
|
}, [svgDivRef, circuitJsonKey, showGroups]);
|
|
317
424
|
};
|
|
318
|
-
function getGroupColor(index) {
|
|
319
|
-
const colors2 = [
|
|
320
|
-
"#FF6B6B",
|
|
321
|
-
// Red
|
|
322
|
-
"#4ECDC4",
|
|
323
|
-
// Teal
|
|
324
|
-
"#45B7D1",
|
|
325
|
-
// Blue
|
|
326
|
-
"#96CEB4",
|
|
327
|
-
// Green
|
|
328
|
-
"#FF8C42",
|
|
329
|
-
// Orange
|
|
330
|
-
"#DDA0DD",
|
|
331
|
-
// Plum
|
|
332
|
-
"#98D8C8",
|
|
333
|
-
// Mint
|
|
334
|
-
"#F7DC6F"
|
|
335
|
-
// Light Yellow
|
|
336
|
-
];
|
|
337
|
-
return colors2[index % colors2.length];
|
|
338
|
-
}
|
|
339
425
|
function calculateGroupBounds(components, svg) {
|
|
340
426
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
341
427
|
for (const component of components) {
|
|
@@ -1500,7 +1586,12 @@ var SchematicViewer = ({
|
|
|
1500
1586
|
activeEditEvent,
|
|
1501
1587
|
editEvents: editEventsWithUnappliedEditEvents
|
|
1502
1588
|
});
|
|
1503
|
-
useSchematicGroupsOverlay(
|
|
1589
|
+
useSchematicGroupsOverlay({
|
|
1590
|
+
svgDivRef,
|
|
1591
|
+
circuitJson,
|
|
1592
|
+
circuitJsonKey,
|
|
1593
|
+
showGroups: showSchematicGroups
|
|
1594
|
+
});
|
|
1504
1595
|
const svgDiv = useMemo2(
|
|
1505
1596
|
() => /* @__PURE__ */ jsx9(
|
|
1506
1597
|
"div",
|