Rasterex APINPM Package/docs/components/measurement/scale

Measurement Scales

Read and set the active measurement scale for the current Canvas document using the SDK.

Overview

Use viewer.measurements.scale to read and set the active measurement scale for the current Canvas document.

Scale commands are available after the viewer is mounted, Canvas is ready, and a document is open.

  • Apply a scale only after the document is active. Canvas ignores scale updates when no file is loaded.
typescript
import { createViewer } from "@rasterex/viewer";

const viewer = createViewer({
  container: "#viewer"
});

await viewer.mount();
await viewer.ready();

await viewer.documents.open({
  url: "https://files.example.com/floor-plan.pdf",
  displayName: "Floor Plan.pdf"
});
Overview

Read Scale Snapshot

Retrieve the current active scale and all defined scales:

  • get(...) sends getScales and waits for the next matching scalesSnapshot. Canvas scale snapshots are not request-ID correlated, so fileIndex is the only SDK-side filter when Canvas includes it.
typescript
const snapshot = await viewer.measurements.scale.get({
  fileIndex: 0,
  timeoutMs: 10000
});

console.log(snapshot.selectedLabel);
console.log(snapshot.scales);
Read Scale Snapshot

Listen For Scale Changes

Subscribe to real-time scale changes emitted by Canvas:

typescript
const unsubscribe = viewer.measurements.scale.on("snapshot", (snapshot) => {
  console.log(snapshot.selectedLabel);
});

// Clean up:
unsubscribe();
Listen For Scale Changes

Request Without Waiting

Triggers getScales command on Canvas but resolves instantly as void. Use this when you are already listening to the snapshot stream.

typescript
viewer.measurements.scale.request({
  fileIndex: 0
});
Request Without Waiting

Add A Scale

Add a new scale definition and set it as selected:

  • add(...) sends addScale and returns void. Listen for snapshot or call get(...) afterwards if you need visual UI verification.
typescript
viewer.measurements.scale.add({
  fileIndex: 0,
  scale: {
    label: "1 m : 10 m",
    value: "1:10",
    metric: "METRIC",
    metricUnit: "Meter",
    dimPrecision: 2,
    isSelected: true
  }
});
Add A Scale

Scale Shape Reference

Supported scale configuration properties:

  • Always use the exact full unit names above. Abbreviations like m, mm, ft are not valid unit types.
typescript
type MeasurementScale = {
  label: string;
  value: string;
  preciseValue?: number;
  metric: "0" | "1" | "METRIC" | "IMPERIAL" | 0 | 1 | 2;
  metricUnit:
    | "Millimeter"
    | "Centimeter"
    | "Decimeter"
    | "Meter"
    | "Kilometer"
    | "Inch"
    | "Feet"
    | "Yard"
    | "Mile"
    | "Nautical Miles";
  dimPrecision: number;
  isSelected: boolean;
  isGlobal?: boolean;
  pageRanges?: [number, number][];
  source?: string;
  imperialNumerator?: number;
  imperialDenominator?: number;
};
Scale Shape Reference

Page Ranges

Restrict scale configuration to specific pages using 1-based inclusive range tuples:

typescript
viewer.measurements.scale.add({
  scale: {
    label: "Detail Scale",
    value: "1:5",
    metric: "METRIC",
    metricUnit: "Meter",
    dimPrecision: 2,
    isSelected: true,
    pageRanges: [
      [1, 5],
      [9, 9]
    ]
  }
});
Page Ranges

Imperial Fraction Example

Define complex imperial scale ratios with fractional dimensions:

typescript
viewer.measurements.scale.add({
  scale: {
    label: "1/8 inch = 1 foot",
    value: "1:96",
    metric: "IMPERIAL",
    metricUnit: "Feet",
    dimPrecision: 2,
    isSelected: true,
    imperialNumerator: 1,
    imperialDenominator: 8
  }
});
Imperial Fraction Example

Snapshot Shape

Canvas emits scale configuration arrays inside the following wrapper:

typescript
type MeasurementScalesSnapshot = {
  fileIndex?: number;
  fileName?: string;
  selectedLabel?: string;
  scales: MeasurementScale[];
  [key: string]: unknown;
};
Snapshot Shape

Vanilla Example

Complete implementation code setup for Vanilla HTML/JS environments:

<div id="viewer" style="height: 640px"></div>

<div>
  <button type="button" id="apply-scale">Apply 1:10 Scale</button>
  <button type="button" id="read-scale">Read Scale</button>
</div>

<p id="status"></p>
Vanilla Example

React Integration

Complete React Component implementation details:

tsx
import { useEffect, useRef, useState } from "react";
import { createViewer, type RasterexViewer } from "@rasterex/viewer";

export function MeasurementScalePanel() {
  const hostRef = useRef<HTMLDivElement | null>(null);
  const viewerRef = useRef<RasterexViewer | null>(null);
  const [status, setStatus] = useState("Loading viewer");
  const [selectedScale, setSelectedScale] = useState<string | null>(null);

  useEffect(() => {
    let disposed = false;
    const viewer = createViewer({
      container: hostRef.current!
    });
    viewerRef.current = viewer;

    const unsubscribe = viewer.measurements.scale.on("snapshot", (snapshot) => {
      if (!disposed) {
        setSelectedScale(snapshot.selectedLabel ?? null);
        setStatus(`Active scale: ${snapshot.selectedLabel ?? "none"}`);
      }
    });

    async function start() {
      await viewer.mount();
      await viewer.ready();
      await viewer.documents.open({
        url: "https://files.example.com/floor-plan.pdf",
        displayName: "Floor Plan.pdf"
      });
      viewer.measurements.scale.request({ fileIndex: 0 });
      if (!disposed) setStatus("Ready");
    }

    void start().catch((error) => {
      if (!disposed) setStatus(error instanceof Error ? error.message : "Viewer failed");
    });

    return () => {
      disposed = true;
      unsubscribe();
      viewer.destroy();
      viewerRef.current = null;
    };
  }, []);

  function applyScale() {
    viewerRef.current?.measurements.scale.add({
      fileIndex: 0,
      scale: {
        label: "1 m : 10 m",
        value: "1:10",
        metric: "METRIC",
        metricUnit: "Meter",
        dimPrecision: 2,
        isSelected: true
      }
    });
    viewerRef.current?.measurements.scale.request({ fileIndex: 0 });
  }

  async function readScale() {
    try {
      const snapshot = await viewerRef.current?.measurements.scale.get({
        fileIndex: 0,
        timeoutMs: 10000
      });
      setSelectedScale(snapshot?.selectedLabel ?? null);
      setStatus(`Active scale: ${snapshot?.selectedLabel ?? "none"}`);
    } catch (error) {
      setStatus(error instanceof Error ? error.message : "Scale read failed");
    }
  }

  return (
    <section>
      <div ref={hostRef} style={{ height: 640 }} />
      <button type="button" onClick={applyScale}>
        Apply 1:10 Scale
      </button>
      <button type="button" onClick={() => void readScale()}>
        Read Scale
      </button>
      <p>{status}</p>
      <p>Selected scale: {selectedScale ?? "none"}</p>
    </section>
  );
}
React Integration

Next.js Integration

Ensure scale modifications are handled entirely client-side:

tsx
"use client";

import { useEffect, useRef, useState } from "react";
import { createViewer, type RasterexViewer } from "@rasterex/viewer";

export default function MeasurementScalePage() {
  const hostRef = useRef<HTMLDivElement | null>(null);
  const viewerRef = useRef<RasterexViewer | null>(null);
  const [status, setStatus] = useState("Loading viewer");

  useEffect(() => {
    let disposed = false;
    const viewer = createViewer({
      container: hostRef.current!
    });
    viewerRef.current = viewer;

    const unsubscribe = viewer.measurements.scale.on("snapshot", (snapshot) => {
      if (!disposed) setStatus(`Active scale: ${snapshot.selectedLabel ?? "none"}`);
    });

    async function start() {
      await viewer.mount();
      await viewer.ready();
      await viewer.documents.open({
        url: "https://files.example.com/floor-plan.pdf",
        displayName: "Floor Plan.pdf"
      });
      if (!disposed) setStatus("Ready");
    }

    void start().catch((error) => {
      if (!disposed) setStatus(error instanceof Error ? error.message : "Viewer failed");
    });

    return () => {
      disposed = true;
      unsubscribe();
      viewer.destroy();
      viewerRef.current = null;
    };
  }, []);

  function applyScale() {
    viewerRef.current?.measurements.scale.add({
      fileIndex: 0,
      scale: {
        label: "1 m : 10 m",
        value: "1:10",
        metric: "METRIC",
        metricUnit: "Meter",
        dimPrecision: 2,
        isSelected: true
      }
    });
    viewerRef.current?.measurements.scale.request({ fileIndex: 0 });
  }

  return (
    <main>
      <div ref={hostRef} style={{ height: 640 }} />
      <button type="button" onClick={applyScale}>
        Apply 1:10 Scale
      </button>
      <p>{status}</p>
    </main>
  );
}
Next.js Integration

Angular Integration

Angular component integration setup details:

typescript
import {
  AfterViewInit,
  Component,
  ElementRef,
  OnDestroy,
  ViewChild
} from "@angular/core";
import { createViewer, type RasterexViewer } from "@rasterex/viewer";

@Component({
  selector: "app-measurement-scale",
  template: `
    <div #viewerHost class="viewer-host"></div>
    <button type="button" (click)="applyScale()">Apply 1:10 Scale</button>
    <button type="button" (click)="readScale()">Read Scale</button>
    <p>{{ status }}</p>
  `,
  styles: [".viewer-host { height: 640px; }"]
})
export class MeasurementScaleComponent implements AfterViewInit, OnDestroy {
  @ViewChild("viewerHost", { static: true })
  private viewerHost!: ElementRef<HTMLDivElement>;

  protected status = "Loading viewer";
  private viewer: RasterexViewer | null = null;
  private unsubscribeScale: (() => void) | null = null;

  async ngAfterViewInit() {
    const viewer = createViewer({
      container: this.viewerHost.nativeElement
    });
    this.viewer = viewer;

    this.unsubscribeScale = viewer.measurements.scale.on("snapshot", (snapshot) => {
      this.status = `Active scale: ${snapshot.selectedLabel ?? "none"}`;
    });

    try {
      await viewer.mount();
      await viewer.ready();
      await viewer.documents.open({
        url: "https://files.example.com/floor-plan.pdf",
        displayName: "Floor Plan.pdf"
      });
      this.status = "Ready";
    } catch (error) {
      this.status = error instanceof Error ? error.message : "Viewer failed";
    }
  }

  applyScale() {
    this.viewer?.measurements.scale.add({
      fileIndex: 0,
      scale: {
        label: "1 m : 10 m",
        value: "1:10",
        metric: "METRIC",
        metricUnit: "Meter",
        dimPrecision: 2,
        isSelected: true
      }
    });
    this.viewer?.measurements.scale.request({ fileIndex: 0 });
  }

  async readScale() {
    try {
      const snapshot = await this.viewer?.measurements.scale.get({
        fileIndex: 0,
        timeoutMs: 10000
      });
      this.status = `Active scale: ${snapshot?.selectedLabel ?? "none"}`;
    } catch (error) {
      this.status = error instanceof Error ? error.message : "Scale read failed";
    }
  }

  ngOnDestroy() {
    this.unsubscribeScale?.();
    this.viewer?.destroy();
  }
}
Angular Integration

Canvas Broker Mapping

Scale actions map cleanly to Canvas broker command flows:

  • viewer.measurements.scale.request(...) sends getScales.
  • viewer.measurements.scale.get(...) sends getScales and awaits scalesSnapshot.
  • viewer.measurements.scale.add(...) sends addScale.
  • viewer.measurements.scale.on("snapshot", ...) listens for scalesSnapshot.