circuit-to-svg 0.0.206 → 0.0.208
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.d.ts +33 -1
- package/dist/index.js +531 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -293,6 +293,38 @@ interface Options {
|
|
|
293
293
|
}
|
|
294
294
|
declare function convertCircuitJsonToSolderPasteMask(circuitJson: AnyCircuitElement[], options: Options): string;
|
|
295
295
|
|
|
296
|
+
type ExperimentType = string;
|
|
297
|
+
interface SimulationExperimentElement {
|
|
298
|
+
type: "simulation_experiment";
|
|
299
|
+
simulation_experiment_id: string;
|
|
300
|
+
name: string;
|
|
301
|
+
experiment_type: ExperimentType;
|
|
302
|
+
}
|
|
303
|
+
interface SimulationTransientVoltageGraphElement {
|
|
304
|
+
type: "simulation_transient_voltage_graph";
|
|
305
|
+
simulation_transient_voltage_graph_id: string;
|
|
306
|
+
simulation_experiment_id: string;
|
|
307
|
+
timestamps_ms?: number[];
|
|
308
|
+
voltage_levels: number[];
|
|
309
|
+
schematic_voltage_probe_id?: string;
|
|
310
|
+
subcircuit_connecivity_map_key?: string;
|
|
311
|
+
time_per_step: number;
|
|
312
|
+
start_time_ms: number;
|
|
313
|
+
end_time_ms: number;
|
|
314
|
+
name?: string;
|
|
315
|
+
}
|
|
316
|
+
type CircuitJsonWithSimulation = AnyCircuitElement | SimulationExperimentElement | SimulationTransientVoltageGraphElement;
|
|
317
|
+
|
|
318
|
+
interface ConvertSimulationGraphParams {
|
|
319
|
+
circuitJson: CircuitJsonWithSimulation[];
|
|
320
|
+
simulation_experiment_id: string;
|
|
321
|
+
simulation_transient_voltage_graph_ids?: string[];
|
|
322
|
+
width?: number;
|
|
323
|
+
height?: number;
|
|
324
|
+
includeVersion?: boolean;
|
|
325
|
+
}
|
|
326
|
+
declare function convertCircuitJsonToSimulationGraphSvg({ circuitJson, simulation_experiment_id, simulation_transient_voltage_graph_ids, width, height, includeVersion, }: ConvertSimulationGraphParams): string;
|
|
327
|
+
|
|
296
328
|
declare function getSoftwareUsedString(circuitJson: AnyCircuitElement[]): string | undefined;
|
|
297
329
|
|
|
298
330
|
declare const CIRCUIT_TO_SVG_VERSION: string;
|
|
@@ -303,4 +335,4 @@ declare const createSvgObjectsForSchComponentPortHovers: ({ component, transform
|
|
|
303
335
|
circuitJson: AnyCircuitElement[];
|
|
304
336
|
}) => INode[];
|
|
305
337
|
|
|
306
|
-
export { type AssemblySvgContext, CIRCUIT_TO_SVG_VERSION, type ColorMap, type ColorOverrides, type PcbColorMap, type PcbColorOverrides, type PcbContext, type PinoutSvgContext, circuitJsonToPcbSvg, circuitJsonToSchematicSvg, convertCircuitJsonToAssemblySvg, convertCircuitJsonToPcbSvg, convertCircuitJsonToPinoutSvg, convertCircuitJsonToSchematicSvg, convertCircuitJsonToSolderPasteMask, createSvgObjectsForSchComponentPortHovers, getSoftwareUsedString };
|
|
338
|
+
export { type AssemblySvgContext, CIRCUIT_TO_SVG_VERSION, type ColorMap, type ColorOverrides, type PcbColorMap, type PcbColorOverrides, type PcbContext, type PinoutSvgContext, circuitJsonToPcbSvg, circuitJsonToSchematicSvg, convertCircuitJsonToAssemblySvg, convertCircuitJsonToPcbSvg, convertCircuitJsonToPinoutSvg, convertCircuitJsonToSchematicSvg, convertCircuitJsonToSimulationGraphSvg, convertCircuitJsonToSolderPasteMask, createSvgObjectsForSchComponentPortHovers, getSoftwareUsedString };
|
package/dist/index.js
CHANGED
|
@@ -1783,7 +1783,7 @@ function getSoftwareUsedString(circuitJson) {
|
|
|
1783
1783
|
var package_default = {
|
|
1784
1784
|
name: "circuit-to-svg",
|
|
1785
1785
|
type: "module",
|
|
1786
|
-
version: "0.0.
|
|
1786
|
+
version: "0.0.207",
|
|
1787
1787
|
description: "Convert Circuit JSON to SVG",
|
|
1788
1788
|
main: "dist/index.js",
|
|
1789
1789
|
files: [
|
|
@@ -8173,6 +8173,535 @@ function createSvgObjectFromPcbBoundary2(transform, minX, minY, maxX, maxY) {
|
|
|
8173
8173
|
}
|
|
8174
8174
|
};
|
|
8175
8175
|
}
|
|
8176
|
+
|
|
8177
|
+
// lib/sim/convert-circuit-json-to-simulation-graph-svg.ts
|
|
8178
|
+
import { stringify as stringify6 } from "svgson";
|
|
8179
|
+
|
|
8180
|
+
// lib/sim/types.ts
|
|
8181
|
+
function isSimulationTransientVoltageGraph(value) {
|
|
8182
|
+
return value?.type === "simulation_transient_voltage_graph";
|
|
8183
|
+
}
|
|
8184
|
+
function isSimulationExperiment(value) {
|
|
8185
|
+
return value?.type === "simulation_experiment";
|
|
8186
|
+
}
|
|
8187
|
+
|
|
8188
|
+
// lib/sim/convert-circuit-json-to-simulation-graph-svg.ts
|
|
8189
|
+
var DEFAULT_WIDTH = 1200;
|
|
8190
|
+
var DEFAULT_HEIGHT = 600;
|
|
8191
|
+
var MARGIN = { top: 64, right: 48, bottom: 80, left: 100 };
|
|
8192
|
+
var FALLBACK_LINE_COLOR = "#1f77b4";
|
|
8193
|
+
function convertCircuitJsonToSimulationGraphSvg({
|
|
8194
|
+
circuitJson,
|
|
8195
|
+
simulation_experiment_id,
|
|
8196
|
+
simulation_transient_voltage_graph_ids,
|
|
8197
|
+
width = DEFAULT_WIDTH,
|
|
8198
|
+
height = DEFAULT_HEIGHT,
|
|
8199
|
+
includeVersion
|
|
8200
|
+
}) {
|
|
8201
|
+
const selectedIds = simulation_transient_voltage_graph_ids ? new Set(simulation_transient_voltage_graph_ids) : null;
|
|
8202
|
+
const experiment = circuitJson.find(
|
|
8203
|
+
(element) => isSimulationExperiment(element) && element.simulation_experiment_id === simulation_experiment_id
|
|
8204
|
+
);
|
|
8205
|
+
const graphs = circuitJson.filter(
|
|
8206
|
+
(element) => isSimulationTransientVoltageGraph(element) && element.simulation_experiment_id === simulation_experiment_id && (!selectedIds || selectedIds.has(element.simulation_transient_voltage_graph_id))
|
|
8207
|
+
);
|
|
8208
|
+
if (graphs.length === 0) {
|
|
8209
|
+
throw new Error(
|
|
8210
|
+
`No simulation_transient_voltage_graph elements found for simulation_experiment_id "${simulation_experiment_id}"`
|
|
8211
|
+
);
|
|
8212
|
+
}
|
|
8213
|
+
const preparedGraphs = prepareSimulationGraphs(graphs);
|
|
8214
|
+
const allPoints = preparedGraphs.flatMap((entry) => entry.points);
|
|
8215
|
+
if (allPoints.length === 0) {
|
|
8216
|
+
throw new Error(
|
|
8217
|
+
`simulation_transient_voltage_graph elements for simulation_experiment_id "${simulation_experiment_id}" do not contain any datapoints`
|
|
8218
|
+
);
|
|
8219
|
+
}
|
|
8220
|
+
const timeAxis = buildAxisInfo(allPoints.map((point) => point.timeMs));
|
|
8221
|
+
const voltageAxis = buildAxisInfo(allPoints.map((point) => point.voltage));
|
|
8222
|
+
const plotWidth = Math.max(1, width - MARGIN.left - MARGIN.right);
|
|
8223
|
+
const plotHeight = Math.max(1, height - MARGIN.top - MARGIN.bottom);
|
|
8224
|
+
const scaleX = createLinearScale(
|
|
8225
|
+
timeAxis.domainMin,
|
|
8226
|
+
timeAxis.domainMax,
|
|
8227
|
+
MARGIN.left,
|
|
8228
|
+
MARGIN.left + plotWidth
|
|
8229
|
+
);
|
|
8230
|
+
const scaleY = createLinearScale(
|
|
8231
|
+
voltageAxis.domainMin,
|
|
8232
|
+
voltageAxis.domainMax,
|
|
8233
|
+
MARGIN.top + plotHeight,
|
|
8234
|
+
MARGIN.top
|
|
8235
|
+
);
|
|
8236
|
+
const clipPathId = createClipPathId(simulation_experiment_id);
|
|
8237
|
+
const softwareUsedString = getSoftwareUsedString(
|
|
8238
|
+
circuitJson
|
|
8239
|
+
);
|
|
8240
|
+
const version = CIRCUIT_TO_SVG_VERSION;
|
|
8241
|
+
const titleNode = createTitleNode(experiment, width);
|
|
8242
|
+
const svgChildren = [
|
|
8243
|
+
createStyleNode(),
|
|
8244
|
+
createBackgroundRect(width, height),
|
|
8245
|
+
createDefsNode(clipPathId, plotWidth, plotHeight),
|
|
8246
|
+
createPlotBackground(plotWidth, plotHeight),
|
|
8247
|
+
createGridLines({
|
|
8248
|
+
timeAxis,
|
|
8249
|
+
voltageAxis,
|
|
8250
|
+
scaleX,
|
|
8251
|
+
scaleY,
|
|
8252
|
+
plotWidth,
|
|
8253
|
+
plotHeight
|
|
8254
|
+
}),
|
|
8255
|
+
createDataGroup(preparedGraphs, clipPathId, scaleX, scaleY),
|
|
8256
|
+
createAxes({
|
|
8257
|
+
timeAxis,
|
|
8258
|
+
voltageAxis,
|
|
8259
|
+
scaleX,
|
|
8260
|
+
scaleY,
|
|
8261
|
+
plotWidth,
|
|
8262
|
+
plotHeight
|
|
8263
|
+
}),
|
|
8264
|
+
createLegend(preparedGraphs),
|
|
8265
|
+
...titleNode ? [titleNode] : []
|
|
8266
|
+
];
|
|
8267
|
+
const svgObject = svgElement(
|
|
8268
|
+
"svg",
|
|
8269
|
+
{
|
|
8270
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
8271
|
+
width: width.toString(),
|
|
8272
|
+
height: height.toString(),
|
|
8273
|
+
viewBox: `0 0 ${formatNumber(width)} ${formatNumber(height)}`,
|
|
8274
|
+
"data-simulation-experiment-id": simulation_experiment_id,
|
|
8275
|
+
...experiment?.name && {
|
|
8276
|
+
"data-simulation-experiment-name": experiment.name
|
|
8277
|
+
},
|
|
8278
|
+
...softwareUsedString && {
|
|
8279
|
+
"data-software-used-string": softwareUsedString
|
|
8280
|
+
},
|
|
8281
|
+
...includeVersion && {
|
|
8282
|
+
"data-circuit-to-svg-version": version
|
|
8283
|
+
}
|
|
8284
|
+
},
|
|
8285
|
+
svgChildren
|
|
8286
|
+
);
|
|
8287
|
+
return stringify6(svgObject);
|
|
8288
|
+
}
|
|
8289
|
+
function prepareSimulationGraphs(graphs) {
|
|
8290
|
+
const palette = Array.isArray(colorMap.palette) ? colorMap.palette : [];
|
|
8291
|
+
return graphs.map((graph, index) => {
|
|
8292
|
+
const points = createGraphPoints(graph);
|
|
8293
|
+
const paletteColor = palette.length > 0 ? palette[index % palette.length] : FALLBACK_LINE_COLOR;
|
|
8294
|
+
const color = paletteColor ?? FALLBACK_LINE_COLOR;
|
|
8295
|
+
const label = graph.name || (graph.schematic_voltage_probe_id ? `Probe ${graph.schematic_voltage_probe_id}` : graph.simulation_transient_voltage_graph_id);
|
|
8296
|
+
return { graph, points, color, label };
|
|
8297
|
+
}).filter((entry) => entry.points.length > 0);
|
|
8298
|
+
}
|
|
8299
|
+
function createGraphPoints(graph) {
|
|
8300
|
+
const timestamps = getTimestamps(graph);
|
|
8301
|
+
const length = Math.min(timestamps.length, graph.voltage_levels.length);
|
|
8302
|
+
const points = [];
|
|
8303
|
+
for (let index = 0; index < length; index++) {
|
|
8304
|
+
const timeMs = Number(timestamps[index] ?? Number.NaN);
|
|
8305
|
+
const voltage = Number(graph.voltage_levels[index] ?? Number.NaN);
|
|
8306
|
+
if (!Number.isFinite(timeMs) || !Number.isFinite(voltage)) continue;
|
|
8307
|
+
points.push({ timeMs, voltage });
|
|
8308
|
+
}
|
|
8309
|
+
return points;
|
|
8310
|
+
}
|
|
8311
|
+
function getTimestamps(graph) {
|
|
8312
|
+
if (Array.isArray(graph.timestamps_ms) && graph.timestamps_ms.length === graph.voltage_levels.length) {
|
|
8313
|
+
return graph.timestamps_ms.map((value) => Number(value));
|
|
8314
|
+
}
|
|
8315
|
+
const count = graph.voltage_levels.length;
|
|
8316
|
+
if (count === 0) return [];
|
|
8317
|
+
const timestamps = [];
|
|
8318
|
+
for (let index = 0; index < count; index++) {
|
|
8319
|
+
timestamps.push(graph.start_time_ms + graph.time_per_step * index);
|
|
8320
|
+
}
|
|
8321
|
+
const lastTimestamp = timestamps.length > 0 ? timestamps[timestamps.length - 1] : void 0;
|
|
8322
|
+
if (lastTimestamp !== void 0 && Number.isFinite(graph.end_time_ms) && Number.isFinite(lastTimestamp) && Math.abs(lastTimestamp - graph.end_time_ms) > graph.time_per_step / 2) {
|
|
8323
|
+
timestamps.push(graph.end_time_ms);
|
|
8324
|
+
}
|
|
8325
|
+
return timestamps;
|
|
8326
|
+
}
|
|
8327
|
+
function buildAxisInfo(values) {
|
|
8328
|
+
if (values.length === 0) {
|
|
8329
|
+
return {
|
|
8330
|
+
domainMin: 0,
|
|
8331
|
+
domainMax: 1,
|
|
8332
|
+
ticks: [0, 1]
|
|
8333
|
+
};
|
|
8334
|
+
}
|
|
8335
|
+
const min = Math.min(...values);
|
|
8336
|
+
const max = Math.max(...values);
|
|
8337
|
+
if (min === max) {
|
|
8338
|
+
const offset = min === 0 ? 1 : Math.abs(min) * 0.1 || 1;
|
|
8339
|
+
return {
|
|
8340
|
+
domainMin: min - offset,
|
|
8341
|
+
domainMax: min + offset,
|
|
8342
|
+
ticks: [min - offset, min, min + offset]
|
|
8343
|
+
};
|
|
8344
|
+
}
|
|
8345
|
+
const ticks = generateTickValues(min, max);
|
|
8346
|
+
const safeTicks = ticks.length > 0 ? ticks : [min, max];
|
|
8347
|
+
const domainMin = safeTicks[0];
|
|
8348
|
+
const domainMax = safeTicks[safeTicks.length - 1];
|
|
8349
|
+
return { domainMin, domainMax, ticks: safeTicks };
|
|
8350
|
+
}
|
|
8351
|
+
function generateTickValues(min, max, desired = 6) {
|
|
8352
|
+
const span = max - min;
|
|
8353
|
+
if (!Number.isFinite(span) || span <= Number.EPSILON) {
|
|
8354
|
+
return [min, max];
|
|
8355
|
+
}
|
|
8356
|
+
const step = niceStep(span / Math.max(1, desired - 1));
|
|
8357
|
+
const niceMin = Math.floor(min / step) * step;
|
|
8358
|
+
const niceMax = Math.ceil(max / step) * step;
|
|
8359
|
+
const values = [];
|
|
8360
|
+
for (let value = niceMin; value <= niceMax + step / 2; value += step) {
|
|
8361
|
+
values.push(Number.parseFloat(value.toPrecision(12)));
|
|
8362
|
+
}
|
|
8363
|
+
return values;
|
|
8364
|
+
}
|
|
8365
|
+
function niceStep(step) {
|
|
8366
|
+
if (!Number.isFinite(step) || step <= 0) return 1;
|
|
8367
|
+
const exponent = Math.floor(Math.log10(step));
|
|
8368
|
+
const fraction = step / Math.pow(10, exponent);
|
|
8369
|
+
let niceFraction;
|
|
8370
|
+
if (fraction <= 1) niceFraction = 1;
|
|
8371
|
+
else if (fraction <= 2) niceFraction = 2;
|
|
8372
|
+
else if (fraction <= 5) niceFraction = 5;
|
|
8373
|
+
else niceFraction = 10;
|
|
8374
|
+
return niceFraction * Math.pow(10, exponent);
|
|
8375
|
+
}
|
|
8376
|
+
function createLinearScale(domainMin, domainMax, rangeMin, rangeMax) {
|
|
8377
|
+
if (!Number.isFinite(domainMin) || !Number.isFinite(domainMax)) {
|
|
8378
|
+
const midpoint = (rangeMin + rangeMax) / 2;
|
|
8379
|
+
return () => midpoint;
|
|
8380
|
+
}
|
|
8381
|
+
const span = domainMax - domainMin;
|
|
8382
|
+
if (Math.abs(span) < Number.EPSILON) {
|
|
8383
|
+
const midpoint = (rangeMin + rangeMax) / 2;
|
|
8384
|
+
return () => midpoint;
|
|
8385
|
+
}
|
|
8386
|
+
return (value) => rangeMin + (value - domainMin) / span * (rangeMax - rangeMin);
|
|
8387
|
+
}
|
|
8388
|
+
function createStyleNode() {
|
|
8389
|
+
const content = `
|
|
8390
|
+
:root { color-scheme: light; }
|
|
8391
|
+
svg { font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif; }
|
|
8392
|
+
.background { fill: ${colorMap.schematic.background}; }
|
|
8393
|
+
.plot-background { fill: #ffffff; }
|
|
8394
|
+
.grid-line { stroke: rgba(0, 0, 0, 0.08); stroke-width: 1; }
|
|
8395
|
+
.axis { stroke: rgba(0, 0, 0, 0.6); stroke-width: 1.5; }
|
|
8396
|
+
.axis-tick { stroke: rgba(0, 0, 0, 0.6); stroke-width: 1; }
|
|
8397
|
+
.axis-label { fill: rgba(0, 0, 0, 0.75); font-size: 12px; }
|
|
8398
|
+
.axis-title { fill: rgba(0, 0, 0, 0.9); font-size: 14px; font-weight: 600; }
|
|
8399
|
+
.legend-label { fill: rgba(0, 0, 0, 0.75); font-size: 13px; }
|
|
8400
|
+
.legend-line { stroke-width: 3; }
|
|
8401
|
+
.simulation-line { fill: none; stroke-width: 2.5; }
|
|
8402
|
+
.simulation-point { stroke-width: 1.5; fill: #ffffff; }
|
|
8403
|
+
.chart-title { fill: rgba(0, 0, 0, 0.85); font-size: 18px; font-weight: 600; }
|
|
8404
|
+
`;
|
|
8405
|
+
return svgElement("style", {}, [textNode(content)]);
|
|
8406
|
+
}
|
|
8407
|
+
function createBackgroundRect(width, height) {
|
|
8408
|
+
return svgElement("rect", {
|
|
8409
|
+
class: "background",
|
|
8410
|
+
x: "0",
|
|
8411
|
+
y: "0",
|
|
8412
|
+
width: formatNumber(width),
|
|
8413
|
+
height: formatNumber(height)
|
|
8414
|
+
});
|
|
8415
|
+
}
|
|
8416
|
+
function createDefsNode(clipPathId, plotWidth, plotHeight) {
|
|
8417
|
+
return svgElement("defs", {}, [
|
|
8418
|
+
svgElement("clipPath", { id: clipPathId }, [
|
|
8419
|
+
svgElement("rect", {
|
|
8420
|
+
x: formatNumber(MARGIN.left),
|
|
8421
|
+
y: formatNumber(MARGIN.top),
|
|
8422
|
+
width: formatNumber(plotWidth),
|
|
8423
|
+
height: formatNumber(plotHeight)
|
|
8424
|
+
})
|
|
8425
|
+
])
|
|
8426
|
+
]);
|
|
8427
|
+
}
|
|
8428
|
+
function createPlotBackground(plotWidth, plotHeight) {
|
|
8429
|
+
return svgElement("rect", {
|
|
8430
|
+
class: "plot-background",
|
|
8431
|
+
x: formatNumber(MARGIN.left),
|
|
8432
|
+
y: formatNumber(MARGIN.top),
|
|
8433
|
+
width: formatNumber(plotWidth),
|
|
8434
|
+
height: formatNumber(plotHeight)
|
|
8435
|
+
});
|
|
8436
|
+
}
|
|
8437
|
+
function createGridLines({
|
|
8438
|
+
timeAxis,
|
|
8439
|
+
voltageAxis,
|
|
8440
|
+
scaleX,
|
|
8441
|
+
scaleY,
|
|
8442
|
+
plotWidth,
|
|
8443
|
+
plotHeight
|
|
8444
|
+
}) {
|
|
8445
|
+
const top = MARGIN.top;
|
|
8446
|
+
const bottom = MARGIN.top + plotHeight;
|
|
8447
|
+
const left = MARGIN.left;
|
|
8448
|
+
const right = MARGIN.left + plotWidth;
|
|
8449
|
+
const children = [];
|
|
8450
|
+
for (const tick of timeAxis.ticks) {
|
|
8451
|
+
const x = formatNumber(scaleX(tick));
|
|
8452
|
+
children.push(
|
|
8453
|
+
svgElement("line", {
|
|
8454
|
+
class: "grid-line grid-line-x",
|
|
8455
|
+
x1: x,
|
|
8456
|
+
y1: formatNumber(top),
|
|
8457
|
+
x2: x,
|
|
8458
|
+
y2: formatNumber(bottom)
|
|
8459
|
+
})
|
|
8460
|
+
);
|
|
8461
|
+
}
|
|
8462
|
+
for (const tick of voltageAxis.ticks) {
|
|
8463
|
+
const y = formatNumber(scaleY(tick));
|
|
8464
|
+
children.push(
|
|
8465
|
+
svgElement("line", {
|
|
8466
|
+
class: "grid-line grid-line-y",
|
|
8467
|
+
x1: formatNumber(left),
|
|
8468
|
+
y1: y,
|
|
8469
|
+
x2: formatNumber(right),
|
|
8470
|
+
y2: y
|
|
8471
|
+
})
|
|
8472
|
+
);
|
|
8473
|
+
}
|
|
8474
|
+
return svgElement("g", { class: "grid" }, children);
|
|
8475
|
+
}
|
|
8476
|
+
function createAxes({
|
|
8477
|
+
timeAxis,
|
|
8478
|
+
voltageAxis,
|
|
8479
|
+
scaleX,
|
|
8480
|
+
scaleY,
|
|
8481
|
+
plotWidth,
|
|
8482
|
+
plotHeight
|
|
8483
|
+
}) {
|
|
8484
|
+
const bottom = MARGIN.top + plotHeight;
|
|
8485
|
+
const left = MARGIN.left;
|
|
8486
|
+
const right = MARGIN.left + plotWidth;
|
|
8487
|
+
const children = [
|
|
8488
|
+
svgElement("line", {
|
|
8489
|
+
class: "axis axis-x",
|
|
8490
|
+
x1: formatNumber(left),
|
|
8491
|
+
y1: formatNumber(bottom),
|
|
8492
|
+
x2: formatNumber(right),
|
|
8493
|
+
y2: formatNumber(bottom)
|
|
8494
|
+
}),
|
|
8495
|
+
svgElement("line", {
|
|
8496
|
+
class: "axis axis-y",
|
|
8497
|
+
x1: formatNumber(left),
|
|
8498
|
+
y1: formatNumber(MARGIN.top),
|
|
8499
|
+
x2: formatNumber(left),
|
|
8500
|
+
y2: formatNumber(bottom)
|
|
8501
|
+
})
|
|
8502
|
+
];
|
|
8503
|
+
for (const tick of timeAxis.ticks) {
|
|
8504
|
+
const x = formatNumber(scaleX(tick));
|
|
8505
|
+
children.push(
|
|
8506
|
+
svgElement("line", {
|
|
8507
|
+
class: "axis-tick axis-tick-x",
|
|
8508
|
+
x1: x,
|
|
8509
|
+
y1: formatNumber(bottom),
|
|
8510
|
+
x2: x,
|
|
8511
|
+
y2: formatNumber(bottom + 6)
|
|
8512
|
+
})
|
|
8513
|
+
);
|
|
8514
|
+
children.push(
|
|
8515
|
+
svgElement(
|
|
8516
|
+
"text",
|
|
8517
|
+
{
|
|
8518
|
+
class: "axis-label axis-label-x",
|
|
8519
|
+
x,
|
|
8520
|
+
y: formatNumber(bottom + 22),
|
|
8521
|
+
"text-anchor": "middle"
|
|
8522
|
+
},
|
|
8523
|
+
[textNode(formatTickLabel(tick, timeAxis.ticks))]
|
|
8524
|
+
)
|
|
8525
|
+
);
|
|
8526
|
+
}
|
|
8527
|
+
for (const tick of voltageAxis.ticks) {
|
|
8528
|
+
const y = formatNumber(scaleY(tick));
|
|
8529
|
+
children.push(
|
|
8530
|
+
svgElement("line", {
|
|
8531
|
+
class: "axis-tick axis-tick-y",
|
|
8532
|
+
x1: formatNumber(left - 6),
|
|
8533
|
+
y1: y,
|
|
8534
|
+
x2: formatNumber(left),
|
|
8535
|
+
y2: y
|
|
8536
|
+
})
|
|
8537
|
+
);
|
|
8538
|
+
children.push(
|
|
8539
|
+
svgElement(
|
|
8540
|
+
"text",
|
|
8541
|
+
{
|
|
8542
|
+
class: "axis-label axis-label-y",
|
|
8543
|
+
x: formatNumber(left - 10),
|
|
8544
|
+
y,
|
|
8545
|
+
"text-anchor": "end",
|
|
8546
|
+
"dominant-baseline": "middle"
|
|
8547
|
+
},
|
|
8548
|
+
[textNode(formatTickLabel(tick, voltageAxis.ticks))]
|
|
8549
|
+
)
|
|
8550
|
+
);
|
|
8551
|
+
}
|
|
8552
|
+
children.push(
|
|
8553
|
+
svgElement(
|
|
8554
|
+
"text",
|
|
8555
|
+
{
|
|
8556
|
+
class: "axis-title axis-title-x",
|
|
8557
|
+
x: formatNumber(left + plotWidth / 2),
|
|
8558
|
+
y: formatNumber(bottom + 48),
|
|
8559
|
+
"text-anchor": "middle"
|
|
8560
|
+
},
|
|
8561
|
+
[textNode("Time (ms)")]
|
|
8562
|
+
),
|
|
8563
|
+
svgElement(
|
|
8564
|
+
"text",
|
|
8565
|
+
{
|
|
8566
|
+
class: "axis-title axis-title-y",
|
|
8567
|
+
x: formatNumber(left - 64),
|
|
8568
|
+
y: formatNumber(MARGIN.top + plotHeight / 2),
|
|
8569
|
+
transform: `rotate(-90 ${formatNumber(left - 64)} ${formatNumber(
|
|
8570
|
+
MARGIN.top + plotHeight / 2
|
|
8571
|
+
)})`,
|
|
8572
|
+
"text-anchor": "middle"
|
|
8573
|
+
},
|
|
8574
|
+
[textNode("Voltage (V)")]
|
|
8575
|
+
)
|
|
8576
|
+
);
|
|
8577
|
+
return svgElement("g", { class: "axes" }, children);
|
|
8578
|
+
}
|
|
8579
|
+
function createLegend(graphs) {
|
|
8580
|
+
const children = graphs.map((entry, index) => {
|
|
8581
|
+
const y = MARGIN.top - 28 + index * 20;
|
|
8582
|
+
return svgElement(
|
|
8583
|
+
"g",
|
|
8584
|
+
{
|
|
8585
|
+
class: "legend-item",
|
|
8586
|
+
transform: `translate(${formatNumber(MARGIN.left)} ${formatNumber(y)})`
|
|
8587
|
+
},
|
|
8588
|
+
[
|
|
8589
|
+
svgElement("line", {
|
|
8590
|
+
class: "legend-line",
|
|
8591
|
+
x1: "0",
|
|
8592
|
+
y1: "0",
|
|
8593
|
+
x2: "24",
|
|
8594
|
+
y2: "0",
|
|
8595
|
+
stroke: entry.color
|
|
8596
|
+
}),
|
|
8597
|
+
svgElement(
|
|
8598
|
+
"text",
|
|
8599
|
+
{
|
|
8600
|
+
class: "legend-label",
|
|
8601
|
+
x: "32",
|
|
8602
|
+
y: "0",
|
|
8603
|
+
"dominant-baseline": "middle"
|
|
8604
|
+
},
|
|
8605
|
+
[textNode(entry.label)]
|
|
8606
|
+
)
|
|
8607
|
+
]
|
|
8608
|
+
);
|
|
8609
|
+
});
|
|
8610
|
+
return svgElement("g", { class: "legend" }, children);
|
|
8611
|
+
}
|
|
8612
|
+
function createDataGroup(graphs, clipPathId, scaleX, scaleY) {
|
|
8613
|
+
const elements = [];
|
|
8614
|
+
for (const entry of graphs) {
|
|
8615
|
+
if (entry.points.length === 0) continue;
|
|
8616
|
+
const commands = [];
|
|
8617
|
+
entry.points.forEach((point, index) => {
|
|
8618
|
+
const x = formatNumber(scaleX(point.timeMs));
|
|
8619
|
+
const y = formatNumber(scaleY(point.voltage));
|
|
8620
|
+
commands.push(`${index === 0 ? "M" : "L"} ${x} ${y}`);
|
|
8621
|
+
});
|
|
8622
|
+
const pathAttributes = {
|
|
8623
|
+
class: "simulation-line",
|
|
8624
|
+
d: commands.join(" "),
|
|
8625
|
+
stroke: entry.color,
|
|
8626
|
+
"clip-path": `url(#${clipPathId})`,
|
|
8627
|
+
"data-simulation-transient-voltage-graph-id": entry.graph.simulation_transient_voltage_graph_id
|
|
8628
|
+
};
|
|
8629
|
+
if (entry.graph.schematic_voltage_probe_id) {
|
|
8630
|
+
pathAttributes["data-schematic-voltage-probe-id"] = entry.graph.schematic_voltage_probe_id;
|
|
8631
|
+
}
|
|
8632
|
+
if (entry.graph.subcircuit_connecivity_map_key) {
|
|
8633
|
+
pathAttributes["data-subcircuit-connectivity-map-key"] = entry.graph.subcircuit_connecivity_map_key;
|
|
8634
|
+
}
|
|
8635
|
+
elements.push(svgElement("path", pathAttributes));
|
|
8636
|
+
entry.points.forEach((point) => {
|
|
8637
|
+
const cx = formatNumber(scaleX(point.timeMs));
|
|
8638
|
+
const cy = formatNumber(scaleY(point.voltage));
|
|
8639
|
+
elements.push(
|
|
8640
|
+
svgElement("circle", {
|
|
8641
|
+
class: "simulation-point",
|
|
8642
|
+
cx,
|
|
8643
|
+
cy,
|
|
8644
|
+
r: "3.5",
|
|
8645
|
+
stroke: entry.color,
|
|
8646
|
+
fill: "#ffffff",
|
|
8647
|
+
"clip-path": `url(#${clipPathId})`
|
|
8648
|
+
})
|
|
8649
|
+
);
|
|
8650
|
+
});
|
|
8651
|
+
}
|
|
8652
|
+
return svgElement("g", { class: "data-series" }, elements);
|
|
8653
|
+
}
|
|
8654
|
+
function createTitleNode(experiment, width) {
|
|
8655
|
+
if (!experiment?.name) return null;
|
|
8656
|
+
return svgElement(
|
|
8657
|
+
"text",
|
|
8658
|
+
{
|
|
8659
|
+
class: "chart-title",
|
|
8660
|
+
x: formatNumber(width / 2),
|
|
8661
|
+
y: formatNumber(MARGIN.top - 40),
|
|
8662
|
+
"text-anchor": "middle"
|
|
8663
|
+
},
|
|
8664
|
+
[textNode(experiment.name)]
|
|
8665
|
+
);
|
|
8666
|
+
}
|
|
8667
|
+
function createClipPathId(simulationExperimentId) {
|
|
8668
|
+
const sanitized = simulationExperimentId.replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
8669
|
+
return `simulation-graph-${sanitized}`;
|
|
8670
|
+
}
|
|
8671
|
+
function formatNumber(value) {
|
|
8672
|
+
if (!Number.isFinite(value)) return "0";
|
|
8673
|
+
const rounded = Number.parseFloat(value.toFixed(6));
|
|
8674
|
+
if (Number.isInteger(rounded)) return rounded.toString();
|
|
8675
|
+
return rounded.toString();
|
|
8676
|
+
}
|
|
8677
|
+
function formatTickLabel(value, ticks) {
|
|
8678
|
+
if (ticks.length <= 1) return formatNumber(value);
|
|
8679
|
+
const span = ticks[ticks.length - 1] - ticks[0];
|
|
8680
|
+
if (!Number.isFinite(span) || span === 0) return formatNumber(value);
|
|
8681
|
+
const precision = span >= 100 ? 0 : span >= 10 ? 1 : span >= 1 ? 2 : 3;
|
|
8682
|
+
const factor = Math.pow(10, precision);
|
|
8683
|
+
const rounded = Math.round(value * factor) / factor;
|
|
8684
|
+
const fixed = rounded.toFixed(precision);
|
|
8685
|
+
return fixed.replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
8686
|
+
}
|
|
8687
|
+
function svgElement(name, attributes, children = []) {
|
|
8688
|
+
return {
|
|
8689
|
+
name,
|
|
8690
|
+
type: "element",
|
|
8691
|
+
value: "",
|
|
8692
|
+
attributes,
|
|
8693
|
+
children
|
|
8694
|
+
};
|
|
8695
|
+
}
|
|
8696
|
+
function textNode(value) {
|
|
8697
|
+
return {
|
|
8698
|
+
name: "",
|
|
8699
|
+
type: "text",
|
|
8700
|
+
value,
|
|
8701
|
+
attributes: {},
|
|
8702
|
+
children: []
|
|
8703
|
+
};
|
|
8704
|
+
}
|
|
8176
8705
|
export {
|
|
8177
8706
|
CIRCUIT_TO_SVG_VERSION,
|
|
8178
8707
|
circuitJsonToPcbSvg,
|
|
@@ -8181,6 +8710,7 @@ export {
|
|
|
8181
8710
|
convertCircuitJsonToPcbSvg,
|
|
8182
8711
|
convertCircuitJsonToPinoutSvg,
|
|
8183
8712
|
convertCircuitJsonToSchematicSvg,
|
|
8713
|
+
convertCircuitJsonToSimulationGraphSvg,
|
|
8184
8714
|
convertCircuitJsonToSolderPasteMask,
|
|
8185
8715
|
createSvgObjectsForSchComponentPortHovers,
|
|
8186
8716
|
getSoftwareUsedString
|