npm package: @rm-graph/react
Description: RM-Graphs React - React components for RM-Graphs charting library
Latest version: 0.1.19

# @rm-graph/react

RM-Graphs React - React components for RM-Graphs charting library.

## Installation

```bash
npm install @rm-graph/react
```

> `scichart` is automatically installed as a dependency!

## Usage

```tsx
import { Column3DChart, PRPDChart, TrendsChart } from '@rm-graph/react';

function App() {
  const data = [[45, 1500, 1], [90, 2200, 2], [180, 1800, 3]];

  return (
    <Column3DChart
      id="my-chart"
      data={data}
      height={400}
      options={{
        theme: 'dark',
        xAxisTitle: 'Phase Angle (°)',
        yAxisTitle: 'Amplitude (mVp)',
        zAxisTitle: 'Cycle',
      }}
      onReady={(chart) => console.log('Chart ready!')}
    />
  );
}
```

## Components

- `<Column3DChart />` - 3D column charts
- `<PRPDChart />` - Phase Resolved Partial Discharge heatmap
- `<TrendsChart />` - Multi-series time trends
- `<Surface3DChart />` - 3D surface visualization

---

## PRPDChart

The `PRPDChart` component displays Phase Resolved Partial Discharge data as an interactive heatmap.

### Usage

```tsx
import { PRPDChart } from '@rm-graph/react';
import type { PRPDDataPoint } from '@rm-graph/react';

function App() {
  // Data format: [phaseAngle, amplitude, count][]
  const prpdData: PRPDDataPoint[] = [
    [45, 1500, 10],   // 45° phase, 1500mV amplitude, 10 pulses
    [90, 2200, 25],   // 90° phase, 2200mV amplitude, 25 pulses
    [180, -1800, 15], // 180° phase, -1800mV amplitude, 15 pulses
    [270, -2500, 30], // 270° phase, -2500mV amplitude, 30 pulses
    // ... more data points
  ];

  return (
    <PRPDChart
      id="my-prpd-chart"
      title="PRPD Analysis"
      data={prpdData}
      height={400}
      scalingFactor={1}
      unitOfMeasurement="mVp"
      maxPeak={5000}
      minPeak={-5000}
      maxPhaseAngle={360}
      showSineWave={true}
      showColorPalette={true}
      showMaximizeIcon={true}
      colorMin={0}
      colorMax={100}
      onStatsChange={(stats) => console.log('Stats:', stats)}
    />
  );
}
```

### PRPDDataPoint Type

```typescript
type PRPDDataPoint = [number, number, number];
// [phaseAngle, amplitude, count]
```

| Index | Type | Description |
|-------|------|-------------|
| `0` | `number` | Phase angle in degrees (0-360) |
| `1` | `number` | Amplitude value (positive or negative) |
| `2` | `number` | Pulse count at this phase/amplitude |

### PRPDChart Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `id` | `string` | auto-generated | Unique ID for the chart |
| `title` | `string` | - | Chart title displayed in header |
| `data` | `PRPDDataPoint[]` | `[]` | Array of [phase, amplitude, count] tuples |
| `height` | `number \| string` | `400` | Chart height |
| `width` | `number \| string` | `'100%'` | Chart width |
| `scalingFactor` | `number` | `1` | Multiplier for amplitude values |
| `unitOfMeasurement` | `string` | `'mVp'` | Unit label for amplitude axis |
| `maxPeak` | `number` | `5000` | Maximum amplitude value |
| `minPeak` | `number` | `-5000` | Minimum amplitude value |
| `maxPhaseAngle` | `number` | `360` | Maximum phase angle |
| `yAxisRange` | `number` | - | Y-axis visible range |
| `showSineWave` | `boolean` | `true` | Show sine wave overlay |
| `showColorPalette` | `boolean` | `true` | Show color legend bar |
| `showToolbar` | `boolean` | `true` | Show toolbar buttons |
| `showMaximizeIcon` | `boolean` | `false` | Show maximize button |
| `isMaximized` | `boolean` | `false` | Current maximized state |
| `colorMin` | `number` | `0` | Minimum color scale value |
| `colorMax` | `number` | `100` | Maximum color scale value |
| `resolutionLabel` | `PRPDResolutionLabel` | - | Resolution mode config |
| `windowingData` | `PRPDWindowingRegion[]` | - | Windowing filter regions |

### Callbacks

| Prop | Type | Description |
|------|------|-------------|
| `onReady` | `(chart) => void` | Called when chart is initialized |
| `onStatsChange` | `(stats) => void` | Called when statistics update |
| `onResolutionChange` | `(label) => void` | Called when resolution mode changes |
| `onWindowingClick` | `() => void` | Called when windowing button clicked |
| `onYAxisRangeChange` | `(range) => void` | Called when Y-range changes |
| `onMaximizeToggle` | `() => void` | Called when maximize toggled |

### Features

- 🎨 **Heatmap Visualization** - Color-coded pulse density display
- 📊 **Color Palette Legend** - Visual scale for pulse counts
- 〰️ **Sine Wave Overlay** - Reference waveform display
- 🔍 **Zoom & Pan** - Interactive data exploration
- 📏 **Y-Range Control** - Adjustable amplitude range (100-5000 or custom)
- 🔄 **Resolution Modes** - UNI/BI and HI/LO toggle options
- 🪟 **Windowing Support** - Filter specific phase/amplitude regions
- 📸 **Screenshot** - Export chart as PNG image
- 📈 **Statistics** - Real-time peak value and total count

---

## TrendsChart

The `TrendsChart` component displays multiple time series with interactive features.

### Usage

```tsx
import { TrendsChart } from '@rm-graph/react';
import type { TrendLineData } from '@rm-graph/react';

function App() {
  const trendsData: TrendLineData[] = [
    {
      name: 'Channel 1',
      values: [150, 145, 160, 155, 148],
      timestamps: [1707012000000, 1707012060000, 1707012120000, 1707012180000, 1707012240000],
      color: '#3b82f6',
      uom: 2,
    },
    {
      name: 'Channel 2',
      values: [100, 95, 110, 105, 98],
      timestamps: [1707012000000, 1707012060000, 1707012120000, 1707012180000, 1707012240000],
      color: '#ef4444',
      uom: 2,
    },
  ];

  return (
    <TrendsChart
      id="my-trends-chart"
      data={trendsData}
      height={500}
      showLegend={true}
      showResetButton={true}
      showPointMarkerToggle={true}
      showOverview={true}
      showScreenshotButton={true}
    />
  );
}
```

### TrendLineData Interface

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `name` | `string` | ✅ | Display name for the series (shown in legend) |
| `values` | `number[]` | ✅ | Array of numeric values (Y-axis data points) |
| `timestamps` | `number[]` | ✅ | Array of timestamps in milliseconds (X-axis data points) |
| `color` | `string` | ✅ | Line color (hex or CSS color, e.g., `#3b82f6`) |
| `uom` | `number` | ❌ | Optional unit of measurement code |

> **Note**: The `values` and `timestamps` arrays must have the same length.

### TrendsChart Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `id` | `string` | auto-generated | Unique ID for the chart |
| `data` | `TrendLineData[]` | `[]` | Array of trend line data |
| `height` | `number \| string` | `400` | Chart height |
| `xAxisTitle` | `string` | `"Time (HH:MM)"` | X-axis title |
| `yAxisTitle` | `string` | Based on chartType | Y-axis title |
| `showLegend` | `boolean` | `true` | Show interactive legend |
| `showResetButton` | `boolean` | `true` | Show reset zoom button |
| `showPointMarkerToggle` | `boolean` | `true` | Show point marker toggle |
| `showOverview` | `boolean` | `true` | Show overview mini-map |
| `showScreenshotButton` | `boolean` | `true` | Show screenshot button |
| `onScreenshot` | `() => void` | - | Callback when screenshot is taken |

### Features

- 📊 **Interactive Legend** - Click to toggle series visibility
- 🔍 **Zoom & Pan** - Rubber band zoom, mouse wheel zoom, drag to pan
- 🔄 **Reset Zoom** - One-click reset to initial view
- 📍 **Point Markers** - Toggle visibility of data point markers
- 🗺️ **Overview Chart** - Mini-map for navigation
- 📸 **Screenshot** - Export chart as PNG with header
- 📅 **Custom Date Labels** - Formatted as DD/MM/YYYY HH:MM

---

## Hooks

```tsx
import { useChart } from '@rm-graph/react';
import { createColumn3DChart } from '@rm-graph/core';

function CustomChart() {
  const { containerRef, chart, isLoading } = useChart({
    config: { data: [...] },
    createChart: createColumn3DChart,
  });

  return <div ref={containerRef} />;
}
```

---

## 🔑 SciChart License Configuration

This package uses SciChart.js, which requires a valid license key to remove watermarks. The license system supports encrypted storage and multiple configuration methods.

### Complete License Flow

#### Step 1: Get Your SciChart License Key

1. **Register your domain** at [SciChart License Portal](https://www.scichart.com/licensing-scichart-js/)
2. **Add allowed domains** to your license:
   - For production: `yourdomain.com`, `app.yourdomain.com`
   - For development: `localhost`, `127.0.0.1`, or your local IP (e.g., `192.168.140.16`)
3. **Copy your license key** (format: `xxxx-xxxx-xxxx-xxxx`)

> **Important**: SciChart licenses are domain-locked. Make sure to register all domains where you'll use the charts.

#### Step 2: Configure License in Your App

You have **three options** to configure the license:

##### Option A: Using LicenseManager Component (Recommended for User-Facing Apps)

Add the `LicenseManager` component to your settings page:

```tsx
import { useState } from 'react';
import { LicenseManager } from '@rm-graph/react';
import type { LicenseStatus } from '@rm-graph/react';

function SettingsPage() {
  const [showLicense, setShowLicense] = useState(false);

  const handleLicenseChange = (status: LicenseStatus) => {
    console.log('License status:', status);
    // Don't auto-close - let user see success message and click Reload
  };

  return (
    <div>
      <button onClick={() => setShowLicense(!showLicense)}>
        🔑 License Settings
      </button>

      {showLicense && (
        <LicenseManager 
          onLicenseChange={handleLicenseChange}
          title="SciChart License Configuration"
          placeholder="Paste your SciChart license key here"
          theme="light"
        />
      )}
    </div>
  );
}
```

**User Flow:**
1. User opens settings page
2. User enters SciChart license key in the input field
3. User clicks "Save License"
4. License is encrypted and stored in localStorage
5. License is immediately applied to SciChart
6. User may need to reload page for existing charts to pick up the license

##### Option B: Programmatic Setup (For Admin/Backend Configuration)

```tsx
// In your app entry point (main.tsx or App.tsx)
import { loadLicense, setLicense } from '@rm-graph/react';

// Load existing license on app startup
async function initializeApp() {
  const loaded = await loadLicense();
  if (loaded) {
    console.log('License loaded from storage');
  } else {
    console.log('No license found - charts will show watermark');
  }
  
  // Render your app
  ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
}

initializeApp();

// To set a license programmatically:
async function configureLicense(licenseKey: string) {
  const success = await setLicense(licenseKey);
  if (success) {
    console.log('License saved and applied!');
  }
}
```

##### Option C: Environment Variable (For Build-Time Configuration)

```bash
# .env file
VITE_SCICHART_LICENSE_KEY=your-license-key-here
```

```tsx
// main.tsx
import { setupSciChartLicense } from '@rm-graph/react';

// This will automatically read from VITE_SCICHART_LICENSE_KEY
setupSciChartLicense({
  fromEnv: true,
  fromLocalStorage: true, // Also check encrypted storage
});
```

#### Step 3: License Storage & Security

The license system provides **encrypted storage** for security:

- **Encryption**: License keys are encrypted using AES-256-GCM (when Web Crypto API is available) or base64 encoding (fallback)
- **Storage**: Encrypted license is stored in `localStorage` under key `RMGRAPH_LICENSE_DATA`
- **Security Note**: While encrypted, this provides obfuscation rather than true security since decryption happens client-side. For production, combine with SciChart's domain-locking.

**Storage Flow:**
```
User enters license → Encrypt → Store in localStorage → Decrypt on load → Apply to SciChart
```

#### Step 4: Verify License is Working

After configuring the license:

1. **Check browser console** - Should see no license errors
2. **Check charts** - Should render without "SCICHART" watermark
3. **Check license status**:

```tsx
import { getLicenseStatus, hasStoredLicense, isLicenseApplied } from '@rm-graph/react';

const status = getLicenseStatus();
console.log('Has license stored:', status.hasLicense);
console.log('License applied:', status.isApplied);

// Or use individual functions
if (hasStoredLicense()) {
  console.log('License exists in storage');
}
if (isLicenseApplied()) {
  console.log('License is active');
}
```

### LicenseManager Component API

The `LicenseManager` component provides a complete UI for license management:

**Features:**
- ✅ Status indicator (green/red dot) showing license state
- ✅ Password-masked input field for secure key entry
- ✅ Save/Remove license buttons
- ✅ Success message ("License saved successfully!")
- ✅ Reload notice with "Reload Now" button
- ✅ Error messages for validation failures
- ✅ Light/Dark theme support

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `onLicenseChange` | `(status: LicenseStatus) => void` | - | Callback when license status changes |
| `className` | `string` | - | Custom CSS class name |
| `style` | `React.CSSProperties` | - | Custom inline styles |
| `title` | `string` | `"SciChart License Configuration"` | Title text |
| `placeholder` | `string` | `"Paste your SciChart license key here"` | Input placeholder |
| `successMessageDuration` | `number` | `2000` | Success message display duration (ms) |
| `autoLoad` | `boolean` | `true` | Auto-load license on mount |
| `requireReload` | `boolean` | `true` | Show reload notice after license change |
| `theme` | `'light' \| 'dark'` | `'light'` | Component theme |

### License Management Functions

```tsx
import {
  // Configuration
  configureLicenseEncryption,
  
  // License operations
  setLicense,           // Save and apply license
  loadLicense,          // Load from storage and apply
  removeLicense,        // Remove stored license
  applyLicenseKey,      // Apply directly without storing
  
  // Status checks
  hasStoredLicense,     // Check if license exists in storage
  isLicenseApplied,     // Check if license is active
  getLicenseStatus,     // Get full status object
  validateLicenseFormat, // Validate key format
  
  // Types
  type LicenseStatus,
  type LicenseConfig,
} from '@rm-graph/react';
```

### Complete Example: Full License Setup

```tsx
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { loadLicense } from '@rm-graph/react';
import App from './App';

// Load license on app startup
async function init() {
  await loadLicense();
  ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
}

init();
```

```tsx
// App.tsx
import { useState } from 'react';
import { LicenseManager, Column3DChart } from '@rm-graph/react';
import type { LicenseStatus } from '@rm-graph/react';

function App() {
  const [showLicenseManager, setShowLicenseManager] = useState(false);

  const handleLicenseChange = (status: LicenseStatus) => {
    console.log('License status:', status);
    // Don't auto-close - let user see success message and click Reload Now button
  };

  return (
    <div>
      <header>
        <h1>My Chart App</h1>
        <button onClick={() => setShowLicenseManager(!showLicenseManager)}>
          🔑 License Settings
        </button>
      </header>

      {showLicenseManager && (
        <LicenseManager 
          onLicenseChange={handleLicenseChange}
          title="SciChart License Configuration"
          placeholder="Paste your SciChart license key here"
          theme="light"
        />
      )}

      <Column3DChart
        id="my-chart"
        data={[[45, 1500, 1], [90, 2200, 2]]}
        height={400}
      />
    </div>
  );
}
```

### Troubleshooting

#### Issue: "License key is not valid for this domain"

**Cause**: Your license key is registered for a different domain than where you're accessing the app.

**Solutions**:
1. **Check your registered domains** in SciChart portal
2. **Add the current domain** to your license:
   - For localhost: Add `localhost` and `127.0.0.1`
   - For IP access: Add your IP (e.g., `192.168.140.16`)
   - For production: Add your production domain
3. **Access via registered domain**: Use the exact domain/IP you registered

#### Issue: "Failed to save license" / Crypto errors

**Cause**: Web Crypto API is not available (requires HTTPS or localhost).

**Solutions**:
- **Use HTTPS**: Deploy with SSL certificate
- **Use localhost**: Access via `http://localhost:PORT` instead of IP
- **The system will automatically fallback** to base64 encoding (less secure but functional)

#### Issue: Charts still show watermark after setting license

**Solutions**:
1. **Reload the page** - Existing charts need to be recreated with the license
2. **Check license status**: `getLicenseStatus()` to verify it's applied
3. **Check browser console** for license errors
4. **Verify domain matches** your registered license

#### Issue: License works on localhost but not on IP

**Cause**: SciChart licenses are domain-specific.

**Solutions**:
1. **Register both domains**: Add both `localhost` AND your IP to the license
2. **Use consistent access method**: Always use the same URL format
3. **For development**: Consider using `localhost` only

### Security Best Practices

1. **Domain Locking**: Always register specific domains in SciChart portal
2. **Environment Variables**: Use `.env` files for build-time configuration (never commit keys)
3. **HTTPS**: Use HTTPS in production for secure crypto operations
4. **User Input**: If allowing users to enter licenses, validate format before saving
5. **Storage**: Encrypted storage provides obfuscation, not true security (decryption is client-side)

### License Status Types

```typescript
interface LicenseStatus {
  /** Whether a license is stored in localStorage */
  hasLicense: boolean;
  /** Whether the license was successfully applied to SciChart */
  isApplied: boolean;
  /** Error message if license operation failed */
  error?: string;
}
```

---

See main README for full documentation.