@equinor/esv-intersection 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +18 -3
  2. package/dist/components/axis.d.ts +48 -0
  3. package/dist/components/axis.d.ts.map +1 -0
  4. package/dist/components/index.d.ts +2 -0
  5. package/dist/components/index.d.ts.map +1 -0
  6. package/dist/constants.d.ts +1 -0
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/control/ExtendedCurveInterpolator.d.ts +59 -0
  9. package/dist/control/ExtendedCurveInterpolator.d.ts.map +1 -0
  10. package/dist/control/IntersectionReferenceSystem.d.ts +97 -0
  11. package/dist/control/IntersectionReferenceSystem.d.ts.map +1 -0
  12. package/dist/control/LayerManager.d.ts +77 -0
  13. package/dist/control/LayerManager.d.ts.map +1 -0
  14. package/dist/control/MainController.d.ts +155 -0
  15. package/dist/control/MainController.d.ts.map +1 -0
  16. package/dist/control/ZoomPanHandler.d.ts +159 -0
  17. package/dist/control/ZoomPanHandler.d.ts.map +1 -0
  18. package/dist/control/index.d.ts +6 -0
  19. package/dist/control/index.d.ts.map +1 -0
  20. package/dist/control/interfaces.d.ts +38 -0
  21. package/dist/control/interfaces.d.ts.map +1 -0
  22. package/dist/control/overlay.d.ts +21 -0
  23. package/dist/control/overlay.d.ts.map +1 -0
  24. package/dist/datautils/colortable.d.ts +2 -0
  25. package/dist/datautils/colortable.d.ts.map +1 -0
  26. package/dist/datautils/findsample.d.ts +3 -0
  27. package/dist/datautils/findsample.d.ts.map +1 -0
  28. package/dist/datautils/index.d.ts +7 -0
  29. package/dist/datautils/index.d.ts.map +1 -0
  30. package/dist/datautils/interfaces.d.ts +64 -0
  31. package/dist/datautils/interfaces.d.ts.map +1 -0
  32. package/dist/datautils/picks.d.ts +75 -0
  33. package/dist/datautils/picks.d.ts.map +1 -0
  34. package/dist/datautils/schematicShapeGenerator.d.ts +60 -0
  35. package/dist/datautils/schematicShapeGenerator.d.ts.map +1 -0
  36. package/dist/datautils/seismicimage.d.ts +46 -0
  37. package/dist/datautils/seismicimage.d.ts.map +1 -0
  38. package/dist/datautils/surfacedata.d.ts +11 -0
  39. package/dist/datautils/surfacedata.d.ts.map +1 -0
  40. package/dist/datautils/trajectory.d.ts +15 -0
  41. package/dist/datautils/trajectory.d.ts.map +1 -0
  42. package/dist/index.cjs +1 -1
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.mjs +141 -143
  47. package/dist/index.mjs.map +1 -1
  48. package/dist/index.umd.js +1 -1
  49. package/dist/index.umd.js.map +1 -1
  50. package/dist/interfaces.d.ts +1 -0
  51. package/dist/interfaces.d.ts.map +1 -0
  52. package/dist/layers/CalloutCanvasLayer.d.ts +61 -0
  53. package/dist/layers/CalloutCanvasLayer.d.ts.map +1 -0
  54. package/dist/layers/CustomDisplayObjects/ComplexRope.d.ts +22 -0
  55. package/dist/layers/CustomDisplayObjects/ComplexRope.d.ts.map +1 -0
  56. package/dist/layers/CustomDisplayObjects/ComplexRopeGeometry.d.ts +24 -0
  57. package/dist/layers/CustomDisplayObjects/ComplexRopeGeometry.d.ts.map +1 -0
  58. package/dist/layers/CustomDisplayObjects/FixedWidthSimpleRope.d.ts +21 -0
  59. package/dist/layers/CustomDisplayObjects/FixedWidthSimpleRope.d.ts.map +1 -0
  60. package/dist/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.d.ts +27 -0
  61. package/dist/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.d.ts.map +1 -0
  62. package/dist/layers/CustomDisplayObjects/UniformTextureStretchRope.d.ts +18 -0
  63. package/dist/layers/CustomDisplayObjects/UniformTextureStretchRope.d.ts.map +1 -0
  64. package/dist/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.d.ts +25 -0
  65. package/dist/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.d.ts.map +1 -0
  66. package/dist/layers/GeomodelCanvasLayer.d.ts +29 -0
  67. package/dist/layers/GeomodelCanvasLayer.d.ts.map +1 -0
  68. package/dist/layers/GeomodelLabelsLayer.d.ts +50 -0
  69. package/dist/layers/GeomodelLabelsLayer.d.ts.map +1 -0
  70. package/dist/layers/GeomodelLayerV2.d.ts +13 -0
  71. package/dist/layers/GeomodelLayerV2.d.ts.map +1 -0
  72. package/dist/layers/GridLayer.d.ts +30 -0
  73. package/dist/layers/GridLayer.d.ts.map +1 -0
  74. package/dist/layers/ImageCanvasLayer.d.ts +21 -0
  75. package/dist/layers/ImageCanvasLayer.d.ts.map +1 -0
  76. package/dist/layers/ReferenceLineLayer.d.ts +30 -0
  77. package/dist/layers/ReferenceLineLayer.d.ts.map +1 -0
  78. package/dist/layers/SchematicLayer.d.ts +114 -0
  79. package/dist/layers/SchematicLayer.d.ts.map +1 -0
  80. package/dist/layers/SeismicCanvasLayer.d.ts +19 -0
  81. package/dist/layers/SeismicCanvasLayer.d.ts.map +1 -0
  82. package/dist/layers/WellborePathLayer.d.ts +18 -0
  83. package/dist/layers/WellborePathLayer.d.ts.map +1 -0
  84. package/dist/layers/base/CanvasLayer.d.ts +20 -0
  85. package/dist/layers/base/CanvasLayer.d.ts.map +1 -0
  86. package/dist/layers/base/HTMLLayer.d.ts +14 -0
  87. package/dist/layers/base/HTMLLayer.d.ts.map +1 -0
  88. package/dist/layers/base/Layer.d.ts +70 -0
  89. package/dist/layers/base/Layer.d.ts.map +1 -0
  90. package/dist/layers/base/PixiLayer.d.ts +33 -0
  91. package/dist/layers/base/PixiLayer.d.ts.map +1 -0
  92. package/dist/layers/base/SVGLayer.d.ts +14 -0
  93. package/dist/layers/base/SVGLayer.d.ts.map +1 -0
  94. package/dist/layers/base/index.d.ts +6 -0
  95. package/dist/layers/base/index.d.ts.map +1 -0
  96. package/dist/layers/index.d.ts +17 -0
  97. package/dist/layers/index.d.ts.map +1 -0
  98. package/dist/layers/schematicInterfaces.d.ts +210 -0
  99. package/dist/layers/schematicInterfaces.d.ts.map +1 -0
  100. package/dist/utils/arc-length.d.ts +24 -0
  101. package/dist/utils/arc-length.d.ts.map +1 -0
  102. package/dist/utils/binary-search.d.ts +9 -0
  103. package/dist/utils/binary-search.d.ts.map +1 -0
  104. package/dist/utils/color.d.ts +6 -0
  105. package/dist/utils/color.d.ts.map +1 -0
  106. package/dist/utils/index.d.ts +2 -0
  107. package/dist/utils/index.d.ts.map +1 -0
  108. package/dist/utils/root-finder.d.ts +35 -0
  109. package/dist/utils/root-finder.d.ts.map +1 -0
  110. package/dist/utils/text.d.ts +15 -0
  111. package/dist/utils/text.d.ts.map +1 -0
  112. package/dist/utils/vectorUtils.d.ts +16 -0
  113. package/dist/utils/vectorUtils.d.ts.map +1 -0
  114. package/dist/vendor/pixi-dashed-line/index.d.ts +57 -0
  115. package/dist/vendor/pixi-dashed-line/index.d.ts.map +1 -0
  116. package/package.json +28 -21
  117. package/src/.eslintrc.json +5 -0
  118. package/src/components/axis.ts +247 -0
  119. package/src/components/index.ts +1 -0
  120. package/src/control/ExtendedCurveInterpolator.ts +155 -0
  121. package/src/control/IntersectionReferenceSystem.ts +391 -0
  122. package/src/control/LayerManager.ts +294 -0
  123. package/src/control/MainController.ts +296 -0
  124. package/src/control/ZoomPanHandler.ts +436 -0
  125. package/src/control/index.ts +5 -0
  126. package/src/control/interfaces.ts +42 -0
  127. package/src/control/overlay.ts +118 -0
  128. package/src/datautils/colortable.ts +14 -0
  129. package/src/datautils/findsample.ts +64 -0
  130. package/src/datautils/index.ts +6 -0
  131. package/src/datautils/interfaces.ts +68 -0
  132. package/src/datautils/picks.ts +328 -0
  133. package/src/datautils/schematicShapeGenerator.ts +1008 -0
  134. package/src/datautils/seismicimage.ts +180 -0
  135. package/src/datautils/surfacedata.ts +317 -0
  136. package/src/datautils/trajectory.ts +206 -0
  137. package/src/layers/CalloutCanvasLayer.ts +338 -0
  138. package/src/layers/CustomDisplayObjects/ComplexRope.ts +44 -0
  139. package/src/layers/CustomDisplayObjects/ComplexRopeGeometry.ts +184 -0
  140. package/src/layers/CustomDisplayObjects/FixedWidthSimpleRope.ts +41 -0
  141. package/src/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.ts +149 -0
  142. package/src/layers/CustomDisplayObjects/UniformTextureStretchRope.ts +39 -0
  143. package/src/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.ts +174 -0
  144. package/src/layers/GeomodelCanvasLayer.ts +176 -0
  145. package/src/layers/GeomodelLabelsLayer.ts +615 -0
  146. package/src/layers/GeomodelLayerV2.ts +111 -0
  147. package/src/layers/GridLayer.ts +145 -0
  148. package/src/layers/ImageCanvasLayer.ts +55 -0
  149. package/src/layers/ReferenceLineLayer.ts +185 -0
  150. package/src/layers/SchematicLayer.ts +870 -0
  151. package/src/layers/SeismicCanvasLayer.ts +46 -0
  152. package/src/layers/WellborePathLayer.ts +129 -0
  153. package/src/layers/base/CanvasLayer.ts +102 -0
  154. package/src/layers/base/HTMLLayer.ts +70 -0
  155. package/src/layers/base/Layer.ts +217 -0
  156. package/src/layers/base/PixiLayer.ts +190 -0
  157. package/src/layers/base/SVGLayer.ts +63 -0
  158. package/src/layers/base/index.ts +5 -0
  159. package/src/layers/index.ts +16 -0
  160. package/src/layers/schematicInterfaces.ts +472 -0
  161. package/src/tsconfig.json +9 -0
  162. package/src/utils/arc-length.ts +66 -0
  163. package/src/utils/binary-search.ts +26 -0
  164. package/src/utils/color.ts +22 -0
  165. package/src/utils/index.ts +1 -0
  166. package/src/utils/root-finder.ts +78 -0
  167. package/src/utils/text.ts +88 -0
  168. package/src/utils/vectorUtils.ts +67 -0
  169. package/src/vendor/pixi-dashed-line/index.ts +390 -0
@@ -0,0 +1,615 @@
1
+ import Vector2 from '@equinor/videx-vector2';
2
+ import { clamp } from '@equinor/videx-math';
3
+
4
+ import { CanvasLayer } from './base/CanvasLayer';
5
+ import { OnUpdateEvent, OnRescaleEvent, OnMountEvent } from '../interfaces';
6
+ import { SurfaceArea, SurfaceLine, findSampleAtPos, SurfaceData } from '../datautils';
7
+ import { SURFACE_LINE_WIDTH } from '../constants';
8
+ import { LayerOptions } from './base/Layer';
9
+
10
+ const DEFAULT_MARGINS = 18;
11
+ const DEFAULT_MIN_FONT_SIZE = 8;
12
+ const DEFAULT_MAX_FONT_SIZE = 13;
13
+ const DEFAULT_TEXT_COLOR = 'black';
14
+ const DEFAULT_FONT = 'Arial';
15
+ const MAX_FONT_SIZE_IN_WORLD_COORDINATES = 70;
16
+
17
+ export interface GeomodelLayerLabelsOptions<T extends SurfaceData> extends LayerOptions<T> {
18
+ margins?: number;
19
+ minFontSize?: number;
20
+ maxFontSize?: number;
21
+ textColor?: string;
22
+ font?: string;
23
+ }
24
+
25
+ interface SurfaceAreaWithAvgTopDepth extends SurfaceArea {
26
+ avgTopDepth: number;
27
+ }
28
+
29
+ export class GeomodelLabelsLayer<T extends SurfaceData> extends CanvasLayer<T> {
30
+ defaultMargins: number = DEFAULT_MARGINS;
31
+ defaultMinFontSize: number = DEFAULT_MIN_FONT_SIZE;
32
+ defaultMaxFontSize: number = DEFAULT_MAX_FONT_SIZE;
33
+ defaultTextColor: string = DEFAULT_TEXT_COLOR;
34
+ defaultFont: string = DEFAULT_FONT;
35
+
36
+ rescaleEvent: OnRescaleEvent;
37
+ isLabelsOnLeftSide: boolean = true;
38
+ maxFontSizeInWorldCoordinates: number = MAX_FONT_SIZE_IN_WORLD_COORDINATES;
39
+ isXFlipped: boolean = false;
40
+ areasWithAvgTopDepth: SurfaceAreaWithAvgTopDepth[] = null;
41
+
42
+ constructor(id?: string, options?: GeomodelLayerLabelsOptions<T>) {
43
+ super(id, options);
44
+ this.render = this.render.bind(this);
45
+ this.getMarginsInWorldCoordinates = this.getMarginsInWorldCoordinates.bind(this);
46
+ this.getSurfacesAreaEdges = this.getSurfacesAreaEdges.bind(this);
47
+ this.updateXFlipped = this.updateXFlipped.bind(this);
48
+ this.generateSurfacesWithAvgDepth = this.generateSurfacesWithAvgDepth.bind(this);
49
+ }
50
+
51
+ override get options(): GeomodelLayerLabelsOptions<T> {
52
+ return this._options;
53
+ }
54
+
55
+ override setData(data: T): void {
56
+ super.setData(data);
57
+ this.areasWithAvgTopDepth = null;
58
+ }
59
+
60
+ generateSurfacesWithAvgDepth(): void {
61
+ const { areas } = this.data;
62
+ this.areasWithAvgTopDepth = areas.reduce((acc: SurfaceAreaWithAvgTopDepth[], area: SurfaceArea) => {
63
+ // Filter surfaces without label
64
+ if (!area.label) {
65
+ return acc;
66
+ }
67
+ const sumAndCount = area.data.reduce(
68
+ (a: { sum: number; count: number }, d: number[]) => {
69
+ if (d[1] != null) {
70
+ a.sum += d[1];
71
+ a.count++;
72
+ }
73
+ return a;
74
+ },
75
+ {
76
+ sum: 0,
77
+ count: 0,
78
+ },
79
+ );
80
+ if (sumAndCount.count === 0) {
81
+ return acc;
82
+ }
83
+ const avgTopDepth = sumAndCount.sum / sumAndCount.count;
84
+
85
+ acc.push({
86
+ ...area,
87
+ avgTopDepth,
88
+ });
89
+ return acc;
90
+ }, []);
91
+ }
92
+
93
+ override onMount(event: OnMountEvent): void {
94
+ super.onMount(event);
95
+ }
96
+
97
+ override onUpdate(event: OnUpdateEvent<T>): void {
98
+ super.onUpdate(event);
99
+ this.render();
100
+ }
101
+
102
+ override onRescale(event: OnRescaleEvent): void {
103
+ this.rescaleEvent = event;
104
+ this.updateXFlipped();
105
+ this.resetTransform();
106
+ this.render();
107
+ }
108
+
109
+ render(): void {
110
+ if (!this.rescaleEvent) {
111
+ return;
112
+ }
113
+
114
+ requestAnimationFrame(() => {
115
+ this.clearCanvas();
116
+
117
+ if (!this.data) {
118
+ return;
119
+ }
120
+
121
+ if (!this.areasWithAvgTopDepth) {
122
+ this.generateSurfacesWithAvgDepth();
123
+ }
124
+
125
+ this.drawAreaLabels();
126
+ this.drawLineLabels();
127
+ });
128
+ }
129
+
130
+ drawAreaLabels(): void {
131
+ this.areasWithAvgTopDepth.forEach((s: SurfaceAreaWithAvgTopDepth, i: number, array: SurfaceAreaWithAvgTopDepth[]) => {
132
+ const topmostSurfaceNotDrawnYet = array.reduce((acc: SurfaceAreaWithAvgTopDepth | null, v, index): SurfaceAreaWithAvgTopDepth | null => {
133
+ if (index > i) {
134
+ if (acc == null) {
135
+ acc = v;
136
+ } else {
137
+ if (v.avgTopDepth < acc.avgTopDepth) {
138
+ acc = v;
139
+ }
140
+ }
141
+ }
142
+ return acc;
143
+ }, null);
144
+
145
+ this.drawAreaLabel(s, topmostSurfaceNotDrawnYet, array, i);
146
+ });
147
+ }
148
+
149
+ drawLineLabels(): void {
150
+ this.data.lines.filter((surfaceLine: SurfaceLine) => surfaceLine.label).forEach((surfaceLine: SurfaceLine) => this.drawLineLabel(surfaceLine));
151
+ }
152
+
153
+ drawAreaLabel = (surfaceArea: SurfaceArea, nextSurfaceArea: SurfaceArea | null, surfaces: SurfaceArea[], i: number): void => {
154
+ const { data } = surfaceArea;
155
+ const { ctx, maxFontSizeInWorldCoordinates, isXFlipped } = this;
156
+ const { xScale, yScale, xRatio, yRatio, zFactor } = this.rescaleEvent;
157
+ let isLabelsOnLeftSide = this.checkDrawLabelsOnLeftSide();
158
+ const margins = (this.options.margins || this.defaultMargins) * (isXFlipped ? -1 : 1);
159
+ const marginsInWorldCoords = margins / xRatio;
160
+ const minFontSize = this.options.minFontSize || this.defaultMinFontSize;
161
+ const maxFontSize = this.options.maxFontSize || this.defaultMaxFontSize;
162
+
163
+ let fontSizeInWorldCoords = maxFontSize / yRatio;
164
+ if (fontSizeInWorldCoords > maxFontSizeInWorldCoordinates) {
165
+ fontSizeInWorldCoords = maxFontSizeInWorldCoordinates;
166
+ if (fontSizeInWorldCoords * yRatio < minFontSize) {
167
+ fontSizeInWorldCoords = minFontSize / yRatio;
168
+ }
169
+ }
170
+
171
+ const leftEdge = xScale.invert(xScale.range()[0]) + marginsInWorldCoords;
172
+ const rightEdge = xScale.invert(xScale.range()[1]) - marginsInWorldCoords;
173
+ const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges();
174
+
175
+ // Get label metrics
176
+ ctx.save();
177
+ ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`;
178
+ let labelMetrics = ctx.measureText(surfaceArea.label);
179
+ let labelLengthInWorldCoords = labelMetrics.width / xRatio;
180
+
181
+ // Check if label will fit horizontally
182
+ if (isLabelsOnLeftSide) {
183
+ const labelRightEdge = leftEdge + (isXFlipped ? -labelLengthInWorldCoords : labelLengthInWorldCoords);
184
+ if ((!isXFlipped && labelRightEdge > surfaceAreaRightEdge) || (isXFlipped && labelRightEdge < surfaceAreaRightEdge)) {
185
+ isLabelsOnLeftSide = false;
186
+ }
187
+ } else {
188
+ const labelLeftEdge = rightEdge + (isXFlipped ? labelLengthInWorldCoords : -labelLengthInWorldCoords);
189
+ if ((!isXFlipped && labelLeftEdge < surfaceAreaLeftEdge) || (isXFlipped && labelLeftEdge > surfaceAreaLeftEdge)) {
190
+ isLabelsOnLeftSide = true;
191
+ }
192
+ }
193
+
194
+ // Find edge where to draw
195
+ let startPos: number;
196
+ const portionOfLabelLengthUsedForPosCalc = 0.07;
197
+ if (isLabelsOnLeftSide) {
198
+ startPos = isXFlipped ? Math.min(surfaceAreaLeftEdge, leftEdge) : Math.max(surfaceAreaLeftEdge, leftEdge);
199
+ } else {
200
+ startPos = isXFlipped ? Math.max(surfaceAreaRightEdge, rightEdge) : Math.min(surfaceAreaRightEdge, rightEdge);
201
+ }
202
+
203
+ const topEdge = yScale.invert(yScale.range()[0]);
204
+ const bottomEdge = yScale.invert(yScale.range()[1]);
205
+
206
+ // Calculate where to sample points
207
+ const dirSteps = 5;
208
+ const posSteps = 3;
209
+ const posStep =
210
+ portionOfLabelLengthUsedForPosCalc * (labelLengthInWorldCoords / posSteps) * (isLabelsOnLeftSide ? 1 : -1) * (isXFlipped ? -1 : 1);
211
+ const dirStep = (labelLengthInWorldCoords / dirSteps) * (isLabelsOnLeftSide ? 1 : -1) * (isXFlipped ? -1 : 1);
212
+
213
+ // Sample points from top and calculate position
214
+ const topData = data.map((d) => [d[0], d[1]]);
215
+ const topPos = this.calcPos(topData, startPos, posSteps, posStep, topEdge, bottomEdge);
216
+ if (!topPos) {
217
+ return;
218
+ }
219
+
220
+ // Sample points from bottom and calculate position
221
+ const bottomData = data.map((d) => [d[0], d[2]]);
222
+ let bottomPos = this.calcPos(
223
+ bottomData,
224
+ startPos,
225
+ posSteps,
226
+ posStep,
227
+ topEdge,
228
+ bottomEdge,
229
+ nextSurfaceArea ? nextSurfaceArea.data.map((d) => [d[0], d[1]]) : null,
230
+ surfaces,
231
+ i,
232
+ );
233
+ if (!bottomPos) {
234
+ bottomPos = new Vector2(topPos.x, bottomEdge);
235
+ }
236
+
237
+ // Check if there is enough height for label
238
+ const thickness = bottomPos.y - topPos.y;
239
+ if (thickness < fontSizeInWorldCoords) {
240
+ // Check minimum fontsize
241
+ if (thickness * yRatio < minFontSize) {
242
+ return;
243
+ }
244
+ // Use reduced fontsize
245
+ fontSizeInWorldCoords = thickness;
246
+ ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`;
247
+ labelMetrics = ctx.measureText(surfaceArea.label);
248
+ labelLengthInWorldCoords = labelMetrics.width / xRatio;
249
+ }
250
+ // Sample points from top and bottom and calculate direction vector
251
+ const initialDirVec = isLabelsOnLeftSide !== isXFlipped ? Vector2.right : Vector2.left;
252
+ const areaDir = this.calcAreaDir(
253
+ topData,
254
+ bottomData,
255
+ startPos,
256
+ dirSteps,
257
+ dirStep,
258
+ initialDirVec,
259
+ topEdge,
260
+ bottomEdge,
261
+ 0,
262
+ Math.PI / 4,
263
+ 4,
264
+ nextSurfaceArea ? nextSurfaceArea.data.map((d) => [d[0], d[1]]) : null,
265
+ surfaces,
266
+ i,
267
+ );
268
+ const scaledAngle = Math.atan(Math.tan(areaDir) * zFactor);
269
+
270
+ // Draw label
271
+ const textX = startPos;
272
+ const textY = (topPos.y + bottomPos.y) / 2;
273
+ const textAngle = isXFlipped ? -scaledAngle : scaledAngle;
274
+ ctx.textAlign = isLabelsOnLeftSide ? 'left' : 'right';
275
+ ctx.translate(xScale(textX), yScale(textY));
276
+ ctx.rotate(textAngle);
277
+ ctx.fillStyle = this.options.textColor || this.defaultTextColor;
278
+ ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`;
279
+ ctx.textBaseline = 'middle';
280
+ ctx.fillText(surfaceArea.label, 0, 0);
281
+
282
+ ctx.restore();
283
+ };
284
+
285
+ drawLineLabel = (s: SurfaceLine): void => {
286
+ const { ctx, isXFlipped } = this;
287
+ const { xScale, yScale, xRatio, yRatio, zFactor } = this.rescaleEvent;
288
+ const isLabelsOnLeftSide = this.checkDrawLabelsOnLeftSide();
289
+ const marginsInWorldCoords = this.getMarginsInWorldCoordinates();
290
+ const maxFontSize = this.options.maxFontSize || this.defaultMaxFontSize;
291
+
292
+ const fontSizeInWorldCoords = maxFontSize / yRatio;
293
+
294
+ ctx.save();
295
+ ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`;
296
+ const labelMetrics = ctx.measureText(s.label);
297
+ const labelLengthInWorldCoords = labelMetrics.width / xRatio;
298
+
299
+ const leftEdge = xScale.invert(xScale.range()[0]) + marginsInWorldCoords;
300
+ const rightEdge = xScale.invert(xScale.range()[1]) - marginsInWorldCoords;
301
+ const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges();
302
+
303
+ // Find edge where to draw
304
+ let startPos: number;
305
+ const steps = 5;
306
+ if (isLabelsOnLeftSide) {
307
+ startPos = isXFlipped ? Math.max(surfaceAreaRightEdge, rightEdge) : Math.min(surfaceAreaRightEdge, rightEdge);
308
+ } else {
309
+ startPos = isXFlipped ? Math.min(surfaceAreaLeftEdge, leftEdge) : Math.max(surfaceAreaLeftEdge, leftEdge);
310
+ }
311
+
312
+ // Calculate where to sample points
313
+ const step = (labelLengthInWorldCoords / steps) * (isLabelsOnLeftSide ? -1 : 1);
314
+
315
+ // Sample points and calculate position and direction vector
316
+ const { data } = s;
317
+ const pos = this.calcPos(data, startPos, steps, step);
318
+ const dir = this.calcLineDir(data, startPos, steps, step, zFactor, isLabelsOnLeftSide ? Vector2.left : Vector2.right);
319
+ if (!pos || !dir) {
320
+ return;
321
+ }
322
+
323
+ // Calculate position and direction for label
324
+ const textX = startPos;
325
+ const textY = pos.y - SURFACE_LINE_WIDTH - fontSizeInWorldCoords / 2;
326
+ const textDir = Vector2.angleRight(dir) - (isLabelsOnLeftSide ? Math.PI : 0);
327
+
328
+ // Draw label
329
+ ctx.textAlign = isLabelsOnLeftSide ? 'right' : 'left';
330
+ ctx.translate(xScale(textX), yScale(textY));
331
+ ctx.rotate(textDir);
332
+ ctx.fillStyle = this.colorToCSSColor(s.color);
333
+ ctx.textBaseline = 'middle';
334
+ ctx.fillText(s.label, 0, 0);
335
+
336
+ ctx.restore();
337
+ };
338
+
339
+ colorToCSSColor(color: number | string): string {
340
+ if (typeof color === 'string') {
341
+ return color;
342
+ }
343
+
344
+ let hexString = color.toString(16);
345
+ // eslint-disable-next-line no-magic-numbers
346
+ hexString = '000000'.substr(0, 6 - hexString.length) + hexString;
347
+ return `#${hexString}`;
348
+ }
349
+
350
+ calcPos(
351
+ data: number[][],
352
+ offset: number,
353
+ count: number,
354
+ step: number,
355
+ topLimit: number = null,
356
+ bottomLimit: number = null,
357
+ alternativeSurfaceData: number[][] = null,
358
+ surfaces: SurfaceArea[] | null = null,
359
+ currentSurfaceIndex: number = null,
360
+ ): Vector2 {
361
+ const pos = Vector2.zero.mutable;
362
+ let samples = 0;
363
+ for (let i = 0; i < count; i++) {
364
+ const x = offset + i * step;
365
+ const y = findSampleAtPos(data, x, topLimit, bottomLimit);
366
+ if (y) {
367
+ const alternativeY = this.getAlternativeYValueIfAvailable(x, topLimit, bottomLimit, alternativeSurfaceData, surfaces, currentSurfaceIndex);
368
+ // Use topmost of value from current surface and alternative surface
369
+ const usedY = alternativeY ? Math.min(y, alternativeY) : y;
370
+ pos.add(x, usedY);
371
+ samples++;
372
+ }
373
+ }
374
+
375
+ if (samples === 0) {
376
+ return null;
377
+ }
378
+
379
+ return Vector2.divide(pos, samples);
380
+ }
381
+
382
+ getAlternativeYValueIfAvailable(
383
+ x: number,
384
+ topLimit: number,
385
+ bottomLimit: number,
386
+ alternativeSurfaceData: number[][],
387
+ surfaces: SurfaceArea[] | null,
388
+ currentSurfaceIndex: number,
389
+ ): number {
390
+ if (!alternativeSurfaceData) {
391
+ return null;
392
+ }
393
+ // Find sample from passed in surface data
394
+ let altY = findSampleAtPos(alternativeSurfaceData, x, topLimit, bottomLimit);
395
+ if (altY == null && surfaces && currentSurfaceIndex != null) {
396
+ //Find topmost surface after current which gives us data
397
+ let si = currentSurfaceIndex + 1;
398
+ while (altY == null && si < surfaces.length) {
399
+ const altSurface = surfaces[si++];
400
+ altY = findSampleAtPos(
401
+ altSurface.data.map((d: number[]) => [d[0], d[1]]),
402
+ x,
403
+ topLimit,
404
+ bottomLimit,
405
+ );
406
+ }
407
+ }
408
+ return altY;
409
+ }
410
+
411
+ calcLineDir(
412
+ data: number[][],
413
+ offset: number,
414
+ count: number,
415
+ step: number,
416
+ zFactor: number,
417
+ initalVector: Vector2 = Vector2.left,
418
+ topLimit: number = null,
419
+ bottomLimit: number = null,
420
+ ): Vector2 {
421
+ const dir = initalVector.mutable;
422
+
423
+ const startY = findSampleAtPos(data, offset, topLimit, bottomLimit);
424
+ if (startY === null) {
425
+ return dir;
426
+ }
427
+
428
+ const vecAtEnd = new Vector2(offset, startY * zFactor);
429
+ const tmpVec = Vector2.zero.mutable;
430
+ for (let i = 1; i <= count; i++) {
431
+ const x = offset + i * step;
432
+ const y = findSampleAtPos(data, offset, topLimit, bottomLimit);
433
+ if (y !== null) {
434
+ tmpVec.set(x, y * zFactor);
435
+ tmpVec.sub(vecAtEnd);
436
+ dir.add(tmpVec);
437
+ }
438
+ }
439
+
440
+ return dir;
441
+ }
442
+
443
+ calcAreaDir(
444
+ top: number[][],
445
+ bottom: number[][],
446
+ offset: number,
447
+ count: number,
448
+ step: number,
449
+ initalVector: Vector2 = Vector2.left,
450
+ topLimit: number = null,
451
+ bottomLimit: number = null,
452
+ minReductionAngle: number = 0,
453
+ maxReductionAngle: number = Math.PI / 4,
454
+ angleReductionExponent: number = 4,
455
+ alternativeSurfaceBottomData: number[][] = null,
456
+ surfaces: SurfaceArea[] | null = null,
457
+ currentSurfaceIndex: number = null,
458
+ ): number {
459
+ const angles: number[] = [];
460
+ const tmpVec = Vector2.zero.mutable;
461
+ let vecAtEnd: Vector2;
462
+ for (let i = 0; i <= count; i++) {
463
+ const x = offset + i * step;
464
+ const topY = findSampleAtPos(top, x, topLimit, bottomLimit);
465
+ const bottomY = findSampleAtPos(bottom, x, topLimit, bottomLimit) || bottomLimit;
466
+ // Find position of next surface in case it's higher than current base
467
+ const alternativeBottomY = this.getAlternativeYValueIfAvailable(
468
+ x,
469
+ topLimit,
470
+ bottomLimit,
471
+ alternativeSurfaceBottomData,
472
+ surfaces,
473
+ currentSurfaceIndex,
474
+ );
475
+ // Use topmost of value from current surface and alternative surface
476
+ const usedBottomY = alternativeBottomY ? Math.min(bottomY, alternativeBottomY) : bottomY;
477
+ if (i === 0) {
478
+ if (topY === null) {
479
+ return Vector2.angleRight(initalVector);
480
+ }
481
+ const startY = (topY + usedBottomY) / 2;
482
+ vecAtEnd = new Vector2(offset, startY);
483
+ } else {
484
+ if (topY !== null) {
485
+ tmpVec.set(x, (topY + usedBottomY) / 2);
486
+ tmpVec.sub(vecAtEnd);
487
+
488
+ angles.push(Vector2.angleRight(tmpVec));
489
+ } else {
490
+ angles.push(Vector2.angleRight(initalVector));
491
+ }
492
+ }
493
+ }
494
+
495
+ const refAngle = angles[0];
496
+ const offsetAngles = angles.map((d: number) => d - refAngle);
497
+ let factors = 0;
498
+ const offsetSum = offsetAngles.reduce((acc: number, v: number) => {
499
+ const ratio = (Math.abs(v) - minReductionAngle) / maxReductionAngle;
500
+ const factor = Math.pow(1 - clamp(ratio, 0, 1), angleReductionExponent);
501
+ factors += factor;
502
+ return acc + v * factor;
503
+ }, 0);
504
+ const angle = offsetSum / factors + refAngle;
505
+ return angle;
506
+ }
507
+
508
+ updateXFlipped(): void {
509
+ const { xBounds } = this.rescaleEvent;
510
+ this.isXFlipped = xBounds[0] > xBounds[1];
511
+ }
512
+
513
+ getMarginsInWorldCoordinates(): number {
514
+ const { xRatio } = this.rescaleEvent;
515
+ const margins = (this.options.margins || this.defaultMargins) * (this.isXFlipped ? -1 : 1);
516
+ const marginsInWorldCoords = margins / xRatio;
517
+ return marginsInWorldCoords;
518
+ }
519
+
520
+ getSurfacesAreaEdges(): number[] {
521
+ const endPoints = this.data.areas.reduce((acc, area) => {
522
+ const { data } = area;
523
+ const firstValidPoint = data.find((d: number[]) => d[1] != null);
524
+ if (firstValidPoint) {
525
+ acc.push(firstValidPoint[0]);
526
+ }
527
+ // TODO: Use findLast() when TypeScript stops complaining about it
528
+ for (let i = data.length - 1; i >= 0; i--) {
529
+ if (data[i][1] != null) {
530
+ acc.push(data[i][0]);
531
+ break;
532
+ }
533
+ }
534
+
535
+ return acc;
536
+ }, []);
537
+ endPoints.push(
538
+ ...this.data.lines.reduce((acc, line) => {
539
+ const { data } = line;
540
+ const firstValidPoint = data.find((d: number[]) => d[1] != null);
541
+ if (firstValidPoint) {
542
+ acc.push(firstValidPoint[0]);
543
+ }
544
+ // TODO: Use findLast() when TypeScript stops complaining about it
545
+ for (let i = data.length - 1; i >= 0; i--) {
546
+ if (data[i][1] != null) {
547
+ acc.push(data[i][0]);
548
+ break;
549
+ }
550
+ }
551
+ return acc;
552
+ }, []),
553
+ );
554
+
555
+ const minX = Math.min(...endPoints);
556
+ const maxX = Math.max(...endPoints);
557
+ const marginsInWorldCoords = this.getMarginsInWorldCoordinates();
558
+ const { isXFlipped } = this;
559
+ const surfaceAreaLeftEdge = isXFlipped ? maxX + marginsInWorldCoords : minX + marginsInWorldCoords;
560
+ const surfaceAreaRightEdge = isXFlipped ? minX - marginsInWorldCoords : maxX - marginsInWorldCoords;
561
+ return [surfaceAreaLeftEdge, surfaceAreaRightEdge];
562
+ }
563
+
564
+ checkDrawLabelsOnLeftSide(): boolean {
565
+ const { referenceSystem, isXFlipped } = this;
566
+ if (!referenceSystem) {
567
+ return true;
568
+ }
569
+
570
+ const { xScale, yScale, xRatio } = this.rescaleEvent;
571
+ const t = 200; // TODO: Use actual size of largest label or average size of all
572
+
573
+ const [dx1, dx2] = xScale.domain();
574
+ const [dy1, dy2] = yScale.domain();
575
+
576
+ let top = referenceSystem.interpolators.curtain.getIntersects(dy1, 1, 0) as number[][];
577
+ if (top.length === 0) {
578
+ top = [referenceSystem.interpolators.curtain.getPointAt(0.0) as number[]];
579
+ }
580
+ let bottom = referenceSystem.interpolators.curtain.getIntersects(dy2, 1, 0) as number[][];
581
+ if (bottom.length === 0) {
582
+ bottom = [referenceSystem.interpolators.curtain.getPointAt(1.0) as number[]];
583
+ }
584
+
585
+ const maxX = Math.max(top[0][0], bottom[0][0]);
586
+ const minX = Math.min(top[0][0], bottom[0][0]);
587
+
588
+ const wbBBox = {
589
+ left: isXFlipped ? maxX : minX,
590
+ right: isXFlipped ? minX : maxX,
591
+ };
592
+
593
+ const margin = this.getMarginsInWorldCoordinates();
594
+ const screenLeftEdge = dx1 + margin;
595
+ const screenRightEdge = dx2 - margin;
596
+
597
+ const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges();
598
+
599
+ const leftLimit = isXFlipped ? Math.min(screenLeftEdge, surfaceAreaLeftEdge) : Math.max(screenLeftEdge, surfaceAreaLeftEdge);
600
+ const rightLimit = isXFlipped ? Math.max(screenRightEdge, surfaceAreaRightEdge) : Math.min(screenRightEdge, surfaceAreaRightEdge);
601
+
602
+ const spaceOnLeftSide = Math.max(isXFlipped ? leftLimit - wbBBox.left : wbBBox.left - leftLimit, 0);
603
+ const spaceOnRightSide = Math.max(isXFlipped ? wbBBox.right - rightLimit : rightLimit - wbBBox.right, 0);
604
+
605
+ const spaceOnLeftSideInScreenCoordinates = spaceOnLeftSide * xRatio;
606
+ const spaceOnRightSideInScreenCoordinates = spaceOnRightSide * xRatio;
607
+ const isLabelsOnLeftSide =
608
+ spaceOnLeftSide > spaceOnRightSide ||
609
+ spaceOnLeftSideInScreenCoordinates > t ||
610
+ (spaceOnLeftSideInScreenCoordinates < t && spaceOnRightSideInScreenCoordinates < t && isXFlipped) ||
611
+ bottom[0][1] < dy1;
612
+
613
+ return isLabelsOnLeftSide;
614
+ }
615
+ }