@tetacom/ng-components 1.6.29 → 1.7.1
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/component/chart-3d/chart3d/chart3d.component.d.ts +25 -2
- package/component/chart-3d/model/base-3d-point.d.ts +1 -0
- package/component/chart-3d/model/chart-3d-options.d.ts +1 -0
- package/component/chart-3d/model/chart3d-tooltip.d.ts +22 -0
- package/component/chart-3d/model/series-3d.d.ts +2 -0
- package/component/chart-3d/services/chart3d-tooltip.service.d.ts +15 -0
- package/fesm2022/tetacom-ng-components.mjs +348 -10
- package/fesm2022/tetacom-ng-components.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,22 +1,38 @@
|
|
|
1
|
-
import { AfterViewInit, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
|
1
|
+
import { AfterViewInit, ChangeDetectorRef, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
|
2
2
|
import { Chart3dOptions } from '../model/chart-3d-options';
|
|
3
3
|
import { ThemeSwitchService } from '../../theme-switch/theme-switch.service';
|
|
4
|
+
import { Chart3dTooltipService } from '../services/chart3d-tooltip.service';
|
|
4
5
|
import * as i0 from "@angular/core";
|
|
5
6
|
export declare class Chart3dComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
6
7
|
private _elementRef;
|
|
7
8
|
private _themeService;
|
|
9
|
+
private _cdr;
|
|
10
|
+
private _tooltipService;
|
|
8
11
|
canvasRef: ElementRef;
|
|
12
|
+
tooltipRef: ElementRef;
|
|
9
13
|
private _scene;
|
|
10
14
|
private _camera;
|
|
11
15
|
private _renderer;
|
|
12
16
|
private _controls;
|
|
13
17
|
private _obs;
|
|
14
18
|
private _config;
|
|
19
|
+
private _animationFrameId;
|
|
15
20
|
private SIDE_SIZE;
|
|
16
21
|
private gridColor;
|
|
17
22
|
private axesColor;
|
|
18
23
|
private _alive;
|
|
19
|
-
|
|
24
|
+
tooltipVisible: boolean;
|
|
25
|
+
tooltipX: number;
|
|
26
|
+
tooltipY: number;
|
|
27
|
+
tooltipWellName: string;
|
|
28
|
+
tooltipMD: string;
|
|
29
|
+
tooltipTVD: string;
|
|
30
|
+
tooltipUnit: string;
|
|
31
|
+
private _wellMeshes;
|
|
32
|
+
private _tooltipMarker;
|
|
33
|
+
private _mouseMoveHandler;
|
|
34
|
+
private _mouseLeaveHandler;
|
|
35
|
+
constructor(_elementRef: ElementRef, _themeService: ThemeSwitchService, _cdr: ChangeDetectorRef, _tooltipService: Chart3dTooltipService);
|
|
20
36
|
set config(config: Chart3dOptions);
|
|
21
37
|
get config(): Chart3dOptions;
|
|
22
38
|
private get canvas();
|
|
@@ -33,6 +49,13 @@ export declare class Chart3dComponent implements OnInit, AfterViewInit, OnDestro
|
|
|
33
49
|
private makeSprite;
|
|
34
50
|
private drawTicks;
|
|
35
51
|
private getScales;
|
|
52
|
+
private addMouseMoveListener;
|
|
53
|
+
private handleMouseMove;
|
|
54
|
+
private showTooltip;
|
|
55
|
+
private hideTooltip;
|
|
56
|
+
private removeMouseListeners;
|
|
57
|
+
private disposeThreeResources;
|
|
58
|
+
private updateTooltipMarker;
|
|
36
59
|
static ɵfac: i0.ɵɵFactoryDeclaration<Chart3dComponent, never>;
|
|
37
60
|
static ɵcmp: i0.ɵɵComponentDeclaration<Chart3dComponent, "teta-chart3d", never, { "config": { "alias": "config"; "required": false; }; }, {}, never, never, true, never>;
|
|
38
61
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { Series3d } from './series-3d';
|
|
3
|
+
import { Base3dPoint } from './base-3d-point';
|
|
4
|
+
export interface WellMeshData {
|
|
5
|
+
mesh: THREE.Mesh;
|
|
6
|
+
series: Series3d<Base3dPoint>;
|
|
7
|
+
scale: {
|
|
8
|
+
x: any;
|
|
9
|
+
y: any;
|
|
10
|
+
z: any;
|
|
11
|
+
};
|
|
12
|
+
curve: THREE.CatmullRomCurve3;
|
|
13
|
+
points: Base3dPoint[];
|
|
14
|
+
}
|
|
15
|
+
export interface IntersectionResult {
|
|
16
|
+
wellData: WellMeshData;
|
|
17
|
+
intersectionPoint: THREE.Vector3;
|
|
18
|
+
closestCurvePoint: THREE.Vector3;
|
|
19
|
+
closestDataPoint: Base3dPoint;
|
|
20
|
+
md: number;
|
|
21
|
+
tvd: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { WellMeshData, IntersectionResult } from '../model/chart3d-tooltip';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare class Chart3dTooltipService {
|
|
5
|
+
private _raycaster;
|
|
6
|
+
private _mouse;
|
|
7
|
+
findClosestPointOnCurveLocal(point: THREE.Vector3, curve: THREE.CatmullRomCurve3): THREE.Vector3;
|
|
8
|
+
constructor();
|
|
9
|
+
findIntersection(event: MouseEvent, canvas: HTMLCanvasElement, camera: THREE.Camera, wellMeshes: WellMeshData[]): IntersectionResult | null;
|
|
10
|
+
private findClosestPointOnCurveToRay;
|
|
11
|
+
private findClosestDataPoint;
|
|
12
|
+
private calculateTVD;
|
|
13
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<Chart3dTooltipService, never>;
|
|
14
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<Chart3dTooltipService>;
|
|
15
|
+
}
|
|
@@ -451,6 +451,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
|
|
|
451
451
|
class Chart3dOptions {
|
|
452
452
|
constructor(options) {
|
|
453
453
|
this.axes = { ...options?.axes };
|
|
454
|
+
this.unit = options?.unit;
|
|
454
455
|
this.series = options?.series || [];
|
|
455
456
|
}
|
|
456
457
|
}
|
|
@@ -486,12 +487,188 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
|
|
|
486
487
|
}]
|
|
487
488
|
}], ctorParameters: () => [] });
|
|
488
489
|
|
|
490
|
+
class Chart3dTooltipService {
|
|
491
|
+
// Kept for marker/orientation logic in the component (not used for MD now)
|
|
492
|
+
findClosestPointOnCurveLocal(point, curve) {
|
|
493
|
+
let closestPoint = new THREE.Vector3();
|
|
494
|
+
let minDistance = Infinity;
|
|
495
|
+
const samples = 200;
|
|
496
|
+
for (let i = 0; i <= samples; i++) {
|
|
497
|
+
const t = i / samples;
|
|
498
|
+
const curvePoint = curve.getPoint(t);
|
|
499
|
+
const distance = point.distanceTo(curvePoint);
|
|
500
|
+
if (distance < minDistance) {
|
|
501
|
+
minDistance = distance;
|
|
502
|
+
closestPoint = curvePoint;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return closestPoint;
|
|
506
|
+
}
|
|
507
|
+
constructor() {
|
|
508
|
+
this._raycaster = new THREE.Raycaster();
|
|
509
|
+
this._mouse = new THREE.Vector2();
|
|
510
|
+
this._raycaster.params.Line.threshold = 2;
|
|
511
|
+
}
|
|
512
|
+
findIntersection(event, canvas, camera, wellMeshes) {
|
|
513
|
+
// 1. Convert mouse coordinates
|
|
514
|
+
const rect = canvas.getBoundingClientRect();
|
|
515
|
+
this._mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
516
|
+
this._mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
517
|
+
// 2. Raycasting
|
|
518
|
+
this._raycaster.setFromCamera(this._mouse, camera);
|
|
519
|
+
const meshes = wellMeshes.map((_) => _.mesh);
|
|
520
|
+
const intersects = this._raycaster.intersectObjects(meshes, false);
|
|
521
|
+
if (intersects.length === 0) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
// Prefer the closest intersection by distance (Three usually returns sorted,
|
|
525
|
+
// but keeping it explicit avoids surprises across versions)
|
|
526
|
+
intersects.sort((a, b) => a.distance - b.distance);
|
|
527
|
+
// 3. Find well data
|
|
528
|
+
const intersect = intersects[0];
|
|
529
|
+
const wellData = wellMeshes.find((_) => _.mesh === intersect.object);
|
|
530
|
+
if (!wellData) {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
// 4. Calculate closest point on the trajectory curve using the mouse ray.
|
|
534
|
+
// Using the curve parameter t makes the tooltip smooth even for very low-point trajectories (e.g. 3 points).
|
|
535
|
+
const hit = this.findClosestPointOnCurveToRay(this._raycaster.ray, wellData.curve, wellData.points);
|
|
536
|
+
const closestPoint = hit.point;
|
|
537
|
+
const intersectPoint = hit.hitPoint ?? intersect.point;
|
|
538
|
+
const intersectDistance = hit.hitDistance ?? intersect.distance;
|
|
539
|
+
// Override intersection with a stable point derived from ray→trajectory projection
|
|
540
|
+
intersect.point = intersectPoint;
|
|
541
|
+
intersect.distance = intersectDistance;
|
|
542
|
+
// 5. Find closest data point (useful for other fields)
|
|
543
|
+
const closestDataPoint = this.findClosestDataPoint(closestPoint, wellData.points, wellData.scale);
|
|
544
|
+
// 6. Calculate TVD and MD
|
|
545
|
+
const tvd = this.calculateTVD(closestPoint, wellData.scale.y);
|
|
546
|
+
const md = hit.md; // smoothly interpolated along curve parameter t
|
|
547
|
+
return {
|
|
548
|
+
wellData,
|
|
549
|
+
intersectionPoint: intersect.point,
|
|
550
|
+
closestCurvePoint: closestPoint,
|
|
551
|
+
closestDataPoint,
|
|
552
|
+
md,
|
|
553
|
+
tvd,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
findClosestPointOnCurveToRay(ray, curve, orderedDataPoints) {
|
|
557
|
+
// Compute closest point on the rendered curve to the mouse ray.
|
|
558
|
+
// We sample t densely (fast enough) and then refine with a few iterations.
|
|
559
|
+
// MD is interpolated along the dataPoints md range using the same t.
|
|
560
|
+
if (!orderedDataPoints.length) {
|
|
561
|
+
return { point: new THREE.Vector3(), md: 0 };
|
|
562
|
+
}
|
|
563
|
+
if (orderedDataPoints.length === 1) {
|
|
564
|
+
// Degenerate curve
|
|
565
|
+
const p = curve.getPoint(0);
|
|
566
|
+
const s = Math.max(0, ray.direction.dot(new THREE.Vector3().subVectors(p, ray.origin)));
|
|
567
|
+
const hitPoint = ray.at(s, new THREE.Vector3());
|
|
568
|
+
return {
|
|
569
|
+
point: p,
|
|
570
|
+
md: orderedDataPoints[0].md ?? 0,
|
|
571
|
+
hitPoint,
|
|
572
|
+
hitDistance: s,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const mdMin = orderedDataPoints[0].md ?? 0;
|
|
576
|
+
const mdMax = orderedDataPoints[orderedDataPoints.length - 1].md ?? mdMin;
|
|
577
|
+
const d = ray.direction.clone().normalize();
|
|
578
|
+
// IMPORTANT: use getPointAt(u) (u = arc-length parameter), not getPoint(t).
|
|
579
|
+
// CatmullRomCurve3 "t" is not proportional to arc length and causes MD to change non-uniformly,
|
|
580
|
+
// especially noticeable when there are very few control points.
|
|
581
|
+
const distSqAt = (u) => {
|
|
582
|
+
const p = curve.getPointAt(u);
|
|
583
|
+
const s = Math.max(0, d.dot(new THREE.Vector3().subVectors(p, ray.origin)));
|
|
584
|
+
const r = ray.at(s, new THREE.Vector3());
|
|
585
|
+
return { distSq: r.distanceToSquared(p), p, s, r };
|
|
586
|
+
};
|
|
587
|
+
// 1) coarse scan over arc-length parameter u
|
|
588
|
+
const samples = 400;
|
|
589
|
+
let bestU = 0;
|
|
590
|
+
let best = distSqAt(0);
|
|
591
|
+
for (let i = 1; i <= samples; i++) {
|
|
592
|
+
const u = i / samples;
|
|
593
|
+
const cur = distSqAt(u);
|
|
594
|
+
if (cur.distSq < best.distSq) {
|
|
595
|
+
best = cur;
|
|
596
|
+
bestU = u;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// 2) refine around bestU
|
|
600
|
+
let left = Math.max(0, bestU - 1 / samples);
|
|
601
|
+
let right = Math.min(1, bestU + 1 / samples);
|
|
602
|
+
for (let iter = 0; iter < 10; iter++) {
|
|
603
|
+
const u1 = left + (right - left) / 3;
|
|
604
|
+
const u2 = right - (right - left) / 3;
|
|
605
|
+
const f1 = distSqAt(u1);
|
|
606
|
+
const f2 = distSqAt(u2);
|
|
607
|
+
if (f1.distSq < f2.distSq) {
|
|
608
|
+
right = u2;
|
|
609
|
+
best = f1;
|
|
610
|
+
bestU = u1;
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
left = u1;
|
|
614
|
+
best = f2;
|
|
615
|
+
bestU = u2;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
// Final eval at bestU
|
|
619
|
+
const final = distSqAt(bestU);
|
|
620
|
+
const md = mdMin + (mdMax - mdMin) * bestU;
|
|
621
|
+
return {
|
|
622
|
+
point: final.p,
|
|
623
|
+
md,
|
|
624
|
+
hitPoint: final.r,
|
|
625
|
+
hitDistance: final.s,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
findClosestDataPoint(curvePoint, dataPoints, scale) {
|
|
629
|
+
let closestPoint = dataPoints[0];
|
|
630
|
+
let minDistance = Infinity;
|
|
631
|
+
for (let i = 0; i < dataPoints.length; i++) {
|
|
632
|
+
const point = dataPoints[i];
|
|
633
|
+
// Convert data point to 3D space using scales
|
|
634
|
+
const scaledPoint = new THREE.Vector3(scale.x(point.x), scale.y(point.y), scale.z(point.z));
|
|
635
|
+
const distance = curvePoint.distanceTo(scaledPoint);
|
|
636
|
+
if (distance < minDistance) {
|
|
637
|
+
minDistance = distance;
|
|
638
|
+
closestPoint = point;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return closestPoint;
|
|
642
|
+
}
|
|
643
|
+
calculateTVD(point, yScale) {
|
|
644
|
+
return yScale.invert(point.y);
|
|
645
|
+
}
|
|
646
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dTooltipService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
647
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dTooltipService, providedIn: 'root' }); }
|
|
648
|
+
}
|
|
649
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dTooltipService, decorators: [{
|
|
650
|
+
type: Injectable,
|
|
651
|
+
args: [{ providedIn: 'root' }]
|
|
652
|
+
}], ctorParameters: () => [] });
|
|
653
|
+
|
|
489
654
|
class Chart3dComponent {
|
|
490
|
-
constructor(_elementRef, _themeService) {
|
|
655
|
+
constructor(_elementRef, _themeService, _cdr, _tooltipService) {
|
|
491
656
|
this._elementRef = _elementRef;
|
|
492
657
|
this._themeService = _themeService;
|
|
658
|
+
this._cdr = _cdr;
|
|
659
|
+
this._tooltipService = _tooltipService;
|
|
493
660
|
this.SIDE_SIZE = 100;
|
|
494
661
|
this._alive = true;
|
|
662
|
+
// Tooltip properties
|
|
663
|
+
this.tooltipVisible = true;
|
|
664
|
+
this.tooltipX = 0;
|
|
665
|
+
this.tooltipY = 0;
|
|
666
|
+
this.tooltipWellName = '';
|
|
667
|
+
this.tooltipMD = '';
|
|
668
|
+
this.tooltipTVD = '';
|
|
669
|
+
this.tooltipUnit = 'm';
|
|
670
|
+
// Raycasting
|
|
671
|
+
this._wellMeshes = [];
|
|
495
672
|
}
|
|
496
673
|
set config(config) {
|
|
497
674
|
if (config) {
|
|
@@ -518,11 +695,14 @@ class Chart3dComponent {
|
|
|
518
695
|
this.addResizeObserver();
|
|
519
696
|
this.createScene();
|
|
520
697
|
this.startRenderingLoop();
|
|
698
|
+
this.addMouseMoveListener();
|
|
521
699
|
this.init();
|
|
522
700
|
}
|
|
523
701
|
ngOnDestroy() {
|
|
524
702
|
this._alive = false;
|
|
525
703
|
this.removeResizeObserver();
|
|
704
|
+
this.removeMouseListeners();
|
|
705
|
+
this.disposeThreeResources();
|
|
526
706
|
}
|
|
527
707
|
init() {
|
|
528
708
|
if (!this._scene) {
|
|
@@ -531,19 +711,30 @@ class Chart3dComponent {
|
|
|
531
711
|
while (this._scene.children.length > 0) {
|
|
532
712
|
this._scene.remove(this._scene.children[0]);
|
|
533
713
|
}
|
|
714
|
+
this._wellMeshes = [];
|
|
534
715
|
const { x, y, z } = this.getScales(this._config.series);
|
|
535
716
|
this.config.series.forEach((data, idx) => {
|
|
536
717
|
if (!data.points?.length) {
|
|
537
718
|
return;
|
|
538
719
|
}
|
|
539
|
-
const
|
|
720
|
+
const orderedDataPoints = [...data.points].sort((a, b) => (a.md ?? 0) - (b.md ?? 0));
|
|
721
|
+
const points = orderedDataPoints.map((_) => new THREE.Vector3(x(_.x), y(_.y), z(_.z)));
|
|
540
722
|
const color = d3.scaleOrdinal(d3.schemeTableau10);
|
|
541
|
-
const material = new THREE.
|
|
723
|
+
const material = new THREE.MeshBasicMaterial({
|
|
542
724
|
color: data?.color ?? color(idx.toString()),
|
|
543
725
|
});
|
|
544
|
-
const
|
|
545
|
-
|
|
726
|
+
const curve = new THREE.CatmullRomCurve3(points);
|
|
727
|
+
const tubeGeometry = new THREE.TubeGeometry(curve, 1024, 0.5, 20, false);
|
|
728
|
+
let tube = new THREE.Mesh(tubeGeometry, material);
|
|
546
729
|
this._scene.add(tube);
|
|
730
|
+
// Store well mesh with its data for raycasting
|
|
731
|
+
this._wellMeshes.push({
|
|
732
|
+
mesh: tube,
|
|
733
|
+
series: data,
|
|
734
|
+
scale: { x, y, z },
|
|
735
|
+
curve: curve,
|
|
736
|
+
points: orderedDataPoints,
|
|
737
|
+
});
|
|
547
738
|
});
|
|
548
739
|
const circles = x.ticks(this.SIDE_SIZE / 10);
|
|
549
740
|
const material = new THREE.LineBasicMaterial({ color: this.axesColor });
|
|
@@ -605,7 +796,7 @@ class Chart3dComponent {
|
|
|
605
796
|
preserveDrawingBuffer: true,
|
|
606
797
|
});
|
|
607
798
|
const animate = () => {
|
|
608
|
-
requestAnimationFrame(animate);
|
|
799
|
+
this._animationFrameId = requestAnimationFrame(animate);
|
|
609
800
|
this._controls?.update();
|
|
610
801
|
this.render();
|
|
611
802
|
};
|
|
@@ -705,15 +896,162 @@ class Chart3dComponent {
|
|
|
705
896
|
.nice();
|
|
706
897
|
return { x, y, z };
|
|
707
898
|
}
|
|
708
|
-
|
|
709
|
-
|
|
899
|
+
addMouseMoveListener() {
|
|
900
|
+
this._mouseMoveHandler = (event) => {
|
|
901
|
+
this.handleMouseMove(event);
|
|
902
|
+
};
|
|
903
|
+
this._mouseLeaveHandler = () => {
|
|
904
|
+
this.hideTooltip();
|
|
905
|
+
this._cdr.detectChanges();
|
|
906
|
+
};
|
|
907
|
+
this.canvas.addEventListener('mousemove', this._mouseMoveHandler);
|
|
908
|
+
this.canvas.addEventListener('mouseleave', this._mouseLeaveHandler);
|
|
909
|
+
}
|
|
910
|
+
handleMouseMove(event) {
|
|
911
|
+
const intersection = this._tooltipService.findIntersection(event, this.canvas, this._camera, this._wellMeshes);
|
|
912
|
+
if (intersection) {
|
|
913
|
+
this.showTooltip(intersection, event);
|
|
914
|
+
this.updateTooltipMarker(intersection.intersectionPoint, intersection.wellData.curve);
|
|
915
|
+
this.canvas.style.cursor = 'pointer';
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
this.hideTooltip();
|
|
919
|
+
this.canvas.style.cursor = 'default';
|
|
920
|
+
}
|
|
921
|
+
this._cdr.detectChanges();
|
|
922
|
+
}
|
|
923
|
+
showTooltip(intersection, event) {
|
|
924
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
925
|
+
this.tooltipVisible = true;
|
|
926
|
+
this.tooltipX = event.clientX - rect.left;
|
|
927
|
+
this.tooltipY = event.clientY - rect.top;
|
|
928
|
+
this.tooltipWellName = intersection.wellData.series.name || 'Well';
|
|
929
|
+
this.tooltipMD = intersection.md.toFixed(2);
|
|
930
|
+
this.tooltipTVD = intersection.tvd.toFixed(2);
|
|
931
|
+
this.tooltipUnit = this._config.unit || 'm';
|
|
932
|
+
if (this._tooltipMarker) {
|
|
933
|
+
this._tooltipMarker.visible = true;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
hideTooltip() {
|
|
937
|
+
this.tooltipVisible = false;
|
|
938
|
+
if (this._tooltipMarker) {
|
|
939
|
+
this._tooltipMarker.visible = false;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
removeMouseListeners() {
|
|
943
|
+
if (this.canvas && this._mouseMoveHandler) {
|
|
944
|
+
this.canvas.removeEventListener('mousemove', this._mouseMoveHandler);
|
|
945
|
+
}
|
|
946
|
+
if (this.canvas && this._mouseLeaveHandler) {
|
|
947
|
+
this.canvas.removeEventListener('mouseleave', this._mouseLeaveHandler);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
disposeThreeResources() {
|
|
951
|
+
// Cancel animation frame
|
|
952
|
+
if (this._animationFrameId) {
|
|
953
|
+
cancelAnimationFrame(this._animationFrameId);
|
|
954
|
+
}
|
|
955
|
+
// Dispose tooltip marker
|
|
956
|
+
if (this._tooltipMarker) {
|
|
957
|
+
this._tooltipMarker.geometry.dispose();
|
|
958
|
+
this._tooltipMarker.material.dispose();
|
|
959
|
+
}
|
|
960
|
+
// Dispose all scene objects
|
|
961
|
+
if (this._scene) {
|
|
962
|
+
this._scene.traverse((object) => {
|
|
963
|
+
if (object instanceof THREE.Mesh) {
|
|
964
|
+
object.geometry?.dispose();
|
|
965
|
+
if (object.material) {
|
|
966
|
+
if (Array.isArray(object.material)) {
|
|
967
|
+
object.material.forEach((material) => material.dispose());
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
object.material.dispose();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (object instanceof THREE.Line || object instanceof THREE.LineSegments) {
|
|
975
|
+
object.geometry?.dispose();
|
|
976
|
+
if (object.material) {
|
|
977
|
+
object.material.dispose();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (object instanceof THREE.Sprite) {
|
|
981
|
+
if (object.material) {
|
|
982
|
+
const spriteMaterial = object.material;
|
|
983
|
+
spriteMaterial.map?.dispose();
|
|
984
|
+
spriteMaterial.dispose();
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
// Dispose controls
|
|
990
|
+
if (this._controls) {
|
|
991
|
+
this._controls.dispose();
|
|
992
|
+
}
|
|
993
|
+
// Dispose renderer
|
|
994
|
+
if (this._renderer) {
|
|
995
|
+
this._renderer.dispose();
|
|
996
|
+
}
|
|
997
|
+
// Clear arrays
|
|
998
|
+
this._wellMeshes = [];
|
|
999
|
+
}
|
|
1000
|
+
updateTooltipMarker(intersectionPoint, curve) {
|
|
1001
|
+
// Remove old marker if exists
|
|
1002
|
+
if (this._tooltipMarker) {
|
|
1003
|
+
this._scene.remove(this._tooltipMarker);
|
|
1004
|
+
this._tooltipMarker.geometry.dispose();
|
|
1005
|
+
this._tooltipMarker.material.dispose();
|
|
1006
|
+
}
|
|
1007
|
+
// Closest point on curve (local helper, keep marker purely visual)
|
|
1008
|
+
const pointOnCurve = this._tooltipService.findClosestPointOnCurveLocal(intersectionPoint, curve);
|
|
1009
|
+
// Find parameter t for tangent calculation
|
|
1010
|
+
let closestT = 0;
|
|
1011
|
+
let minDistance = Infinity;
|
|
1012
|
+
const samples = 100;
|
|
1013
|
+
for (let i = 0; i <= samples; i++) {
|
|
1014
|
+
const t = i / samples;
|
|
1015
|
+
const curvePoint = curve.getPoint(t);
|
|
1016
|
+
const distance = pointOnCurve.distanceTo(curvePoint);
|
|
1017
|
+
if (distance < minDistance) {
|
|
1018
|
+
minDistance = distance;
|
|
1019
|
+
closestT = t;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
// Get tangent at this point to orient the ring
|
|
1023
|
+
const tangent = curve.getTangent(closestT).normalize();
|
|
1024
|
+
const markerGeometry = new THREE.TorusGeometry(.85, 0.25, 16, 32);
|
|
1025
|
+
const markerMaterial = new THREE.MeshBasicMaterial({
|
|
1026
|
+
color: 0xff4444,
|
|
1027
|
+
transparent: true,
|
|
1028
|
+
opacity: 0.85,
|
|
1029
|
+
depthTest: false,
|
|
1030
|
+
side: THREE.DoubleSide,
|
|
1031
|
+
});
|
|
1032
|
+
this._tooltipMarker = new THREE.Mesh(markerGeometry, markerMaterial);
|
|
1033
|
+
// Position the marker at the point on curve
|
|
1034
|
+
this._tooltipMarker.position.copy(pointOnCurve);
|
|
1035
|
+
// Orient the ring perpendicular to the tangent
|
|
1036
|
+
const up = new THREE.Vector3(0, 0, 1);
|
|
1037
|
+
const quaternion = new THREE.Quaternion().setFromUnitVectors(up, tangent);
|
|
1038
|
+
this._tooltipMarker.setRotationFromQuaternion(quaternion);
|
|
1039
|
+
this._tooltipMarker.renderOrder = 999;
|
|
1040
|
+
this._tooltipMarker.visible = true;
|
|
1041
|
+
this._scene.add(this._tooltipMarker);
|
|
1042
|
+
}
|
|
1043
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dComponent, deps: [{ token: i0.ElementRef }, { token: ThemeSwitchService }, { token: i0.ChangeDetectorRef }, { token: Chart3dTooltipService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1044
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.5", type: Chart3dComponent, isStandalone: true, selector: "teta-chart3d", inputs: { config: "config" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }, { propertyName: "tooltipRef", first: true, predicate: ["tooltip"], descendants: true }], ngImport: i0, template: "<canvas #canvas></canvas>\n<div #tooltip class=\"bg-global-bgcard tooltip color-text-90\" [style.display]=\"tooltipVisible ? 'block' : 'none'\" [style.left.px]=\"tooltipX\" [style.top.px]=\"tooltipY\">\n <div class=\"tooltip-content\">\n <div class=\"tooltip-header\">\n <strong>{{ tooltipWellName }}</strong>\n </div>\n <div class=\"tooltip-row\">\n <span class=\"label\">MD:</span>\n <span class=\"value\">{{ tooltipMD }} {{ tooltipUnit }}</span>\n </div>\n <div class=\"tooltip-row\">\n <span class=\"label\">TVD:</span>\n <span class=\"value\">{{ tooltipTVD }} {{ tooltipUnit }}</span>\n </div>\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;height:100%;position:relative}.tooltip{position:absolute;pointer-events:none;z-index:1000;min-width:80px;font-size:12px;white-space:nowrap;transform:translate(-50%,-100%);margin-top:-8px;background-color:var(--color-global-bgcard)}.tooltip .tooltip-content .tooltip-header{font-weight:600;margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid rgba(255,255,255,.2)}.tooltip .tooltip-content .tooltip-row{display:flex;justify-content:space-between;gap:12px;margin-bottom:2px}.tooltip .tooltip-content .tooltip-row .label{opacity:.8}.tooltip .tooltip-content .tooltip-row .value{font-weight:500}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
710
1045
|
}
|
|
711
1046
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dComponent, decorators: [{
|
|
712
1047
|
type: Component,
|
|
713
|
-
args: [{ selector: 'teta-chart3d', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<canvas #canvas></canvas>\n", styles: [":host{display:flex;width:100%;height:100%}\n"] }]
|
|
714
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: ThemeSwitchService }], propDecorators: { canvasRef: [{
|
|
1048
|
+
args: [{ selector: 'teta-chart3d', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<canvas #canvas></canvas>\n<div #tooltip class=\"bg-global-bgcard tooltip color-text-90\" [style.display]=\"tooltipVisible ? 'block' : 'none'\" [style.left.px]=\"tooltipX\" [style.top.px]=\"tooltipY\">\n <div class=\"tooltip-content\">\n <div class=\"tooltip-header\">\n <strong>{{ tooltipWellName }}</strong>\n </div>\n <div class=\"tooltip-row\">\n <span class=\"label\">MD:</span>\n <span class=\"value\">{{ tooltipMD }} {{ tooltipUnit }}</span>\n </div>\n <div class=\"tooltip-row\">\n <span class=\"label\">TVD:</span>\n <span class=\"value\">{{ tooltipTVD }} {{ tooltipUnit }}</span>\n </div>\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;height:100%;position:relative}.tooltip{position:absolute;pointer-events:none;z-index:1000;min-width:80px;font-size:12px;white-space:nowrap;transform:translate(-50%,-100%);margin-top:-8px;background-color:var(--color-global-bgcard)}.tooltip .tooltip-content .tooltip-header{font-weight:600;margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid rgba(255,255,255,.2)}.tooltip .tooltip-content .tooltip-row{display:flex;justify-content:space-between;gap:12px;margin-bottom:2px}.tooltip .tooltip-content .tooltip-row .label{opacity:.8}.tooltip .tooltip-content .tooltip-row .value{font-weight:500}\n"] }]
|
|
1049
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: ThemeSwitchService }, { type: i0.ChangeDetectorRef }, { type: Chart3dTooltipService }], propDecorators: { canvasRef: [{
|
|
715
1050
|
type: ViewChild,
|
|
716
1051
|
args: ['canvas']
|
|
1052
|
+
}], tooltipRef: [{
|
|
1053
|
+
type: ViewChild,
|
|
1054
|
+
args: ['tooltip']
|
|
717
1055
|
}], config: [{
|
|
718
1056
|
type: Input
|
|
719
1057
|
}] } });
|