Bin
2025-12-17 d616898802dfe7e5dd648bcf53c6d1f86b6d3642
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import type { Waveform } from "..";
import { rgba } from "../Common/Color";
import type { Visualizer } from "../Visual/Visualizer";
import type { Regions } from "./Regions";
import { Segment, type SegmentGlobalEvents, type SegmentOptions } from "./Segment";
 
export interface RegionGlobalEvents extends SegmentGlobalEvents {
  regionCreated: (region: Region | Segment) => void;
  regionUpdated: (region: Region | Segment) => void;
  regionSelected: (region: Region | Segment, event: MouseEvent) => void;
  regionUpdatedEnd: (region: Region | Segment) => void;
  regionRemoved: (region: Region | Segment) => void;
}
 
export interface RegionOptions extends SegmentOptions {
  labels?: string[];
  color?: string;
}
 
export class Region extends Segment {
  labels: string[] | undefined = undefined;
 
  constructor(options: RegionOptions, waveform: Waveform, visualizer: Visualizer, controller: Regions) {
    super(options, waveform, visualizer, controller);
    this.labels = options.labels ?? this.labels;
    this.color = options.color ? rgba(options.color) : this.color;
  }
 
  get isRegion() {
    return true;
  }
 
  get options() {
    return {
      ...super.options,
      labels: this.labels,
      color: this.color.toString(),
    };
  }
 
  renderLabels(): void {
    if (this.labels?.length && this.controller.showLabels && this.visible) {
      const layer = this.controller.layerGroup;
      const color = this.color;
      const timelineTop = this.timelinePlacement;
      const timelineLayer = this.visualizer.getLayer("timeline");
      const timelineHeight = this.timelineHeight;
      const top = (timelineLayer?.isVisible && timelineTop ? timelineHeight : 0) + 4;
      const labelMeasures = this.labels.map((label) => layer.context.measureText(label));
 
      const allVerticalStackedLabelsHeight = labelMeasures.reduce((accumulator, currentValue) => {
        return accumulator + currentValue.fontBoundingBoxAscent + currentValue.fontBoundingBoxDescent + 2;
      }, 0);
      const start = this.xStart + this.handleWidth + 2;
      const width = labelMeasures[0].width + 10;
      const rangeWidth = this.xEnd - this.xStart - this.handleWidth * 2;
      const adjustedWidth = rangeWidth < width ? rangeWidth : width;
      const selectedAdjustmentWidth = this.selected ? width : adjustedWidth;
 
      layer.fillStyle = `rgba(${color.r + color.r}, ${color.g + color.g}, ${color.b + color.b})`;
      this.selected && layer.roundRect(start, top, selectedAdjustmentWidth, allVerticalStackedLabelsHeight + 5, 4);
      layer.fillStyle = this.selected ? "white" : "black";
      layer.font = "12px Arial";
      this.labels.forEach((label, iterator) => {
        const stackedLabelHeight = (allVerticalStackedLabelsHeight / labelMeasures.length) * (iterator + 1) - 1;
 
        layer.fitText(label, start + 6, top + stackedLabelHeight, selectedAdjustmentWidth - this.handleWidth - 6);
      });
    }
  }
 
  render(): void {
    super.render();
    this.renderLabels();
  }
 
  update(options: Partial<RegionOptions>): void {
    super.update(options);
    this.labels = options.labels ?? this.labels;
    this.color = options.color ? rgba(options.color) : this.color;
  }
 
  toJSON() {
    return {
      start: this.start,
      end: this.end,
      color: this.color.toString(),
      labels: this.labels,
      layerName: this.layerName,
      id: this.id,
    };
  }
}