React Vector Field Plot

Creates a React Vector Field using our FastLineSegmentRenderableSeries paired with a custom PaletteProvider and optional arrow heads made with FastTriangleRenderableSeries

Fullscreen

Edit

 Edit

Docs

src/drawExample.ts
import {
    NumberRange,
    SciChartSurface,
    NumericAxis,
    CentralAxesLayoutManager,
    FastLineSegmentRenderableSeries,
    IStrokePaletteProvider,
    EStrokePaletteMode,
    parseColorToUIntArgb,
    IRenderableSeries,
    TPaletteProviderDefinition,
    EPaletteProviderType,
    XyDataSeries,
    ZoomPanModifier,
    ZoomExtentsModifier,
    MouseWheelZoomModifier,
    FastTriangleRenderableSeries,
    ETriangleSeriesDrawMode,
    CursorModifier,
    TCursorTooltipDataTemplate,
    SeriesInfo,
    XySeriesInfo,
} from "scichart";

import { appTheme } from "./appTheme";

const lineEndColor = appTheme.VividOrange;

class LineSegmentPaletteProvider implements IStrokePaletteProvider {
    public readonly strokePaletteMode = EStrokePaletteMode.GRADIENT;
    private readonly palettedStart = parseColorToUIntArgb(appTheme.VividBlue);
    private readonly palettedEnd = parseColorToUIntArgb(lineEndColor);

    public onAttached(parentSeries: IRenderableSeries): void {}

    public onDetached(): void {}

    public overrideStrokeArgb(xValue: number, yValue: number, index: number): number {
        return index % 2 === 0 ? this.palettedStart : this.palettedEnd;
    }

    public toJSON(): TPaletteProviderDefinition {
        return { type: EPaletteProviderType.Custom, customType: "MyPaletteProvider" };
    }
}

/**
 * Format function
 */
const fFn = (v: number) => v.toFixed(1);

// Tooltip Data Template
const tooltipDataTemplate: TCursorTooltipDataTemplate = (seriesInfos: SeriesInfo[]) => {
    const valuesWithLabels: string[] = [];
    seriesInfos.forEach((si) => {
        const xySI = si as XySeriesInfo;
        if (xySI.isWithinDataBounds) {
            if (!isNaN(xySI.yValue) && xySI.isHit) {
                valuesWithLabels.push(
                    `start (${fFn(xySI.xValue)},${fFn(xySI.yValue)}) end (${fFn(xySI.point2xValue)},${fFn(xySI.point2yValue)})`
                );
            }
        }
    });
    return valuesWithLabels;
};

function addArrowheads(xValues: number[], yValues: number[], arrowLength = 0.36, arrowAngle = Math.PI / 12) {
    const arrows = [];
    for (let i = 0; i < xValues.length; i += 2) {
        // Line start and end
        const x1 = xValues[i];
        const y1 = yValues[i];
        const x2 = xValues[i + 1];
        const y2 = yValues[i + 1];

        // Direction of the line
        const dx = x2 - x1;
        const dy = y2 - y1;
        const lineAngle = Math.atan2(dy, dx);

        // Arrowhead points
        const angle1 = lineAngle + Math.PI - arrowAngle;
        const angle2 = lineAngle + Math.PI + arrowAngle;

        const arrowPoint1 = [x2 + arrowLength * Math.cos(angle1), y2 + arrowLength * Math.sin(angle1)];
        const arrowPoint2 = [x2 + arrowLength * Math.cos(angle2), y2 + arrowLength * Math.sin(angle2)];

        // Arrowhead: [tip, base1, base2]
        arrows.push([
            [x2, y2], // Tip of the arrow
            arrowPoint1, // First base point
            arrowPoint2, // Second base point
        ]);
    }
    return arrows;
}

export const drawExample = async (rootElement: string | HTMLDivElement) => {
    // Create a SciChartSurface
    const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
        theme: appTheme.SciChartJsTheme,
    });

    // configure central axes
    const layoutManager = new CentralAxesLayoutManager();

    sciChartSurface.layoutManager = layoutManager;

    const xMin = -15;
    const xMax = 15;
    const yMin = -10;
    const yMax = 10;

    // Slightly crop the sparse outer arrows while leaving more of the field visible.
    const growBy = new NumberRange(-0.05, -0.05);

    const xAxis = new NumericAxis(wasmContext, {
        axisBorder: { color: "white", borderBottom: 1 },
        drawMajorBands: false,
        drawMajorGridLines: false,
        drawMinorGridLines: false,
        growBy,
    });
    sciChartSurface.xAxes.add(xAxis);

    const yAxis = new NumericAxis(wasmContext, {
        axisBorder: { color: "white", borderRight: 1 },
        drawMajorBands: false,
        drawMajorGridLines: false,
        drawMinorGridLines: false,
        growBy,
    });
    sciChartSurface.yAxes.add(yAxis);

    const multiplier = 0.01;

    const linesXValues: number[] = [];
    const linesYValues: number[] = [];

    for (let x = xMin; x <= xMax; x++) {
        for (let y = yMin; y <= yMax; y++) {
            // start point
            linesXValues.push(x);
            linesYValues.push(y);
            // end point
            const xEnd = x + (x * x - y * y - 4) * multiplier;
            const yEnd = y + 2 * x * y * multiplier;

            linesXValues.push(xEnd);
            linesYValues.push(yEnd);
        }
    }

    const lineSegmentSeries = new FastLineSegmentRenderableSeries(wasmContext, {
        dataSeries: new XyDataSeries(wasmContext, {
            xValues: linesXValues,
            yValues: linesYValues,
        }),
        strokeThickness: 3,
        paletteProvider: new LineSegmentPaletteProvider(),
    });

    const arrowheads = addArrowheads(linesXValues, linesYValues);

    const arrowheadsXvalues = arrowheads.flat().map((d) => d[0]);
    const arrowheadsYvalues = arrowheads.flat().map((d) => d[1]);

    const polygonSeries = new FastTriangleRenderableSeries(wasmContext, {
        dataSeries: new XyDataSeries(wasmContext, {
            xValues: arrowheadsXvalues,
            yValues: arrowheadsYvalues,
        }),
        isDigitalLine: false,
        opacity: 1,
        fill: lineEndColor,
        drawMode: ETriangleSeriesDrawMode.List, // Polygon / List / Strip
    });

    sciChartSurface.renderableSeries.add(lineSegmentSeries);

    sciChartSurface.renderableSeries.add(polygonSeries);

    sciChartSurface.chartModifiers.add(new ZoomPanModifier());
    sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());
    sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier());
    sciChartSurface.chartModifiers.add(new CursorModifier({ showTooltip: true, tooltipDataTemplate }));

    return { sciChartSurface, wasmContext };
};

Vector Field Chart - React

Overview

This React implementation of a Vector Field Chart wraps the core SciChart.js visualization in a reusable SciChartReact component. It demonstrates efficient integration of mathematical vector field visualization in a React application.

Technical Implementation

The chart configuration from drawExample.ts is passed via the initChart prop. React manages the component lifecycle while SciChart handles WebGL rendering. The solution uses XyDataSeries for vector data and implements a custom tooltip template through CursorModifier.

Features and Capabilities

The component maintains all JavaScript version features including dynamic vector generation, gradient-colored segments, and interactive zoom/pan. The React wrapper adds responsive resizing and clean unmounting through SciChartReact's built-in lifecycle management.

Integration and Best Practices

This pattern follows React best practices by separating visualization logic (drawExample) from presentation (ChartComponent). For advanced use cases, consider using React context for theme propagation or memoizing calculation-heavy operations.