diff --git a/Examples/src/components/AppRouter/examplePaths.ts b/Examples/src/components/AppRouter/examplePaths.ts index 52ef139ff..95f7aa0d1 100644 --- a/Examples/src/components/AppRouter/examplePaths.ts +++ b/Examples/src/components/AppRouter/examplePaths.ts @@ -14,7 +14,6 @@ export default [ "../Examples/Charts2D/AxisLabelCustomization/MultiLineLabels", "../Examples/Charts2D/AxisLabelCustomization/RotatedLabels", "../Examples/Charts2D/BasicChartTypes/BandSeriesChart", - "../Examples/Charts2D/v4Charts/HeatmapOverMap", "../Examples/Charts2D/BasicChartTypes/BubbleChart", "../Examples/Charts2D/BasicChartTypes/CandlestickChart", "../Examples/Charts2D/BasicChartTypes/ColumnChart", @@ -60,13 +59,14 @@ export default [ "../Examples/Charts2D/Filters/PercentageChange", "../Examples/Charts2D/Filters/TrendMARatio", "../Examples/Charts2D/Legends/ChartLegendsAPI", - "../Examples/Charts2D/ModifyAxisBehavior/DiscontinuousDateAxisComparison", "../Examples/Charts2D/ModifyAxisBehavior/BaseValueAxes", "../Examples/Charts2D/ModifyAxisBehavior/CentralAxes", + "../Examples/Charts2D/ModifyAxisBehavior/DiscontinuousDateAxisComparison", "../Examples/Charts2D/ModifyAxisBehavior/DrawBehindAxes", "../Examples/Charts2D/ModifyAxisBehavior/LogarithmicAxis", "../Examples/Charts2D/ModifyAxisBehavior/MultipleXAxes", "../Examples/Charts2D/ModifyAxisBehavior/SecondaryYAxes", + "../Examples/Charts2D/ModifyAxisBehavior/SmithChart", "../Examples/Charts2D/ModifyAxisBehavior/StaticAxis", "../Examples/Charts2D/ModifyAxisBehavior/VerticalCharts", "../Examples/Charts2D/ModifyAxisBehavior/VerticallyStackedAxes", @@ -128,6 +128,7 @@ export default [ "../Examples/Charts2D/v4Charts/AnimatedColumns", "../Examples/Charts2D/v4Charts/BoxPlotChart", "../Examples/Charts2D/v4Charts/GanttChart", + "../Examples/Charts2D/v4Charts/HeatmapOverMap", "../Examples/Charts2D/v4Charts/HeatmapRectangle", "../Examples/Charts2D/v4Charts/HistogramChart", "../Examples/Charts2D/v4Charts/LinearGauges", @@ -168,5 +169,5 @@ export default [ "../Examples/FeaturedApps/ShowCases/OilAndGasDashboard", "../Examples/FeaturedApps/ShowCases/PopulationPyramid", "../Examples/FeaturedApps/ShowCases/ServerTrafficDashboard", - "../Examples/FeaturedApps/ShowCases/WebsocketBigData", + "../Examples/FeaturedApps/ShowCases/WebsocketBigData" ]; diff --git a/Examples/src/components/AppRouter/examples.ts b/Examples/src/components/AppRouter/examples.ts index f4c6daa11..312a77e9a 100644 --- a/Examples/src/components/AppRouter/examples.ts +++ b/Examples/src/components/AppRouter/examples.ts @@ -193,6 +193,7 @@ export const MENU_ITEMS_2D: TMenuItem[] = [ EXAMPLES_PAGES.chart2D_modifyAxisBehavior_SecondaryYAxes, EXAMPLES_PAGES.chart2D_modifyAxisBehavior_VerticalCharts, EXAMPLES_PAGES.chart2D_modifyAxisBehavior_CentralAxes, + EXAMPLES_PAGES.chart2D_modifyAxisBehavior_SmithChart, EXAMPLES_PAGES.chart2D_modifyAxisBehavior_StaticAxis, EXAMPLES_PAGES.chart2D_modifyAxisBehavior_VerticallyStackedAxes, EXAMPLES_PAGES.chart2D_modifyAxisBehavior_LogarithmicAxis, diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/README.md b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/README.md new file mode 100644 index 000000000..c02b6aea5 --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/README.md @@ -0,0 +1,68 @@ +# Central Axes (Central Axes Layout Example) + +## Overview + +This example demonstrates how to customize the axes layout in SciChart.js by placing the axes in the center of the chart. The example creates a chart with central axes that cross at the data value (0,0) (oscilloscope style) and includes implementations for React, Vanilla JavaScript/TypeScript, and Angular. + +## Technologies Used + +- SciChart.js (core charting library) +- SciChart Angular Component +- SciChart React Component +- TypeScript and JavaScript + +## Code Explanation + +The heart of the example is the `drawExample` function (provided in both JavaScript and TypeScript versions). This function creates a SciChart Surface and configures a custom layout manager (CentralAxesLayoutManager) to display the x- and y-axes centrally. Key points include: + +- **Chart Creation:** A SciChart Surface is created with a specified theme. +- **Custom Axis Layout:** The `CentralAxesLayoutManager` is instantiated with options to position the horizontal and vertical axes at data value 0, ensuring that the axes pan with the chart. +- **Axis Configuration:** The x-axis and y-axis are configured as inner axes by setting `isInnerAxis` to true. This positions them in the center of the chart. Additional styling (like label color and axis borders) is applied. +- **Data Series and Annotation:** A fast line renderable series is added that draws a butterfly curve generated by a helper function. Furthermore, a text annotation is added to describe the chart’s functionality. +- **Interaction Modifiers:** Standard chart interaction modifiers (zoom pan, mouse wheel zoom, and zoom extents) are included to allow users to interact with the chart. + +The example also includes framework-specific files: + +- **Angular:** The `angular.ts` file sets up a standalone Angular component that uses the SciChart Angular component and binds its `drawExample` function. +- **React:** The `index.tsx` file presents a React component which uses the SciChart React wrapper to initialize the chart with the `drawExample` function. +- **Vanilla:** The `vanilla.js` and `vanilla.ts` files demonstrate how to create and dispose of the chart in a plain JavaScript/TypeScript environment. + +## Customization + +Key configuration options in this example include: + +- **Axis Positioning:** You can modify the options passed to the `CentralAxesLayoutManager` to change the axis alignment. By default, the axes are set to cross at (0,0) using the data value coordinate mode, but commented code shows how to use relative coordinate modes as well. +- **Animation:** The fast line renderable series uses a fade animation with a duration of 500ms, which can be adjusted as needed. +- **Styling:** Colors for axis labels, axis borders, and the renderable series stroke are set based on a theme. These can be customized to match your desired appearance. + +## Running the Example + +To run any example from the SciChart.JS.Examples repository, follow these steps: + +1. **Clone the Repository**: Download the entire repository to your local machine using Git: + + ```bash + git clone https://github.com/ABTSoftware/SciChart.JS.Examples.git + ``` + +2. **Navigate to the Examples Directory**: Change into the `Examples` folder: + + ```bash + cd SciChart.JS.Examples/Examples + ``` + +3. **Install Dependencies**: Install the necessary packages using npm: + + ```bash + npm install + ``` + +4. **Run the Development Server**: Start the development server to view and interact with the examples: + + ```bash + npm run dev + ``` + + This will launch the demo application, allowing you to explore various examples, including the one in question. + +For more detailed instructions, refer to the [SciChart.JS.Examples README](https://github.com/ABTSoftware/SciChart.JS.Examples/blob/master/README.md). diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/SMITH_CHART_REDESIGN_SPEC.md b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/SMITH_CHART_REDESIGN_SPEC.md new file mode 100644 index 000000000..2e32819b1 --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/SMITH_CHART_REDESIGN_SPEC.md @@ -0,0 +1,313 @@ +# Smith Chart Redesign Spec + +## Goal + +Replace the current `drawExample.ts` implementation (which draws ~40 circles/arcs as 200-point `XyDataSeries` polylines) with a new architecture using paired custom axis classes that draw true hardware-accelerated arcs via `renderContext.drawArcs`. This is SciChart-idiomatic, eliminates ~40 series objects and ~8000 data points, and produces geometrically perfect circles regardless of zoom level. + +--- + +## Architecture + +Split into two files: + +### `smithChartAxes.ts` (new) + +Exports: + +- `SmithResistanceTickProvider` — custom tick provider for R values +- `SmithReactanceTickProvider` — custom tick provider for X values +- `SmithChartResistanceAxis extends NumericAxis` — X axis; draws constant-R circles + unit circle + real axis +- `SmithChartReactanceAxis extends NumericAxis` — Y axis; draws constant-X arcs (± mirrored) + +### `drawExample.ts` (rewrite) + +- Wire `SmithChartResistanceAxis` as xAxis, `SmithChartReactanceAxis` as yAxis +- Replace 3× `XyDataSeries` highlight series with 3× `ArcAnnotation` +- Keep `SmithChartDragModifier` (unchanged logic) +- Remove `preRender` aspect-ratio hack (no longer needed — `getArcParams` takes `aspectRatio`) +- Keep all `TextAnnotation` readouts + +--- + +## Smith Chart Mathematics + +### Coordinate system + +The Γ plane is a standard Cartesian plane (Γ_real on X axis, Γ_imag on Y axis). The unit circle is the boundary |Γ| = 1. Data lives within the unit disk. + +### Constant-R circles (drawn by `SmithChartResistanceAxis`) + +For normalised resistance `r`: + +- Centre: `(r/(1+r), 0)` in data coordinates +- Radius: `1/(1+r)` in data coordinates +- All circles pass through `(1, 0)` and are tangent to the right edge + +### Constant-X arcs (drawn by `SmithChartReactanceAxis`) + +For normalised reactance `x` (both `+x` and `-x` are mirrored): + +- Centre: `(1, 1/x)` in data coordinates (or `(1, -1/x)` for negative) +- Radius: `1/|x|` in data coordinates +- Arc runs from `(1, 0)` (right edge of chart) to unit-circle intersection: + - `xInt = (x²-1)/(1+x²)`, `yInt = ±2x/(1+x²)` + +--- + +## The `drawGridLines` Override Pattern + +Both axes follow the pattern from `PolarAxisBase.ts`: + +```ts +protected drawGridLines(renderContext, tickCoords, pen, isMajor) { + // 1. Get the sibling axis via this.parentSurface + // 2. Get the viewport pixel dimensions from coordinate calculators + // 3. Get raw data values (R or X) from this.getTicksWithCoords() + // (ignore the tickCoords parameter — it gives pixel positions but we need data values) + // 4. For each data value, compute circle/arc centre and radius in data coords + // 5. Convert to pixel coords using xCalc.getCoordinate() / yCalc.getCoordinate() + // 6. Compute aspectRatio = xCalc.getCoordWidth(1) / yCalc.getCoordWidth(1) + // 7. Call getArcParams(...) to create SCRTArcVertex + // 8. Push into vecArcs, call renderContext.drawArcs(...) +} +``` + +### Axis cross-wiring + +Like `PolarAxisBase`, in `measure()` each axis locates its sibling: + +```ts +// In SmithChartResistanceAxis (X axis): +const yAxis = this.parentSurface.yAxes.get(0) as SmithChartReactanceAxis; +this.sibling = yAxis; + +// In SmithChartReactanceAxis (Y axis): +const xAxis = this.parentSurface.xAxes.get(0) as SmithChartResistanceAxis; +this.sibling = xAxis; +``` + +Both axes need to know the sibling exists to draw grid lines that span the full viewport. + +--- + +## Key SciChart APIs + +### `getArcParams` (from `NativeObject.ts`) + +```ts +getArcParams( + wasmContext: TSciChart, + centerX: number, // pixel X of arc centre (in native/viewport coords) + centerY: number, // pixel Y of arc centre (FLIPPED: vpHeight - yCalc.getCoordinate(cy_data)) + startAngle: number, // radians; 0 = full circle start + endAngle: number, // radians; 2π = full circle + radius: number, // pixel radius + innerRadius: number,// 0 for lines + isGridLineMode: number, // 1 = anti-aliased arc line, 0 = filled sector + aspectRatio: number, // xCalc.getCoordWidth(1) / yCalc.getCoordWidth(1) + lineThickness: number +): SCRTArcParams +``` + +### `renderContext.drawArcs` (from `WebGlRenderContext2D.ts`) + +```ts +renderContext.drawArcs( + vertices: VectorArcVertex, + xCenter: number, // pixel X of viewport reference centre (for rotation) + yCenter: number, // pixel Y (native, flipped) + rotation: number, // 0 for Smith chart + clipRect: Rect, + pen: SCRTPen, + brush?: SCRTBrush, + left: number, // svr.left (series view rect left) + top: number // svr.top +) +``` + +### `getVectorArcVertex` / `getArcVertex` + +```ts +import { getVectorArcVertex, getArcVertex } from "scichart/Charting/Visuals/..."; +// or from the same NativeObject imports as getArcParams +const vecArcs = getVectorArcVertex(wasmContext); +const arc = getArcVertex(wasmContext); +arc.MakeCircularArc(arcParms); +vecArcs.push_back(arc); +``` + +### WebGL Y-flip + +In SciChart's native pixel space, Y is flipped: `nativeY = vpHeight - pixelY` + +- `vpHeight = yCalc.getCoordinate(yAxis.visibleRange.min)` (bottom of viewport) +- Or compute from `renderContext` viewport dimensions + +--- + +## `ArcAnnotation` for Interactive Highlights + +Used for the 3 interactive highlight overlays (R circle, X arc, |Γ| circle). + +### Full circle (for R highlight and |Γ| highlight) + +```ts +// Circle centred at (cx, 0) with radius rad: +new ArcAnnotation({ + x1: cx - rad, + y1: 0, // left point on diameter + x2: cx + rad, + y2: 0, // right point on diameter + height: 0, // chord IS the diameter → height = 0 gives full circle + isLineMode: true, + stroke: "#FF4444", + strokeThickness: 2.5, +}); +``` + +**Update on drag:** set `.x1`, `.x2`, `.height` properties directly — no series rebuild. + +### Arc segment (for X highlight) + +For constant-X arc with circle centre `(cx, cy)`, start point `(1, 0)`, end point `(xInt, yInt)`: + +```ts +const height = arcHeightFromCenter(1, 0, xInt, yInt, cx, cy); +new ArcAnnotation({ + x1: 1, + y1: 0, + x2: xInt, + y2: yInt, + height, + isLineMode: true, + stroke: "#4488FF", + strokeThickness: 2.5, +}); +``` + +### `arcHeightFromCenter` helper + +Given two endpoints and the circle centre, compute the `height` parameter (perpendicular offset from chord midpoint to arc): + +```ts +function arcHeightFromCenter(x1: number, y1: number, x2: number, y2: number, cx: number, cy: number): number { + const midX = (x1 + x2) / 2; + const midY = (y1 + y2) / 2; + const diffX = x2 - x1; + const diffY = y2 - y1; + const len = Math.sqrt(diffX * diffX + diffY * diffY); + // Normal vector pointing from chord midpoint toward circle centre + const normalX = diffY / len; + const normalY = -diffX / len; + // Project (centre - midpoint) onto normal + return (cx - midX) * normalX + (cy - midY) * normalY; +} +``` + +--- + +## Tick Values + +### `SmithResistanceTickProvider` + +```ts +majorTicks = [0, 0.2, 0.5, 1, 2, 5, 10, 20, 50]; +minorTicks = [0.1, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.2, 1.4, 1.6, 1.8, 3, 4]; +``` + +### `SmithReactanceTickProvider` + +```ts +majorTicks = [0.2, 0.5, 1, 2, 5, 10, 20, 50]; // positive only; negative mirror is implicit +minorTicks = [0.1, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.2, 1.4, 1.6, 1.8, 3, 4]; +``` + +--- + +## Ownership of Grid Elements + +| Element | Drawn by | +| --------------------------- | ------------------------------------------------------------------------------ | +| Unit circle | `SmithChartResistanceAxis.drawGridLines` (r=0 case or always drawn separately) | +| Real axis line (horizontal) | `SmithChartResistanceAxis.drawGridLines` | +| Constant-R circles | `SmithChartResistanceAxis.drawGridLines` | +| Constant-X arcs (+X) | `SmithChartReactanceAxis.drawGridLines` | +| Constant-X arcs (−X) | `SmithChartReactanceAxis.drawGridLines` (mirrored) | +| R labels | `TextAnnotation` in `drawExample.ts` (unchanged) | +| X labels | `TextAnnotation` in `drawExample.ts` (unchanged) | +| Interactive highlights | 3× `ArcAnnotation` in `drawExample.ts` | +| Draggable point | `XyScatterRenderableSeries` in `drawExample.ts` (unchanged) | +| Readout text | `TextAnnotation` in `drawExample.ts` (unchanged) | + +--- + +## What Gets Removed vs Kept + +### Removed from `drawExample.ts` + +- `createCircle()` helper function +- `createReactanceArc()` helper function +- `createLine()` helper function +- All `FastLineRenderableSeries` for grid circles/arcs (~40 series) +- All `XyDataSeries` for grid circles/arcs +- `preserveAspectRatio()` function +- `preRender.subscribe()` aspect-ratio hack +- `rCircleDS`, `xArcDS`, `gammaCircleDS` (`XyDataSeries`) +- 3× `FastLineRenderableSeries` for highlights +- `populateRCircle()`, `populateXArc()`, `populateCircle()` functions + +### Added to `drawExample.ts` + +- Import of `SmithChartResistanceAxis`, `SmithChartReactanceAxis` from `./smithChartAxes` +- Import of `ArcAnnotation` from scichart +- `arcHeightFromCenter()` helper +- `rCircleAnnotation`, `xArcAnnotation`, `gammaCircleAnnotation` — 3× `ArcAnnotation` +- `updateInteractiveElements()` sets `.x1/.x2/.height` on `ArcAnnotation` objects instead of rebuilding `XyDataSeries` + +### Kept in `drawExample.ts` (unchanged) + +- `SciChartSurface.create()` +- All `TextAnnotation` readouts +- `SmithChartDragModifier` class +- `pointDS` / `XyScatterRenderableSeries` (draggable dot) +- `ZoomExtentsModifier`, `MouseWheelZoomModifier`, `PinchZoomModifier` +- Impedance conversion formula: `Z = (1+Γ)/(1−Γ)` + +--- + +## Files to Create/Modify + +| File | Action | +| ------------------- | ------------------------------------------------------------------------ | +| `smithChartAxes.ts` | **Create** — tick providers + axis subclasses | +| `drawExample.ts` | **Rewrite** — wire new axes, ArcAnnotation highlights, remove old series | + +No other files need to change (index.tsx, exampleInfo.tsx, vanilla.ts are untouched). + +--- + +## Open Questions / Risks + +1. **Import paths for `getArcParams`, `getVectorArcVertex`, `getArcVertex`** — need to confirm exact import path from scichart package (likely `scichart/Charting/Visuals/RenderContext/NativeObject` or similar). Look at PolarAxisBase.ts imports. + +2. **`drawGridLines` override signature** — confirm exact TypeScript signature from `AxisBase2D.ts`. It likely takes `(renderContext: WebGlRenderContext2D, tickCoords: number[], linesPen: SCRTPen, isMajor: boolean)`. + +3. **`getTicksWithCoords()` method** — confirm this method exists on `AxisBase2D` and returns `{ majorTicks, minorTicks, majorTickCoords, minorTickCoords }` with the raw data values accessible. + +4. **`ArcAnnotation` height=0 for full circle** — needs verification. The chord `(cx-rad, 0)` to `(cx+rad, 0)` is a diameter, so `height=0` should give a semicircle — may need `height` small positive/negative, or two arcs. **Alternative:** Use `x1=cx, y1=rad, x2=cx, y2=-rad` (vertical chord) to avoid ambiguity. + +5. **Clip rectangle for `drawArcs`** — the `clipRect` parameter clips arcs to the unit circle. Determine whether to pass the series view rect or a custom unit-circle clip. + +6. **`measure()` override** — confirm that `measure()` is the right lifecycle hook for setting `this.sibling`. It may need to be `onAttach()` or similar. + +--- + +## Reference Files to Read Before Implementing + +These files are in the SciChart source (likely under `C:\SciChart\SciChart.Dev.Alt\` or the npm package): + +- `Charting/Visuals/Axis/PolarAxisBase.ts` — the key pattern to follow +- `Charting/Annotations/ArcAnnotation.ts` — full circle / arc annotation API +- `Charting/Visuals/Axis/AxisBase2D.ts` — `drawGridLines` signature, `getTicksWithCoords` +- `Charting/Visuals/RenderContext/NativeObject.ts` — `getArcParams`, `getVectorArcVertex`, `getArcVertex` +- `Charting/Visuals/RenderContext/WebGlRenderContext2D.ts` — `drawArcs` signature diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/SMITH_CHART_RESEARCH.md b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/SMITH_CHART_RESEARCH.md new file mode 100644 index 000000000..660a0f8b0 --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/SMITH_CHART_RESEARCH.md @@ -0,0 +1,185 @@ +# Smith Chart Research: Engineering Use Cases & Feature Roadmap + +## What Problem the Smith Chart Solves + +The Smith chart is a graphical calculator for RF/microwave engineers working with **complex impedance**. +At RF frequencies, components have complex impedances that vary with frequency, and mismatched impedances +cause reflections that waste power and damage equipment. Engineers need to: + +1. Visualise how impedance changes along transmission lines +2. Design networks that transform one impedance to another (matching) +3. Trade off competing objectives (gain vs. noise vs. stability) in amplifier design + +The chart works by mapping the complex impedance plane `z = R + jX` through a Möbius transformation +onto the unit disk, where position = reflection coefficient Γ = (Z_L − Z_0) / (Z_L + Z_0). + +--- + +## Key Curve Families + +| Family | Shape | Centre / Radius | +| --------------------------- | -------------------------------------- | -------------------------------------------- | +| Constant resistance circles | Circles tangent to right edge at (1,0) | Centre (r/(1+r), 0), radius 1/(1+r) | +| Constant reactance arcs | Arcs tangent to right edge | Centre (1, 1/x), radius 1/\|x\| | +| Constant \|Γ\| circles | Concentric circles at origin | = constant VSWR = constant return loss | +| Constant angle lines | Radial spokes from centre | Phase angle of Γ | +| Q circles | Circles through origin | Constant Q = \|x\|/r — used in filter design | + +--- + +## Outer Rim Scales + +A traditional paper Smith chart has three concentric scales around the outer rim: + +1. **Wavelengths Toward Generator (WTG)** — 0 to 0.5λ, running clockwise. + Moving clockwise by a value here = moving that electrical distance toward the source along a transmission line. +2. **Wavelengths Toward Load (WTL)** — 0 to 0.5λ, counter-clockwise. +3. **Angle of reflection coefficient** — degrees (±180°). The phase of Γ at that point. + +The WTG/WTL scales are critical: a lossless transmission line of length `d` rotates the impedance +point **clockwise** by `2d/λ` turns (0.5λ = one full revolution = back to start). + +--- + +## Component Movements on the Chart + +This is the heart of matching network design. Each component type traces a specific arc: + +| Component added | Movement on Z-chart | +| ------------------------------------- | ------------------------------------------------------------------- | +| Series inductor (+jωL) | Moves **up** along constant-R circle (increasing reactance) | +| Series capacitor (−j/ωC) | Moves **down** along constant-R circle | +| Series resistor (+R) | Moves **right** along constant-X arc | +| Shunt inductor | Moves along constant-G circle (easier on Y-chart) | +| Shunt capacitor | Moves along constant-G circle (easier on Y-chart) | +| Shunt resistor | Moves along constant-B arc (easier on Y-chart) | +| Lossless transmission line (length d) | Rotates **clockwise** around origin at constant \|Γ\| by 2d/λ turns | +| Lossy transmission line | Spirals inward clockwise toward centre | + +**Key rule:** Series components are easiest to work with on the Z (impedance) chart; +shunt/parallel components are easiest on the Y (admittance) chart. +Switching between Z and Y is a 180° rotation about the centre. + +--- + +## Core Engineering Workflows + +### 1. Transmission Line Analysis + +- Plot the load impedance point Z_L +- The impedance seen at distance `d` from the load = rotate clockwise by `2d/λ` on a constant-|Γ| circle +- Read the resulting Z, VSWR, return loss at any distance using the WTG outer scale + +### 2. Single-Stub Matching + +- From the load point, rotate along a constant-|Γ| circle until hitting the unit conductance circle (g=1 on Y-chart) +- Add a shunt stub (open or short-circuit stub) of the right length to cancel the susceptance → reach centre (Γ=0) + +### 3. L-Network Matching + +- Goal: move the load impedance to the chart centre (Γ=0, Z=Z₀, perfect match) +- Choose a series component to move along a constant-R circle until hitting a desired constant-|Γ| circle +- Then choose a shunt component to arc into the centre +- (Or shunt first, then series — two solutions exist) + +### 4. Amplifier Design (Advanced) + +- Plot **input stability circle** and **output stability circle** from transistor S-parameters +- Plot **available gain circles** (constant transducer gain G_T, concentric but not centred at origin) +- Plot **noise figure circles** (constant F, centred at Γ_opt) +- Find the source impedance Γ_S that trades off gain vs. noise +- Design input/output matching networks to hit those targets + +--- + +## Key Readouts Needed at Any Point + +When an engineer places a marker or hovers over a point, they want to see: + +| Value | Formula / Source | +| ---------------------------- | ----------------------------------------------- | +| Γ (complex) | x + jy directly from position | +| \|Γ\| | Distance from chart centre | +| ∠Γ (degrees) | Angle from positive real axis | +| Normalised impedance z | r + jx (read from resistance/reactance circles) | +| Normalised admittance y | g + jb (rotate 180° or read Y overlay) | +| VSWR | (1 + \|Γ\|) / (1 − \|Γ\|) | +| Return loss | −20 log₁₀(\|Γ\|) dB | +| Mismatch loss | −10 log₁₀(1 − \|Γ\|²) dB | +| Wavelengths toward generator | WTG outer scale | +| Wavelengths toward load | WTL outer scale | +| Q factor | \|x\| / r | + +--- + +## Advanced Overlays + +These circles are drawn on top of the base chart for specific use cases: + +- **Stability circles** — for amplifier design; circles bounding stable source/load regions. + Can be centred anywhere, including outside the unit circle. +- **Available gain circles** — loci of constant transducer gain G_T. Concentric circles whose + centre moves toward the optimal Γ_opt. +- **Noise figure circles** — loci of constant noise figure F. Centre at Γ_opt (optimal noise + match), expanding outward as F increases. +- **VSWR target circle** — user-defined VSWR limit (e.g. VSWR=2.0); the region inside this + circle represents "acceptable" match. Often shaded. +- **Frequency locus** — the path traced by a load as frequency sweeps. Shows how impedance + changes with frequency; often a curved arc or spiral. + +--- + +## Features of Existing Interactive Smith Chart Tools + +Tools like SimSmith, Smith V3 (web), Keysight ADS, and RF Tools provide: + +- **Click/drag** to place or move an impedance point +- **Component palette** — click "add series L", "add shunt C", "add TL section"; the arc from + current point to new point is drawn and the new position shown +- **Frequency sweep** — input component values and see the full frequency locus over a band +- **Z / Y / ZY toggle** — switch between impedance grid, admittance grid, or combined "immittance" view +- **Multiple numbered markers** — pin multiple points, display full readout table for each +- **WTG/WTL cursor** — snap to outer scale, show current electrical position on transmission line +- **VSWR goal circle** — shade the region inside a target VSWR circle +- **Stability / gain / noise circles** — input transistor S-parameters (Touchstone .s2p) and draw design circles +- **Touchstone file import** — read a measured .s1p or .s2p file and plot the frequency locus directly + +--- + +## Proposed Feature Roadmap for SciChart Smith Chart + +Ordered by engineering value: + +### Tier 1 — Core Usefulness + +1. **Full readout panel** — show |Γ|, ∠Γ, Z (r+jx), Y (g+jb), VSWR, return loss, mismatch loss, Q, WTG, WTL simultaneously when hovering or at a placed marker +2. **Outer rim scales** — WTG, WTL, and angle of Γ rendered as tick marks / labels around the perimeter +3. **Multiple named markers** — pin points on the chart, label them (M1, M2…), compare values + +### Tier 2 — Matching Design + +4. **Admittance (Y) overlay** — toggle a second grid rotated 180° (constant-G circles, constant-B arcs drawn in a different colour) +5. **VSWR target circle** — user sets a VSWR limit (e.g. 2.0), draw and optionally shade that circle +6. **Component trace** — select a component type (series L/C/R, shunt L/C/R, TL section), enter its value, draw the arc from the current point to the new impedance point + +### Tier 3 — RF Engineering / VNA Data + +7. **Frequency locus** — import a Touchstone .s1p file and plot impedance vs. frequency as a trace on the chart; markers snap to specific frequencies +8. **Frequency sweep animation** — animate the point moving along the frequency locus + +### Tier 4 — Amplifier Design (Advanced) + +9. **Stability circles** — input S-parameters, compute and draw source/load stability circles +10. **Available gain circles** — compute and draw constant-G_T circles +11. **Noise figure circles** — compute and draw constant-F circles + +--- + +## Note on Coordinate System (Cartesian vs Polar) + +The current Cartesian SciChartSurface implementation is the correct approach. +The Γ plane IS a Cartesian plane (real/imaginary axes). The Smith chart's "polar feel" +comes from data living within the unit disk, not from the gridlines being polar curves. +The constant-R circles and constant-X arcs are offset circles (not centred at the origin) +and do not simplify in polar coordinates — they would become more complex curves. +The Cartesian approach used here matches how every professional Smith chart renderer works. diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/angular.ts b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/angular.ts new file mode 100644 index 000000000..1e5fa5e21 --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/angular.ts @@ -0,0 +1,15 @@ +import { Component } from "@angular/core"; +import { ScichartAngularComponent } from "scichart-angular"; +import { drawExample } from "./drawExample"; + +@Component({ + standalone: true, + selector: "app-root", + imports: [ScichartAngularComponent], + template: ``, +}) +export class AppComponent { + title = "scichart-angular-app"; + + drawExample = drawExample; +} diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/drawExample.ts b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/drawExample.ts new file mode 100644 index 000000000..cd5c22779 --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/drawExample.ts @@ -0,0 +1,634 @@ +import { appTheme } from "../../../theme"; +import { + ChartModifierBase2D, + EAxisAlignment, + ECoordinateMode, + EHorizontalAnchorPoint, + EllipsePointMarker, + EVerticalAnchorPoint, + FastLineRenderableSeries, + ModifierMouseArgs, + MouseWheelZoomModifier, + NumericAxis, + NumberRange, + PinchZoomModifier, + SciChartSurface, + TextAnnotation, + TSciChart, + XyDataSeries, + XyScatterRenderableSeries, + ZoomExtentsModifier, +} from "scichart"; + +export const drawExample = async (rootElement: string | HTMLDivElement) => { + const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, { + theme: appTheme.SciChartJsTheme, + }); + + // Configure axes for Smith Chart (-1.15 to 1.15 range to show the full chart with padding) + sciChartSurface.xAxes.add( + new NumericAxis(wasmContext, { + visibleRange: new NumberRange(-1.15, 1.15), + axisAlignment: EAxisAlignment.Bottom, + drawMajorGridLines: false, + drawMinorGridLines: false, + drawMajorBands: false, + drawLabels: false, + drawMajorTickLines: false, + drawMinorTickLines: false, + }) + ); + + sciChartSurface.yAxes.add( + new NumericAxis(wasmContext, { + visibleRange: new NumberRange(-1.15, 1.15), + axisAlignment: EAxisAlignment.Left, + drawMajorGridLines: false, + drawMinorGridLines: false, + drawMajorBands: false, + drawLabels: false, + drawMajorTickLines: false, + drawMinorTickLines: false, + }) + ); + + // Define colors for grid lines + const gridColor = appTheme.ForegroundColor + "30"; // Light gray with transparency + const outerCircleColor = appTheme.ForegroundColor + "80"; // Darker for outer circle + + // Outer circle (unit circle) + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: createCircle(wasmContext, 0, 0, 1, 360), + stroke: outerCircleColor, + strokeThickness: 2, + }) + ); + + // Constant resistance circles (r values) + const resistanceValues = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 3.0, 4.0, 5.0, 10, 20, 50]; + const majorResistanceValues = [0.2, 0.5, 1.0, 2.0, 5.0, 10, 20, 50]; + + resistanceValues.forEach((r) => { + const center = r / (1 + r); + const radius = 1 / (1 + r); + const isMajor = majorResistanceValues.includes(r); + + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: createCircle(wasmContext, center, 0, radius, 360), + stroke: gridColor, + strokeThickness: isMajor ? 1 : 0.5, + }) + ); + + // Add resistance labels on the horizontal axis + if (isMajor) { + const labelX = center - radius; + sciChartSurface.annotations.add( + new TextAnnotation({ + text: r >= 1 ? r.toString() : r.toFixed(1), + x1: labelX, + y1: 0, + xCoordShift: 0, + yCoordShift: -15, + fontSize: 10, + textColor: appTheme.ForegroundColor, + opacity: 0.7, + horizontalAnchorPoint: EHorizontalAnchorPoint.Center, + }) + ); + } + }); + + // Constant reactance arcs (x values) + const reactanceValues = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 3.0, 4.0, 5.0, 10, 20, 50]; + const majorReactanceValues = [0.2, 0.5, 1.0, 2.0, 5.0, 10, 20, 50]; + + // Positive reactance (upper half) + reactanceValues.forEach((x) => { + const isMajor = majorReactanceValues.includes(x); + + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: createReactanceArc(wasmContext, x, true), + stroke: gridColor, + strokeThickness: isMajor ? 1 : 0.5, + }) + ); + + // Add reactance labels on the outer circle where the arc meets it + if (isMajor) { + const xVal2 = x * x; + const labelX = (xVal2 - 1) / (1 + xVal2); + const labelY = 2 * x / (1 + xVal2); + + sciChartSurface.annotations.add( + new TextAnnotation({ + text: `+${x >= 1 ? x.toString() : x.toFixed(1)}`, + x1: labelX, + y1: labelY, + xCoordShift: 15, + yCoordShift: 0, + fontSize: 10, + textColor: appTheme.ForegroundColor, + opacity: 0.7, + horizontalAnchorPoint: EHorizontalAnchorPoint.Left, + verticalAnchorPoint: EVerticalAnchorPoint.Center, + }) + ); + } + }); + + // Negative reactance (lower half) + reactanceValues.forEach((x) => { + const isMajor = majorReactanceValues.includes(x); + + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: createReactanceArc(wasmContext, x, false), + stroke: gridColor, + strokeThickness: isMajor ? 1 : 0.5, + }) + ); + + // Add reactance labels on the outer circle where the arc meets it + if (isMajor) { + const xVal2 = x * x; + const labelX = (xVal2 - 1) / (1 + xVal2); + const labelY = -2 * x / (1 + xVal2); + + sciChartSurface.annotations.add( + new TextAnnotation({ + text: `-${x >= 1 ? x.toString() : x.toFixed(1)}`, + x1: labelX, + y1: labelY, + xCoordShift: 15, + yCoordShift: 0, + fontSize: 10, + textColor: appTheme.ForegroundColor, + opacity: 0.7, + horizontalAnchorPoint: EHorizontalAnchorPoint.Left, + verticalAnchorPoint: EVerticalAnchorPoint.Center, + }) + ); + } + }); + + // Add horizontal axis line (real axis) + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: createLine(wasmContext, -1, 0, 1, 0), + stroke: gridColor, + strokeThickness: 1, + }) + ); + + // Add axis label at the left end + sciChartSurface.annotations.add( + new TextAnnotation({ + text: "0.0", + x1: -1, + y1: 0, + xCoordShift: -20, + yCoordShift: 0, + fontSize: 10, + textColor: appTheme.ForegroundColor, + opacity: 0.7, + horizontalAnchorPoint: EHorizontalAnchorPoint.Right, + verticalAnchorPoint: EVerticalAnchorPoint.Center, + }) + ); + + // ═══════════════════════════════════════════════════════ + // INTERACTIVE ELEMENTS - Draggable point with highlights + // ═══════════════════════════════════════════════════════ + + const rHighlightColor = "#FF4444"; // Red for constant resistance + const xHighlightColor = "#4488FF"; // Blue for constant reactance + const gammaColor = "#44CC44"; // Green for |Γ| and point + + // Highlighted constant-R circle (red) + const rCircleDS = new XyDataSeries(wasmContext); + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: rCircleDS, + stroke: rHighlightColor, + strokeThickness: 2.5, + }) + ); + + // Highlighted constant-X arc (blue) + const xArcDS = new XyDataSeries(wasmContext); + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: xArcDS, + stroke: xHighlightColor, + strokeThickness: 2.5, + }) + ); + + // |Γ| circle (green, dashed) + const gammaCircleDS = new XyDataSeries(wasmContext); + sciChartSurface.renderableSeries.add( + new FastLineRenderableSeries(wasmContext, { + dataSeries: gammaCircleDS, + stroke: gammaColor, + strokeThickness: 1.5, + strokeDashArray: [5, 5], + }) + ); + + // Draggable point marker (green) + const pointDS = new XyDataSeries(wasmContext); + pointDS.append(0, 0); + sciChartSurface.renderableSeries.add( + new XyScatterRenderableSeries(wasmContext, { + dataSeries: pointDS, + pointMarker: new EllipsePointMarker(wasmContext, { + width: 14, + height: 14, + fill: gammaColor, + stroke: "#FFFFFF", + strokeThickness: 2, + }), + }) + ); + + // ═══════════════════════════════════════════════════════ + // TEXT READOUTS - Γ, |Γ|, Z displayed on chart + // ═══════════════════════════════════════════════════════ + + const gammaTextAnnotation = new TextAnnotation({ + x1: 0.02, + y1: 0.02, + xCoordinateMode: ECoordinateMode.Relative, + yCoordinateMode: ECoordinateMode.Relative, + text: "Γ = 0.000 + j0.000", + fontSize: 14, + fontFamily: "monospace", + textColor: appTheme.ForegroundColor, + horizontalAnchorPoint: EHorizontalAnchorPoint.Left, + verticalAnchorPoint: EVerticalAnchorPoint.Top, + }); + + const gammaMagTextAnnotation = new TextAnnotation({ + x1: 0.02, + y1: 0.06, + xCoordinateMode: ECoordinateMode.Relative, + yCoordinateMode: ECoordinateMode.Relative, + text: "|Γ| = 0.000", + fontSize: 14, + fontFamily: "monospace", + textColor: gammaColor, + horizontalAnchorPoint: EHorizontalAnchorPoint.Left, + verticalAnchorPoint: EVerticalAnchorPoint.Top, + }); + + const impedanceTextAnnotation = new TextAnnotation({ + x1: 0.02, + y1: 0.10, + xCoordinateMode: ECoordinateMode.Relative, + yCoordinateMode: ECoordinateMode.Relative, + text: "Z = 1.000 + j0.000", + fontSize: 14, + fontFamily: "monospace", + textColor: appTheme.ForegroundColor, + horizontalAnchorPoint: EHorizontalAnchorPoint.Left, + verticalAnchorPoint: EVerticalAnchorPoint.Top, + }); + + const resistanceLabel = new TextAnnotation({ + x1: 0.02, + y1: 0.14, + xCoordinateMode: ECoordinateMode.Relative, + yCoordinateMode: ECoordinateMode.Relative, + text: "R = 1.000", + fontSize: 14, + fontFamily: "monospace", + textColor: rHighlightColor, + horizontalAnchorPoint: EHorizontalAnchorPoint.Left, + verticalAnchorPoint: EVerticalAnchorPoint.Top, + }); + + const reactanceLabel = new TextAnnotation({ + x1: 0.02, + y1: 0.18, + xCoordinateMode: ECoordinateMode.Relative, + yCoordinateMode: ECoordinateMode.Relative, + text: "X = 0.000", + fontSize: 14, + fontFamily: "monospace", + textColor: xHighlightColor, + horizontalAnchorPoint: EHorizontalAnchorPoint.Left, + verticalAnchorPoint: EVerticalAnchorPoint.Top, + }); + + sciChartSurface.annotations.add( + gammaTextAnnotation, + gammaMagTextAnnotation, + impedanceTextAnnotation, + resistanceLabel, + reactanceLabel + ); + + // ═══════════════════════════════════════════════════════ + // UPDATE LOGIC - Recalculates all visuals when point moves + // ═══════════════════════════════════════════════════════ + + function updateInteractiveElements(gammaR: number, gammaI: number) { + // Clamp to unit circle + const mag = Math.sqrt(gammaR * gammaR + gammaI * gammaI); + let gr = gammaR; + let gi = gammaI; + if (mag > 1) { + gr = gammaR / mag; + gi = gammaI / mag; + } + + const gammaMag = Math.sqrt(gr * gr + gi * gi); + + // Compute impedance: Z = (1 + Γ) / (1 - Γ) = R + jX + const denom = (1 - gr) * (1 - gr) + gi * gi; + const r = denom > 1e-10 ? (1 - gr * gr - gi * gi) / denom : Infinity; + const x = denom > 1e-10 ? (2 * gi) / denom : Infinity; + + // Update draggable point position + pointDS.clear(); + pointDS.append(gr, gi); + + // Update highlighted R circle + populateRCircle(rCircleDS, r); + + // Update highlighted X arc + populateXArc(xArcDS, x); + + // Update |Γ| circle + populateCircle(gammaCircleDS, 0, 0, gammaMag); + + // Update text readouts + const signG = gi >= 0 ? "+" : "−"; + gammaTextAnnotation.text = `Γ = ${gr.toFixed(3)} ${signG} j${Math.abs(gi).toFixed(3)}`; + gammaMagTextAnnotation.text = `|Γ| = ${gammaMag.toFixed(3)}`; + + if (!isFinite(r) || !isFinite(x)) { + impedanceTextAnnotation.text = "Z = ∞"; + resistanceLabel.text = "R = ∞"; + reactanceLabel.text = "X = ∞"; + } else { + const signZ = x >= 0 ? "+" : "−"; + impedanceTextAnnotation.text = `Z = ${r.toFixed(3)} ${signZ} j${Math.abs(x).toFixed(3)}`; + resistanceLabel.text = `R = ${r.toFixed(3)}`; + reactanceLabel.text = `X = ${x >= 0 ? "" : "−"}${Math.abs(x).toFixed(3)}`; + } + } + + // Populate an XyDataSeries with a constant-R circle + function populateRCircle(ds: XyDataSeries, r: number) { + ds.clear(); + if (!isFinite(r) || r < 0) return; + const cx = r / (1 + r); + const rad = 1 / (1 + r); + const n = 200; + for (let i = 0; i <= n; i++) { + const angle = (i / n) * 2 * Math.PI; + ds.append(cx + rad * Math.cos(angle), rad * Math.sin(angle)); + } + } + + // Populate an XyDataSeries with a constant-X reactance arc + function populateXArc(ds: XyDataSeries, xVal: number) { + ds.clear(); + if (!isFinite(xVal)) return; + + // Near zero: draw horizontal axis + if (Math.abs(xVal) < 0.001) { + ds.append(-1, 0); + ds.append(1, 0); + return; + } + + const absX = Math.abs(xVal); + const isPos = xVal > 0; + const radius = 1 / absX; + const cx = 1; + const cy = isPos ? radius : -radius; + + const xv2 = absX * absX; + const xInt = (xv2 - 1) / (1 + xv2); + const yInt = isPos ? (2 * absX) / (1 + xv2) : (-2 * absX) / (1 + xv2); + + const thetaOther = Math.atan2(yInt - cy, xInt - cx); + const thetaOrigin = isPos ? -Math.PI / 2 : Math.PI / 2; + + let startAngle: number; + let endAngle: number; + + if (isPos) { + startAngle = thetaOther; + endAngle = thetaOrigin; + while (endAngle <= startAngle) endAngle += 2 * Math.PI; + } else { + startAngle = thetaOrigin; + endAngle = thetaOther; + while (endAngle <= startAngle) endAngle += 2 * Math.PI; + } + + const n = 200; + for (let i = 0; i <= n; i++) { + const angle = startAngle + (i / n) * (endAngle - startAngle); + ds.append(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)); + } + } + + // Populate an XyDataSeries with a circle centered at (cx, cy) with given radius + function populateCircle(ds: XyDataSeries, cx: number, cy: number, radius: number) { + ds.clear(); + if (radius < 0.001) return; + const n = 200; + for (let i = 0; i <= n; i++) { + const angle = (i / n) * 2 * Math.PI; + ds.append(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)); + } + } + + // Initialize interactive elements at the origin (Γ = 0 → Z = 1) + updateInteractiveElements(0, 0); + + // ═══════════════════════════════════════════════════════ + // CUSTOM CHART MODIFIER - Handles click & drag on chart + // ═══════════════════════════════════════════════════════ + + class SmithChartDragModifier extends ChartModifierBase2D { + readonly type = "SmithChartDragModifier"; + private isDragging = false; + + modifierMouseDown(args: ModifierMouseArgs) { + super.modifierMouseDown(args); + this.isDragging = true; + this.handleDrag(args); + args.handled = true; + } + + modifierMouseMove(args: ModifierMouseArgs) { + super.modifierMouseMove(args); + if (this.isDragging) { + this.handleDrag(args); + args.handled = true; + } + } + + modifierMouseUp(args: ModifierMouseArgs) { + super.modifierMouseUp(args); + this.isDragging = false; + } + + private handleDrag(args: ModifierMouseArgs) { + const { mousePoint } = args; + const xCalc = this.parentSurface.xAxes.get(0).getCurrentCoordinateCalculator(); + const yCalc = this.parentSurface.yAxes.get(0).getCurrentCoordinateCalculator(); + + const dataX = xCalc.getDataValue(mousePoint.x); + const dataY = yCalc.getDataValue(mousePoint.y); + + updateInteractiveElements(dataX, dataY); + } + } + + // Add interaction modifiers + sciChartSurface.chartModifiers.add( + new SmithChartDragModifier(), + new MouseWheelZoomModifier(), + new ZoomExtentsModifier(), + new PinchZoomModifier() + ); + + // Preserve aspect ratio to always show circle (1:1 aspect ratio) + const xAxis = sciChartSurface.xAxes.get(0); + const yAxis = sciChartSurface.yAxes.get(0); + + sciChartSurface.preRender.subscribe(() => { + const result = preserveAspectRatio( + sciChartSurface.viewRect.width, + sciChartSurface.viewRect.height, + xAxis.visibleRange.min, + xAxis.visibleRange.max, + yAxis.visibleRange.min, + yAxis.visibleRange.max + ); + + xAxis.visibleRange = new NumberRange(result.minVisibleX, result.maxVisibleX); + yAxis.visibleRange = new NumberRange(result.minVisibleY, result.maxVisibleY); + }); + + return { sciChartSurface, wasmContext }; +}; + +// ═══════════════════════════════════════════════════════ +// HELPER FUNCTIONS +// ═══════════════════════════════════════════════════════ + +// Preserve aspect ratio (ensures circles remain circular) +function preserveAspectRatio( + width: number, + height: number, + minVisibleX: number, + maxVisibleX: number, + minVisibleY: number, + maxVisibleY: number +) { + const visibleWidth = maxVisibleX - minVisibleX; + const visibleHeight = maxVisibleY - minVisibleY; + const containerAspectRatio = width / height; + const visibleAspectRatio = visibleWidth / visibleHeight; + + let newMinX: number, newMaxX: number, newMinY: number, newMaxY: number; + + if (containerAspectRatio > visibleAspectRatio) { + const newVisibleWidth = visibleHeight * containerAspectRatio; + const widthDiff = newVisibleWidth - visibleWidth; + newMinX = minVisibleX - widthDiff / 2; + newMaxX = maxVisibleX + widthDiff / 2; + newMinY = minVisibleY; + newMaxY = maxVisibleY; + } else { + const newVisibleHeight = visibleWidth / containerAspectRatio; + const heightDiff = newVisibleHeight - visibleHeight; + newMinX = minVisibleX; + newMaxX = maxVisibleX; + newMinY = minVisibleY - heightDiff / 2; + newMaxY = maxVisibleY + heightDiff / 2; + } + + return { minVisibleX: newMinX, maxVisibleX: newMaxX, minVisibleY: newMinY, maxVisibleY: newMaxY }; +} + +// Create a circle as an XyDataSeries +function createCircle(wasmContext: TSciChart, cx: number, cy: number, radius: number, points: number = 360): XyDataSeries { + const dataSeries = new XyDataSeries(wasmContext); + for (let i = 0; i <= points; i++) { + const angle = (i / points) * 2 * Math.PI; + dataSeries.append(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)); + } + return dataSeries; +} + +// Create a reactance arc as an XyDataSeries. +// For reactance value xVal, the circle has center (1, 1/xVal) and radius 1/xVal (positive) +// or center (1, -1/xVal) and radius 1/xVal (negative). +// All arcs pass through (1, 0) and curve to the other intersection with the unit circle. +function createReactanceArc( + wasmContext: TSciChart, + xVal: number, + isPositive: boolean, + numPoints: number = 200 +): XyDataSeries { + const dataSeries = new XyDataSeries(wasmContext); + const radius = 1 / xVal; + const cx = 1; + const cy = isPositive ? radius : -radius; + + // The reactance circle intersects the unit circle at two points: + // 1) Always at (1, 0) + // 2) At ((xVal²-1)/(1+xVal²), ±2·xVal/(1+xVal²)) + const xVal2 = xVal * xVal; + const xInt = (xVal2 - 1) / (1 + xVal2); + const yInt = isPositive ? (2 * xVal) / (1 + xVal2) : (-2 * xVal) / (1 + xVal2); + + // Angle from circle center to the second intersection point + const thetaOther = Math.atan2(yInt - cy, xInt - cx); + + // Angle from circle center to (1, 0): + // Positive: atan2(0 - 1/xVal, 0) = -π/2 + // Negative: atan2(0 + 1/xVal, 0) = π/2 + const thetaOrigin = isPositive ? -Math.PI / 2 : Math.PI / 2; + + let startAngle: number, endAngle: number; + + if (isPositive) { + // Sweep counterclockwise from thetaOther to thetaOrigin + startAngle = thetaOther; + endAngle = thetaOrigin; + while (endAngle <= startAngle) endAngle += 2 * Math.PI; + } else { + // Sweep counterclockwise from thetaOrigin to thetaOther + startAngle = thetaOrigin; + endAngle = thetaOther; + while (endAngle <= startAngle) endAngle += 2 * Math.PI; + } + + for (let i = 0; i <= numPoints; i++) { + const angle = startAngle + (i / numPoints) * (endAngle - startAngle); + dataSeries.append(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)); + } + + return dataSeries; +} + +// Create a straight line as an XyDataSeries +function createLine(wasmContext: TSciChart, x1: number, y1: number, x2: number, y2: number): XyDataSeries { + const dataSeries = new XyDataSeries(wasmContext); + dataSeries.append(x1, y1); + dataSeries.append(x2, y2); + return dataSeries; +} diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/exampleInfo.tsx b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/exampleInfo.tsx new file mode 100644 index 000000000..0b9b36245 --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/exampleInfo.tsx @@ -0,0 +1,66 @@ +import { createExampleInfo } from "../../../exampleInfoUtils"; +import { IExampleMetadata } from "../../../IExampleMetadata"; + +const metaData: IExampleMetadata = + //// This metadata is computer generated - do not edit! + { + reactComponent: "SmithChart", + id: "chart2D_modifyAxisBehavior_SmithChart", + imagePath: "smith-chart.jpg", + description: + "Demonstrates how to create a **JavaScript Chart with Smith Chart** using SciChart.js, High Performance JavaScript Charts", + tips: [], + frameworks: { + javascript: { + subtitle: + "Demonstrates how to create a **JavaScript Chart with Smith Chart** using SciChart.js, High Performance JavaScript Charts", + title: "JavaScript Chart with Smith Chart", + pageTitle: "JavaScript Chart with Smith Chart", + metaDescription: + "Demonstrates Smith Chart on a JavaScript Chart using SciChart.js. SciChart supports unlimited left, right, top, bottom X, Y axis with configurable layout", + markdownContent: + "## Smith Chart JavaScript Chart\n\n### Overview\nThis example demonstrates how to create a high-performance chart with **Smith Chart** using SciChart.js in a JavaScript framework. The chart positions the x and y axes to cross at the data value (0,0), producing an oscilloscope-like layout. It leverages the advanced customization available in SciChart.js for axis placement, as detailed in the [Central Axis Layout documentation](https://www.scichart.com/documentation/js/v5/2d-charts/axis-api/multi-axis-and-layout/central-axis-layout/).\n\n### Technical Implementation\nThe implementation begins by initializing a `SciChartSurface` with an integrated WebAssembly context, following guidelines from the [Tutorial 01 - Including SciChart.js in an HTML Page using CDN](https://www.scichart.com/documentation/js/v5/get-started/tutorials-cdn/tutorial-01-using-cdn/) documentation. Both the x and y axes are configured as inner axes by enabling the `axis.isInnerAxis` property and aligned to the center by setting the `sciChartSurface.layoutManager` property equal to a `SmithChartLayoutManager` instance with data value based positioning. A dynamically generated butterfly curve is rendered using the `FastLineRenderableSeries` and utilizes a fade animation as provided by the [Animations API](https://www.scichart.com/documentation/js/v5/2d-charts/animations-api/animations-api-overview/) for performance and visual enhancement. Interaction is further enriched with modifiers such as `ZoomPanModifier`, `MouseWheelZoomModifier`, and `ZoomExtentsModifier`, which support smooth zooming and panning.\n\n### Features and Capabilities\nThis example showcases advanced chart capabilities such as real-time data rendering for complex curves and dynamic interactivity. The efficient use of WebAssembly ensures high performance even when visualizing large data sets. Additionally, the chart demonstrates clear separation of concerns by isolating initialization, axis configuration, data rendering, and user interaction, which allows for robust customization based on the application’s needs.\n\n### Integration and Best Practices\nIntegrating SciChart.js into a JavaScript application is streamlined by employing a modular approach where the chart initialization function is directly invoked with a designated root element. Resource cleanup is handled by returning a destructor function that calls the `delete()` method on the `SciChartSurface`, in line with best practices suggested in the [Memory Best Practices](https://www.scichart.com/documentation/js/v5/2d-charts/performance-tips/memory-best-practices/) documentation. Developers looking to extend or optimize their chart applications can also refer to the [Getting Started with SciChart JS](https://www.scichart.com/getting-started/scichart-javascript/) guide for additional insights on integration and performance optimization.", + }, + react: { + subtitle: + "Demonstrates how to create a **React Chart with Smith Chart** using SciChart.js, High Performance JavaScript Charts", + title: "React Chart with Smith Chart", + pageTitle: "React Chart with Smith Chart", + metaDescription: + "Demonstrates Smith Chart on a React Chart using SciChart.js. SciChart supports unlimited left, right, top, bottom X, Y axis with configurable layout", + markdownContent: + "## React Chart with Smith Chart\n\n### Overview\nThis example demonstrates how to create a high performance React chart with Smith Chart using SciChart.js. The chart leverages a custom Smith Chart layout, showcasing how axes can be positioned in the center of the chart by setting the `axis.isInnerAxis` property and using the `SmithChartLayoutManager`.\n\n### Technical Implementation\nThe chart is initialized through the `` component by passing the drawExample function as a prop, a pattern that follows [best practices for React integration](https://www.scichart.com/blog/react-charts-with-scichart-js/). The drawExample function sets up the `SciChartSurface` and configures the Smith Chart by using the `SmithChartLayoutManager` with options that specify data value based positioning. `NumericAxis` instances are added with the `isInnerAxis` flag enabled to ensure that the axes are rendered inside the chart. For more detailed information on central axis customization, please refer to the [Central Axis Layout documentation](https://www.scichart.com/documentation/js/v5/2d-charts/axis-api/multi-axis-and-layout/central-axis-layout/).\n\n### Features and Capabilities\nThe example features an efficient data series generation that calculates a butterfly curve and displays it using `FastLineRenderableSeries` with a fade animation, as described in [The Animations API](https://www.scichart.com/documentation/js/v5/2d-charts/animations-api/animations-api-overview/). Interaction is enhanced by including several chart modifiers such as `ZoomPanModifier`, `MouseWheelZoomModifier`, and `ZoomExtentsModifier`, providing dynamic zoom and pan capabilities which can be explored further in the [ZoomPanModifier documentation](https://www.scichart.com/documentation/js/v5/2d-charts/chart-modifier-api/zooming-and-panning/zoom-pan-modifier/). Additionally, a `TextAnnotation` is added to explain the chart's functionality.\n\n### Integration and Best Practices\nThis React implementation follows a modular approach by using the `` component to encapsulate chart initialization, making it easier to integrate SciChart.js into larger React applications. The example illustrates proper use of inner axis configurations with `NumericAxis`, supporting efficient performance and clear visual presentation. Developers looking to extend the feature set or optimize performance can find further guidance in the [Numeric Axis documentation](https://www.scichart.com/documentation/js/v5/2d-charts/axis-api/axis-types/numeric-axis/) as well as the [React Charts with SciChart.js article](https://www.scichart.com/blog/react-charts-with-scichart-js/).", + }, + angular: { + subtitle: + "Demonstrates how to create a **Angular Chart with Smith Chart** using SciChart.js, High Performance JavaScript Charts", + title: "Angular Chart with Smith Chart", + pageTitle: "Angular Chart with Smith Chart", + metaDescription: + "Demonstrates Smith Chart on a Angular Chart using SciChart.js. SciChart supports unlimited left, right, top, bottom X, Y axis with configurable layout", + markdownContent: + "## Angular Chart with Smith Chart Example\n\n### Overview\nThis Angular example demonstrates how to create a high-performance chart with **Smith Chart** using SciChart.js. The chart positions both axes in the center by using a custom central axis layout and inner axis configuration. It leverages the [scichart-angular](https://www.npmjs.com/package/scichart-angular) package, which simplifies integration with Angular projects using the `ScichartAngularComponent`.\n\n### Technical Implementation\nThe implementation initializes a `SciChartSurface` using the Angular standalone component, which passes the chart setup function through the `[initChart]` property. The chart employs the `SmithChartLayoutManager` with options set to position the axes based on data values, ensuring that the axes cross at (0,0). Both the x and y axes are defined as inner axes by setting the `axis.isInnerAxis` property, as detailed in the [Central Axis Layout documentation](https://www.scichart.com/documentation/js/v5/2d-charts/axis-api/multi-axis-and-layout/central-axis-layout/). A `FastLineRenderableSeries` is used to render a dynamically generated butterfly curve, with a fade animation enhancing performance. Additional interactivity is provided through the `ZoomPanModifier`, `MouseWheelZoomModifier`, and `ZoomExtentsModifier`, which offer smooth zooming and panning as described in the [Tutorial 03 - Adding Zooming, Panning Behavior](https://www.scichart.com/documentation/js/v5/get-started/tutorials-js-npm-webpack/tutorial-03-adding-zooming-panning-behavior/).\n\n### Features and Capabilities\nThis example highlights advanced chart capabilities such as real-time updates, dynamic data rendering, and a unique oscilloscope style layout. The efficient rendering of complex data series, such as the butterfly curve, is optimized by the use of `FastLineRenderableSeries` and `FadeAnimation`, supporting high performance even with large data sets. For insights into performance optimization, developers can refer to the techniques discussed in [Performance Optimisation of JavaScript Applications & Charts](https://www.scichart.com/blog/performance-optimisation-of-javascript-applications-charts/).\n\n### Integration and Best Practices\nFollowing Angular best practices, the example uses a standalone component to encapsulate chart initialization, which promotes modularity and ease of management within larger Angular applications. Developers are encouraged to consult the [Getting Started with SciChart JS](https://www.scichart.com/getting-started/scichart-javascript/) guide for further details on Angular application integration techniques. This example provides a robust reference for implementing complex axis layouts, interactive modifiers, and performance optimizations in Angular environments.", + }, + }, + documentationLinks: [ + { + href: "https://www.scichart.com/documentation/js/v5/2d-charts/axis-api/multi-axis-and-layout/central-axis-layout/", + title: "SciChart.js Central Axis Documentation page", + linkTitle: "Central Axis documentation", + }, + ], + path: "smith-chart", + metaKeywords: "multiple, axis, chart, javascript, webgl, canvas", + onWebsite: true, + filepath: "Charts2D/ModifyAxisBehavior/SmithChart", + thumbnailImage: "smith-chart.jpg", + sandboxConfig: {}, + markdownContent: null, + pageLayout: "default", + extraDependencies: {}, + isNew: false, + }; +//// End of computer generated metadata + +export const SmithChartExampleInfo = createExampleInfo(metaData); +export default SmithChartExampleInfo; diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/index.tsx b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/index.tsx new file mode 100644 index 000000000..aa8d648b1 --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/index.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; +import { appTheme } from "../../../theme"; +import { SciChartReact } from "scichart-react"; +import commonClasses from "../../../styles/Examples.module.scss"; +import { drawExample } from "./drawExample"; + +// React component needed as our examples app is react. +// SciChart can be used in Angular, Vue, Blazor and vanilla JS! See our Github repo for more info +export default function ChartComponent() { + return ; +} diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/smith-chart.jpg b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/smith-chart.jpg new file mode 100644 index 000000000..351db0f9e Binary files /dev/null and b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/smith-chart.jpg differ diff --git a/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/vanilla.ts b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/vanilla.ts new file mode 100644 index 000000000..4f87ed08b --- /dev/null +++ b/Examples/src/components/Examples/Charts2D/ModifyAxisBehavior/SmithChart/vanilla.ts @@ -0,0 +1,19 @@ +import { drawExample } from "./drawExample"; + +/** + * Creates charts on the provided root elements + * @returns cleanup function + */ +const create = async () => { + const { sciChartSurface } = await drawExample("chart"); + + const destructor = () => { + sciChartSurface.delete(); + }; + + return destructor; +}; + +create(); + +// call the `destructor` returned by the `create` promise to dispose the charts when necessary