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