npm package: @rm-graph/angular
Description: RM-Graphs Angular - Angular components for RM-Graphs charting library
Latest version: 0.1.26

# @rm-graph/angular

RM-Graphs Angular - Angular components for RM-Graphs charting library.

## Installation

```bash
npm install @rm-graph/angular
# or just the core for direct usage
npm install @rm-graph/core
```

> `scichart` is automatically installed as a dependency!

## Usage Options

### Option 1: Using Core Library Directly (Recommended)

The most reliable approach is to use the core library directly in your Angular components:

```typescript
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import {
  Column3DChart,
  createColumn3DChart,
} from '@rm-graph/core';

@Component({
  selector: 'app-chart',
  standalone: true,
  template: `<div #chartContainer style="width: 100%; height: 400px;"></div>`
})
export class ChartComponent implements AfterViewInit, OnDestroy {
  @ViewChild('chartContainer') chartRef!: ElementRef<HTMLDivElement>;
  private chart: Column3DChart | null = null;

  chartData = [[45, 1500, 1], [90, 2200, 2], [180, 1800, 3]];

  async ngAfterViewInit(): Promise<void> {
    this.chart = await createColumn3DChart(this.chartRef.nativeElement, {
      theme: 'dark',
      data: this.chartData,
      xAxisTitle: 'Phase Angle (°)',
      yAxisTitle: 'Amplitude (mVp)',
      zAxisTitle: 'Cycle',
    });
  }

  ngOnDestroy(): void {
    this.chart?.destroy();
  }

  // Update data dynamically
  updateData(newData: number[][]): void {
    this.chart?.setData(newData);
  }

  // Change theme
  setTheme(theme: string): void {
    this.chart?.setOptions({ theme });
  }
}
```

### Option 2: Using Angular Components (Standalone)

Import the pre-built Angular components:

```typescript
import { Component } from '@angular/core';
import { Column3DChartComponent, PRPDChartComponent, TrendsChartComponent } from '@rm-graph/angular';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [Column3DChartComponent, PRPDChartComponent, TrendsChartComponent],
  template: `
    <sui-column3d-chart
      [data]="chartData"
      height="400px"
      theme="dark"
      xAxisTitle="Phase Angle (°)"
      yAxisTitle="Amplitude (mVp)"
      zAxisTitle="Cycle"
      (ready)="onChartReady($event)"
    />
  `
})
export class DashboardComponent {
  chartData = [[45, 1500, 1], [90, 2200, 2], [180, 1800, 3]];

  onChartReady(chart: any) {
    console.log('Chart ready!', chart);
  }
}
```

## Components

### `<rm-column3d-chart>`

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `data` | `number[][]` | `[]` | Chart data as [x, y, z] tuples |
| `width` | `string` | `'100%'` | Chart width |
| `height` | `string` | `'400px'` | Chart height |
| `theme` | `string \| ThemeConfig` | - | Theme |
| `xAxisTitle` | `string` | - | X-axis title |
| `yAxisTitle` | `string` | - | Y-axis title |
| `zAxisTitle` | `string` | - | Z-axis title |

| Output | Type | Description |
|--------|------|-------------|
| `ready` | `EventEmitter<Column3DChart>` | Emitted when chart is ready |
| `chartError` | `EventEmitter<Error>` | Emitted on error |

## Chart Types Available from Core

- `createColumn3DChart` - 3D column charts for phase-resolved data
- `createPRPDChart` - Phase Resolved Partial Discharge heatmaps
- `createSurface3DChart` - 3D surface visualization

---

## PRPDChart

The `PRPDChartComponent` displays Phase Resolved Partial Discharge data as an interactive heatmap.

### Usage

```typescript
import { Component } from '@angular/core';
import { PRPDChartComponent } from '@rm-graph/angular';
import type { PRPDDataPoint, PRPDChartStats } from '@rm-graph/core';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [PRPDChartComponent],
  template: `
    <sui-prpd-chart
      id="my-prpd-chart"
      title="PRPD Analysis"
      [data]="prpdData"
      height="400px"
      [scalingFactor]="1"
      unitOfMeasurement="mVp"
      [maxPeak]="5000"
      [minPeak]="-5000"
      [maxPhaseAngle]="360"
      [showSineWave]="true"
      [showColorPalette]="true"
      [showMaximizeIcon]="true"
      [colorMin]="0"
      [colorMax]="100"
      (statsChange)="onStatsChange($event)"
    />
  `
})
export class DashboardComponent {
  // Data format: [phaseAngle, amplitude, count][]
  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
  ];

  onStatsChange(stats: PRPDChartStats) {
    console.log('Peak:', stats.peakValue, 'Total:', stats.totalCount);
  }
}
```

### 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 |

### Inputs

| Input | 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` | `string` | `'400px'` | Chart height |
| `width` | `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 |
| `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 |

### Outputs

| Output | Type | Description |
|--------|------|-------------|
| `ready` | `EventEmitter<PRPDChart>` | Emitted when chart is initialized |
| `statsChange` | `EventEmitter<PRPDChartStats>` | Emitted when statistics update |
| `resolutionChange` | `EventEmitter<PRPDResolutionLabel>` | Emitted when resolution mode changes |
| `windowingClick` | `EventEmitter<void>` | Emitted when windowing button clicked |
| `yAxisRangeChange` | `EventEmitter<number>` | Emitted when Y-range changes |
| `maximizeToggle` | `EventEmitter<void>` | Emitted 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 `TrendsChartComponent` displays multiple time series with interactive features.

### Usage

```typescript
import { Component } from '@angular/core';
import { TrendsChartComponent, TrendLineData } from '@rm-graph/angular';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [TrendsChartComponent],
  template: `
    <sui-trends-chart
      id="my-trends-chart"
      [data]="trendsData"
      height="500px"
      [showLegend]="true"
      [showResetButton]="true"
      [showPointMarkerToggle]="true"
      [showOverview]="true"
    />
  `
})
export class DashboardComponent {
  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,
    },
  ];
}
```

### 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.

### Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `id` | `string` | auto-generated | Unique ID for the chart |
| `data` | `TrendLineData[]` | `[]` | Array of trend line data |
| `height` | `string` | `'400px'` | 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 |

### 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

---

## Theming

```typescript
import { getThemeManager } from '@rm-graph/core';

// Built-in themes: 'light', 'dark', 'modern', 'midnight'

// Register custom theme
getThemeManager().registerTheme({
  name: 'custom',
  backgroundColor: '#1a1a2e',
  colorPalette: ['#ff6b6b', '#4ecdc4'],
  // ...
});
```

---

## 🔑 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 `LicenseManagerComponent` to your settings page:

```typescript
import { Component } from '@angular/core';
import { LicenseManagerComponent } from '@rm-graph/angular';
import type { LicenseStatus } from '@rm-graph/angular';

@Component({
  selector: 'app-settings',
  standalone: true,
  imports: [LicenseManagerComponent],
  template: `
    <rm-license-manager
      title="SciChart License Configuration"
      placeholder="Paste your SciChart license key here"
      theme="light"
      (licenseChange)="onLicenseChange($event)"
    ></rm-license-manager>
  `
})
export class SettingsComponent {
  onLicenseChange(status: LicenseStatus) {
    if (status.isApplied) {
      console.log('License activated!');
      // Optionally reload to apply to existing charts
      window.location.reload();
    }
  }
}
```

**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)

```typescript
// In your app initialization (main.ts or app.component.ts)
import { loadLicense, setLicense } from '@rm-graph/angular';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

// 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');
  }
  
  // Bootstrap your app
  bootstrapApplication(AppComponent).catch(err => console.error(err));
}

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 or environment configuration
SCICHART_LICENSE_KEY=your-license-key-here
```

```typescript
// main.ts
import { setupSciChartLicense } from '@rm-graph/angular';

// This will automatically read from SCICHART_LICENSE_KEY
setupSciChartLicense({
  fromEnv: true,
  fromLocalStorage: true, // Also check encrypted storage
});

bootstrapApplication(AppComponent).catch(err => console.error(err));
```

#### 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**:

```typescript
import { getLicenseStatus, hasStoredLicense, isLicenseApplied } from '@rm-graph/angular';

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');
}
```

### LicenseManagerComponent API

**Selector:** `<rm-license-manager>`

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `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 |
| `className` | `string` | - | Custom CSS class name |

| Output | Type | Description |
|--------|------|-------------|
| `licenseChange` | `EventEmitter<LicenseStatus>` | Emitted when license status changes |

### License Management Functions

```typescript
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/angular';
```

### Complete Example: Full License Setup

```typescript
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { loadLicense } from '@rm-graph/angular';

async function init() {
  await loadLicense();
  bootstrapApplication(AppComponent).catch(err => console.error(err));
}

init();
```

```typescript
// app.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LicenseManagerComponent, Column3DChartComponent } from '@rm-graph/angular';
import type { LicenseStatus } from '@rm-graph/angular';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, LicenseManagerComponent, Column3DChartComponent],
  template: `
    <header>
      <h1>My Chart App</h1>
      <button (click)="toggleLicenseManager()">
        🔑 License Settings
      </button>
    </header>

    <rm-license-manager
      *ngIf="showLicenseManager"
      title="SciChart License Configuration"
      placeholder="Paste your SciChart license key here"
      theme="light"
      (licenseChange)="onLicenseChange($event)"
    ></rm-license-manager>

    <rm-column3d-chart
      [data]="chartData"
      height="400px"
    ></rm-column3d-chart>
  `
})
export class AppComponent {
  showLicenseManager = false;
  chartData = [[45, 1500, 1], [90, 2200, 2]];

  toggleLicenseManager() {
    this.showLicenseManager = !this.showLicenseManager;
  }

  onLicenseChange(status: LicenseStatus) {
    console.log('License status:', status);
    // Don't auto-close - let user see success message and click Reload
  }
}
```

### 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 environment configuration for build-time setup (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.