smithkit 0.3.0 → 0.5.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.
Files changed (45) hide show
  1. package/README.md +324 -37
  2. package/dist/lib/OptionsValidation.d.ts +16 -0
  3. package/dist/lib/OptionsValidation.js +172 -0
  4. package/dist/lib/Smith.d.ts +20 -5
  5. package/dist/lib/Smith.js +114 -15
  6. package/dist/lib/appearance/Theme.js +5 -0
  7. package/dist/lib/grid/CircleLayerControl.d.ts +1 -1
  8. package/dist/lib/grid/CircleLayerControl.js +10 -8
  9. package/dist/lib/grid/ConstQCircles.d.ts +1 -0
  10. package/dist/lib/grid/ConstQCircles.js +5 -0
  11. package/dist/lib/grid/ConstSwrCircles.d.ts +1 -0
  12. package/dist/lib/grid/ConstSwrCircles.js +5 -0
  13. package/dist/lib/grid/GridLayerControl.js +5 -4
  14. package/dist/lib/grid/LayerControl.d.ts +1 -2
  15. package/dist/lib/grid/LayerControl.js +1 -10
  16. package/dist/lib/index.d.ts +3 -2
  17. package/dist/lib/io/Touchstone.d.ts +13 -7
  18. package/dist/lib/io/Touchstone.js +146 -46
  19. package/dist/lib/io/types.d.ts +17 -0
  20. package/dist/lib/io/types.js +1 -0
  21. package/dist/lib/layers.d.ts +7 -1
  22. package/dist/lib/measurements.d.ts +2 -0
  23. package/dist/lib/options.d.ts +58 -0
  24. package/dist/lib/options.js +1 -0
  25. package/dist/lib/rendering/SvgChartRenderer.d.ts +6 -3
  26. package/dist/lib/rendering/SvgChartRenderer.js +58 -11
  27. package/dist/lib/rf/RfCalculations.d.ts +3 -1
  28. package/dist/lib/rf/RfCalculations.js +23 -6
  29. package/dist/lib/samples.d.ts +5 -1
  30. package/dist/lib/scales/SmithPeripheralScales.d.ts +5 -0
  31. package/dist/lib/scales/SmithPeripheralScales.js +20 -1
  32. package/dist/lib/svg/SvgExporter.d.ts +1 -1
  33. package/dist/lib/svg/SvgExporter.js +4 -3
  34. package/dist/lib/svg/export.d.ts +3 -1
  35. package/dist/lib/traces/SmithData.d.ts +10 -4
  36. package/dist/lib/traces/SmithData.js +14 -4
  37. package/dist/lib/traces/TraceBuffer.d.ts +19 -0
  38. package/dist/lib/traces/TraceBuffer.js +176 -0
  39. package/dist/lib/traces/TraceLineDetail.d.ts +8 -0
  40. package/dist/lib/traces/TraceLineDetail.js +52 -0
  41. package/dist/lib/traces/TraceModel.d.ts +6 -8
  42. package/dist/lib/traces/TraceModel.js +24 -49
  43. package/dist/lib/traces/TraceRenderer.d.ts +13 -3
  44. package/dist/lib/traces/TraceRenderer.js +83 -23
  45. package/package.json +4 -2
@@ -1,5 +1,7 @@
1
- import { line } from 'd3';
1
+ import { path, pathRound } from 'd3';
2
2
  import { SmithGroup } from '../svg/SmithGroup.js';
3
+ import { TraceLineDetail } from './TraceLineDetail.js';
4
+ import { TraceBuffer } from './TraceBuffer.js';
3
5
  /** Draws a trace; sample validation and marker selection belong to TraceModel. */
4
6
  export class TraceRenderer {
5
7
  data;
@@ -10,6 +12,8 @@ export class TraceRenderer {
10
12
  viewportScale = 1;
11
13
  visible = true;
12
14
  group;
15
+ lineDetail;
16
+ lineIndices;
13
17
  constructor(data, color, transform, container, scaler, style = {}) {
14
18
  this.data = data;
15
19
  this.color = color;
@@ -19,6 +23,7 @@ export class TraceRenderer {
19
23
  mode: style.mode ?? 'points',
20
24
  lineWidth: style.lineWidth ?? 2,
21
25
  pointRadius: style.pointRadius ?? 2,
26
+ lineTolerancePx: style.lineTolerancePx ?? 0,
22
27
  };
23
28
  this.group = this.drawTrace(data).attr('pointer-events', 'none');
24
29
  container.append(this.group);
@@ -32,12 +37,10 @@ export class TraceRenderer {
32
37
  group.attr('data-role', 'samples');
33
38
  group.attr('data-mode', this.style.mode);
34
39
  if (this.style.mode !== 'points') {
35
- const path = line()
36
- .x((sample) => this.scaler.x(sample.reflectionCoefficient[0]))
37
- .y((sample) => this.scaler.y(sample.reflectionCoefficient[1]));
40
+ this.lineIndices = this.selectLineIndices();
38
41
  group.Element.append('path')
39
42
  .attr('class', 'trace-line')
40
- .attr('d', path(data))
43
+ .attr('d', this.linePath(this.lineIndices))
41
44
  .attr('fill', 'none')
42
45
  .attr('stroke', this.color)
43
46
  .attr('stroke-width', this.style.lineWidth)
@@ -50,32 +53,83 @@ export class TraceRenderer {
50
53
  }
51
54
  return group;
52
55
  }
56
+ selectLineIndices() {
57
+ if (this.style.lineTolerancePx === 0) {
58
+ return;
59
+ }
60
+ this.lineDetail ??= new TraceLineDetail(this.data);
61
+ const unitsToPixels = Math.max(Math.abs(this.scaler.x(1) - this.scaler.x(0)), Math.abs(this.scaler.y(1) - this.scaler.y(0))) *
62
+ this.transform.k *
63
+ this.viewportScale;
64
+ return this.lineDetail.select(this.style.lineTolerancePx / unitsToPixels);
65
+ }
66
+ linePath(indices) {
67
+ // Simplified paths retain numeric precision even at high zoom. Full paths keep
68
+ // the existing SVG serialization; the tolerance measures additional simplification.
69
+ const line = indices ? path() : pathRound(3);
70
+ const length = indices?.length ?? this.data.length;
71
+ for (let position = 0; position < length; position++) {
72
+ const index = indices ? indices[position] : position;
73
+ const x = this.scaler.x(this.data.real(index));
74
+ const y = this.scaler.y(this.data.imaginary(index));
75
+ if (position === 0) {
76
+ line.moveTo(x, y);
77
+ }
78
+ else {
79
+ line.lineTo(x, y);
80
+ }
81
+ }
82
+ if (length === 1) {
83
+ line.closePath();
84
+ }
85
+ return line.toString();
86
+ }
87
+ updateLine() {
88
+ if (this.style.mode === 'points') {
89
+ return;
90
+ }
91
+ const indices = this.selectLineIndices();
92
+ if (indices !== this.lineIndices) {
93
+ this.lineIndices = indices;
94
+ this.group.Element.select('.trace-line').attr('d', this.linePath(indices));
95
+ }
96
+ }
97
+ /** Replace geometry only in the export copy, never in the live SVG. */
98
+ fullExportLine() {
99
+ const node = this.group.Element.select('.trace-line').node();
100
+ if (node && this.lineIndices) {
101
+ return { node, path: this.linePath() };
102
+ }
103
+ return;
104
+ }
53
105
  renderPoints(group, data) {
54
- let visible = data;
55
- if (data.length > 5000) {
56
- const cells = new Set();
57
- const cellSize = Math.max(0.5, this.style.pointRadius) / this.viewportScale;
58
- const extent = this.scaler.x(1);
59
- const margin = this.style.pointRadius / this.viewportScale;
60
- visible = data.filter((sample) => {
61
- const x = this.scaler.x(sample.reflectionCoefficient[0]) * this.transform.k + this.transform.x;
62
- const y = this.scaler.y(sample.reflectionCoefficient[1]) * this.transform.k + this.transform.y;
106
+ const visible = [];
107
+ const cells = new Set();
108
+ const cellSize = Math.max(0.5, this.style.pointRadius) / this.viewportScale;
109
+ const extent = this.scaler.x(1);
110
+ const margin = this.style.pointRadius / this.viewportScale;
111
+ const firstCell = Math.floor(-margin / cellSize);
112
+ const columns = Math.floor((extent + margin) / cellSize) - firstCell + 1;
113
+ for (let i = 0; i < data.length; i++) {
114
+ if (data.length > 5000) {
115
+ const x = this.scaler.x(data.real(i)) * this.transform.k + this.transform.x;
116
+ const y = this.scaler.y(data.imaginary(i)) * this.transform.k + this.transform.y;
63
117
  if (x < -margin || y < -margin || x > extent + margin || y > extent + margin) {
64
- return false;
118
+ continue;
65
119
  }
66
- const cell = `${Math.floor(x / cellSize)},${Math.floor(y / cellSize)}`;
120
+ const cell = (Math.floor(y / cellSize) - firstCell) * columns + Math.floor(x / cellSize) - firstCell;
67
121
  if (cells.has(cell)) {
68
- return false;
122
+ continue;
69
123
  }
70
124
  cells.add(cell);
71
- return true;
72
- });
125
+ }
126
+ visible.push(i);
73
127
  }
74
128
  group.Element.selectAll('circle')
75
129
  .data(visible)
76
130
  .join('circle')
77
- .attr('cx', (sample) => this.scaler.x(sample.reflectionCoefficient[0]))
78
- .attr('cy', (sample) => this.scaler.y(sample.reflectionCoefficient[1]))
131
+ .attr('cx', (index) => this.scaler.x(data.real(index)))
132
+ .attr('cy', (index) => this.scaler.y(data.imaginary(index)))
79
133
  .attr('r', this.style.pointRadius / (this.transform.k * this.viewportScale));
80
134
  }
81
135
  setViewportScale(scale) {
@@ -90,6 +144,7 @@ export class TraceRenderer {
90
144
  this.zoomDataPoints();
91
145
  }
92
146
  zoomDataPoints() {
147
+ this.updateLine();
93
148
  const k = this.transform.k;
94
149
  if (this.style.mode === 'line') {
95
150
  return;
@@ -114,10 +169,12 @@ export class TraceRenderer {
114
169
  mode: options.mode ?? this.style.mode,
115
170
  lineWidth: options.lineWidth ?? this.style.lineWidth,
116
171
  pointRadius: options.pointRadius ?? this.style.pointRadius,
172
+ lineTolerancePx: options.lineTolerancePx ?? this.style.lineTolerancePx,
117
173
  };
118
174
  if (next.mode === this.style.mode &&
119
175
  next.lineWidth === this.style.lineWidth &&
120
- next.pointRadius === this.style.pointRadius) {
176
+ next.pointRadius === this.style.pointRadius &&
177
+ next.lineTolerancePx === this.style.lineTolerancePx) {
121
178
  return;
122
179
  }
123
180
  const modeChanged = next.mode !== this.style.mode;
@@ -143,10 +200,13 @@ export class TraceRenderer {
143
200
  }
144
201
  update(data) {
145
202
  this.data = data;
203
+ this.lineDetail = undefined;
146
204
  this.redraw();
147
205
  }
148
206
  destroy() {
149
- this.data = [];
207
+ this.lineDetail = undefined;
208
+ this.lineIndices = undefined;
209
+ this.data = TraceBuffer.empty();
150
210
  this.group.Element.remove();
151
211
  }
152
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smithkit",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Framework-independent, interactive Smith chart library for JavaScript and TypeScript",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,7 +21,9 @@
21
21
  "format:check": "prettier . --check",
22
22
  "check:package": "node scripts/check-package.mjs",
23
23
  "check:example": "node scripts/check-example.mjs",
24
- "prepare:release": "node scripts/prepare-release.mjs"
24
+ "prepare:release": "node scripts/prepare-release.mjs",
25
+ "benchmark:traces": "node scripts/benchmark-traces.mjs",
26
+ "benchmark:workloads": "node scripts/benchmark-workloads.mjs"
25
27
  },
26
28
  "dependencies": {
27
29
  "@types/d3": "^7.4.3",