@tetacom/ng-components 1.6.29 → 1.7.0
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 +14 -0
- package/fesm2022/tetacom-ng-components.mjs +294 -9
- 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,14 @@
|
|
|
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
|
+
constructor();
|
|
8
|
+
findIntersection(event: MouseEvent, canvas: HTMLCanvasElement, camera: THREE.Camera, wellMeshes: WellMeshData[]): IntersectionResult | null;
|
|
9
|
+
findClosestPointOnCurve(point: THREE.Vector3, curve: THREE.CatmullRomCurve3): THREE.Vector3;
|
|
10
|
+
private findClosestDataPoint;
|
|
11
|
+
private calculateTVD;
|
|
12
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<Chart3dTooltipService, never>;
|
|
13
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<Chart3dTooltipService>;
|
|
14
|
+
}
|
|
@@ -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,136 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
|
|
|
486
487
|
}]
|
|
487
488
|
}], ctorParameters: () => [] });
|
|
488
489
|
|
|
490
|
+
class Chart3dTooltipService {
|
|
491
|
+
constructor() {
|
|
492
|
+
this._raycaster = new THREE.Raycaster();
|
|
493
|
+
this._mouse = new THREE.Vector2();
|
|
494
|
+
this._raycaster.params.Line.threshold = 2;
|
|
495
|
+
}
|
|
496
|
+
findIntersection(event, canvas, camera, wellMeshes) {
|
|
497
|
+
// 1. Convert mouse coordinates
|
|
498
|
+
const rect = canvas.getBoundingClientRect();
|
|
499
|
+
this._mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
500
|
+
this._mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
501
|
+
// 2. Raycasting
|
|
502
|
+
this._raycaster.setFromCamera(this._mouse, camera);
|
|
503
|
+
const meshes = wellMeshes.map((_) => _.mesh);
|
|
504
|
+
const intersects = this._raycaster.intersectObjects(meshes, false);
|
|
505
|
+
if (intersects.length === 0) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
// 3. Find well data
|
|
509
|
+
const intersect = intersects[0];
|
|
510
|
+
const wellData = wellMeshes.find((_) => _.mesh === intersect.object);
|
|
511
|
+
if (!wellData) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
// 4. Calculate closest point on curve
|
|
515
|
+
const closestPoint = this.findClosestPointOnCurve(intersect.point, wellData.curve);
|
|
516
|
+
// 5. Find closest data point to get MD
|
|
517
|
+
const closestDataPoint = this.findClosestDataPoint(closestPoint, wellData.points, wellData.scale);
|
|
518
|
+
// 6. Calculate TVD and get MD from data point
|
|
519
|
+
const tvd = this.calculateTVD(closestPoint, wellData.scale.y);
|
|
520
|
+
const md = closestDataPoint.md ?? 0;
|
|
521
|
+
return {
|
|
522
|
+
wellData,
|
|
523
|
+
intersectionPoint: intersect.point,
|
|
524
|
+
closestCurvePoint: closestPoint,
|
|
525
|
+
closestDataPoint,
|
|
526
|
+
md,
|
|
527
|
+
tvd,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
findClosestPointOnCurve(point, curve) {
|
|
531
|
+
let closestPoint = new THREE.Vector3();
|
|
532
|
+
let minDistance = Infinity;
|
|
533
|
+
const samples = 100;
|
|
534
|
+
for (let i = 0; i <= samples; i++) {
|
|
535
|
+
const t = i / samples;
|
|
536
|
+
const curvePoint = curve.getPoint(t);
|
|
537
|
+
const distance = point.distanceTo(curvePoint);
|
|
538
|
+
if (distance < minDistance) {
|
|
539
|
+
minDistance = distance;
|
|
540
|
+
closestPoint = curvePoint;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return closestPoint;
|
|
544
|
+
}
|
|
545
|
+
findClosestDataPoint(curvePoint, dataPoints, scale) {
|
|
546
|
+
let closestPoint = dataPoints[0];
|
|
547
|
+
let minDistance = Infinity;
|
|
548
|
+
let closestIndex = 0;
|
|
549
|
+
for (let i = 0; i < dataPoints.length; i++) {
|
|
550
|
+
const point = dataPoints[i];
|
|
551
|
+
// Convert data point to 3D space using scales
|
|
552
|
+
const scaledPoint = new THREE.Vector3(scale.x(point.x), scale.y(point.y), scale.z(point.z));
|
|
553
|
+
const distance = curvePoint.distanceTo(scaledPoint);
|
|
554
|
+
if (distance < minDistance) {
|
|
555
|
+
minDistance = distance;
|
|
556
|
+
closestPoint = point;
|
|
557
|
+
closestIndex = i;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// If MD is not available in the closest point, try to interpolate
|
|
561
|
+
if (closestPoint.md === undefined || closestPoint.md === null) {
|
|
562
|
+
// Find first point with MD
|
|
563
|
+
for (const point of dataPoints) {
|
|
564
|
+
if (point.md !== undefined && point.md !== null) {
|
|
565
|
+
return point;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// Try to interpolate MD between two closest points
|
|
570
|
+
if (closestIndex > 0 && closestIndex < dataPoints.length - 1) {
|
|
571
|
+
const prevPoint = dataPoints[closestIndex - 1];
|
|
572
|
+
const nextPoint = dataPoints[closestIndex + 1];
|
|
573
|
+
if (prevPoint.md !== undefined &&
|
|
574
|
+
nextPoint.md !== undefined &&
|
|
575
|
+
closestPoint.md === undefined) {
|
|
576
|
+
// Linear interpolation
|
|
577
|
+
const prevScaled = new THREE.Vector3(scale.x(prevPoint.x), scale.y(prevPoint.y), scale.z(prevPoint.z));
|
|
578
|
+
const nextScaled = new THREE.Vector3(scale.x(nextPoint.x), scale.y(nextPoint.y), scale.z(nextPoint.z));
|
|
579
|
+
const totalDist = prevScaled.distanceTo(nextScaled);
|
|
580
|
+
const distFromPrev = curvePoint.distanceTo(prevScaled);
|
|
581
|
+
const t = distFromPrev / totalDist;
|
|
582
|
+
const interpolatedMD = prevPoint.md + (nextPoint.md - prevPoint.md) * t;
|
|
583
|
+
return {
|
|
584
|
+
...closestPoint,
|
|
585
|
+
md: interpolatedMD,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return closestPoint;
|
|
590
|
+
}
|
|
591
|
+
calculateTVD(point, yScale) {
|
|
592
|
+
return yScale.invert(point.y);
|
|
593
|
+
}
|
|
594
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dTooltipService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
595
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dTooltipService, providedIn: 'root' }); }
|
|
596
|
+
}
|
|
597
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dTooltipService, decorators: [{
|
|
598
|
+
type: Injectable,
|
|
599
|
+
args: [{ providedIn: 'root' }]
|
|
600
|
+
}], ctorParameters: () => [] });
|
|
601
|
+
|
|
489
602
|
class Chart3dComponent {
|
|
490
|
-
constructor(_elementRef, _themeService) {
|
|
603
|
+
constructor(_elementRef, _themeService, _cdr, _tooltipService) {
|
|
491
604
|
this._elementRef = _elementRef;
|
|
492
605
|
this._themeService = _themeService;
|
|
606
|
+
this._cdr = _cdr;
|
|
607
|
+
this._tooltipService = _tooltipService;
|
|
493
608
|
this.SIDE_SIZE = 100;
|
|
494
609
|
this._alive = true;
|
|
610
|
+
// Tooltip properties
|
|
611
|
+
this.tooltipVisible = true;
|
|
612
|
+
this.tooltipX = 0;
|
|
613
|
+
this.tooltipY = 0;
|
|
614
|
+
this.tooltipWellName = '';
|
|
615
|
+
this.tooltipMD = '';
|
|
616
|
+
this.tooltipTVD = '';
|
|
617
|
+
this.tooltipUnit = 'm';
|
|
618
|
+
// Raycasting
|
|
619
|
+
this._wellMeshes = [];
|
|
495
620
|
}
|
|
496
621
|
set config(config) {
|
|
497
622
|
if (config) {
|
|
@@ -518,11 +643,14 @@ class Chart3dComponent {
|
|
|
518
643
|
this.addResizeObserver();
|
|
519
644
|
this.createScene();
|
|
520
645
|
this.startRenderingLoop();
|
|
646
|
+
this.addMouseMoveListener();
|
|
521
647
|
this.init();
|
|
522
648
|
}
|
|
523
649
|
ngOnDestroy() {
|
|
524
650
|
this._alive = false;
|
|
525
651
|
this.removeResizeObserver();
|
|
652
|
+
this.removeMouseListeners();
|
|
653
|
+
this.disposeThreeResources();
|
|
526
654
|
}
|
|
527
655
|
init() {
|
|
528
656
|
if (!this._scene) {
|
|
@@ -531,6 +659,7 @@ class Chart3dComponent {
|
|
|
531
659
|
while (this._scene.children.length > 0) {
|
|
532
660
|
this._scene.remove(this._scene.children[0]);
|
|
533
661
|
}
|
|
662
|
+
this._wellMeshes = [];
|
|
534
663
|
const { x, y, z } = this.getScales(this._config.series);
|
|
535
664
|
this.config.series.forEach((data, idx) => {
|
|
536
665
|
if (!data.points?.length) {
|
|
@@ -538,12 +667,21 @@ class Chart3dComponent {
|
|
|
538
667
|
}
|
|
539
668
|
const points = data.points.map((_) => new THREE.Vector3(x(_.x), y(_.y), z(_.z)));
|
|
540
669
|
const color = d3.scaleOrdinal(d3.schemeTableau10);
|
|
541
|
-
const material = new THREE.
|
|
670
|
+
const material = new THREE.MeshBasicMaterial({
|
|
542
671
|
color: data?.color ?? color(idx.toString()),
|
|
543
672
|
});
|
|
544
|
-
const
|
|
545
|
-
|
|
673
|
+
const curve = new THREE.CatmullRomCurve3(points);
|
|
674
|
+
const tubeGeometry = new THREE.TubeGeometry(curve, 1024, 0.5, 20, false);
|
|
675
|
+
let tube = new THREE.Mesh(tubeGeometry, material);
|
|
546
676
|
this._scene.add(tube);
|
|
677
|
+
// Store well mesh with its data for raycasting
|
|
678
|
+
this._wellMeshes.push({
|
|
679
|
+
mesh: tube,
|
|
680
|
+
series: data,
|
|
681
|
+
scale: { x, y, z },
|
|
682
|
+
curve: curve,
|
|
683
|
+
points: data.points,
|
|
684
|
+
});
|
|
547
685
|
});
|
|
548
686
|
const circles = x.ticks(this.SIDE_SIZE / 10);
|
|
549
687
|
const material = new THREE.LineBasicMaterial({ color: this.axesColor });
|
|
@@ -605,7 +743,7 @@ class Chart3dComponent {
|
|
|
605
743
|
preserveDrawingBuffer: true,
|
|
606
744
|
});
|
|
607
745
|
const animate = () => {
|
|
608
|
-
requestAnimationFrame(animate);
|
|
746
|
+
this._animationFrameId = requestAnimationFrame(animate);
|
|
609
747
|
this._controls?.update();
|
|
610
748
|
this.render();
|
|
611
749
|
};
|
|
@@ -705,15 +843,162 @@ class Chart3dComponent {
|
|
|
705
843
|
.nice();
|
|
706
844
|
return { x, y, z };
|
|
707
845
|
}
|
|
708
|
-
|
|
709
|
-
|
|
846
|
+
addMouseMoveListener() {
|
|
847
|
+
this._mouseMoveHandler = (event) => {
|
|
848
|
+
this.handleMouseMove(event);
|
|
849
|
+
};
|
|
850
|
+
this._mouseLeaveHandler = () => {
|
|
851
|
+
this.hideTooltip();
|
|
852
|
+
this._cdr.detectChanges();
|
|
853
|
+
};
|
|
854
|
+
this.canvas.addEventListener('mousemove', this._mouseMoveHandler);
|
|
855
|
+
this.canvas.addEventListener('mouseleave', this._mouseLeaveHandler);
|
|
856
|
+
}
|
|
857
|
+
handleMouseMove(event) {
|
|
858
|
+
const intersection = this._tooltipService.findIntersection(event, this.canvas, this._camera, this._wellMeshes);
|
|
859
|
+
if (intersection) {
|
|
860
|
+
this.showTooltip(intersection, event);
|
|
861
|
+
this.updateTooltipMarker(intersection.intersectionPoint, intersection.wellData.curve);
|
|
862
|
+
this.canvas.style.cursor = 'pointer';
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
this.hideTooltip();
|
|
866
|
+
this.canvas.style.cursor = 'default';
|
|
867
|
+
}
|
|
868
|
+
this._cdr.detectChanges();
|
|
869
|
+
}
|
|
870
|
+
showTooltip(intersection, event) {
|
|
871
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
872
|
+
this.tooltipVisible = true;
|
|
873
|
+
this.tooltipX = event.clientX - rect.left;
|
|
874
|
+
this.tooltipY = event.clientY - rect.top;
|
|
875
|
+
this.tooltipWellName = intersection.wellData.series.name || 'Well';
|
|
876
|
+
this.tooltipMD = intersection.md.toFixed(2);
|
|
877
|
+
this.tooltipTVD = intersection.tvd.toFixed(2);
|
|
878
|
+
this.tooltipUnit = this._config.unit || 'm';
|
|
879
|
+
if (this._tooltipMarker) {
|
|
880
|
+
this._tooltipMarker.visible = true;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
hideTooltip() {
|
|
884
|
+
this.tooltipVisible = false;
|
|
885
|
+
if (this._tooltipMarker) {
|
|
886
|
+
this._tooltipMarker.visible = false;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
removeMouseListeners() {
|
|
890
|
+
if (this.canvas && this._mouseMoveHandler) {
|
|
891
|
+
this.canvas.removeEventListener('mousemove', this._mouseMoveHandler);
|
|
892
|
+
}
|
|
893
|
+
if (this.canvas && this._mouseLeaveHandler) {
|
|
894
|
+
this.canvas.removeEventListener('mouseleave', this._mouseLeaveHandler);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
disposeThreeResources() {
|
|
898
|
+
// Cancel animation frame
|
|
899
|
+
if (this._animationFrameId) {
|
|
900
|
+
cancelAnimationFrame(this._animationFrameId);
|
|
901
|
+
}
|
|
902
|
+
// Dispose tooltip marker
|
|
903
|
+
if (this._tooltipMarker) {
|
|
904
|
+
this._tooltipMarker.geometry.dispose();
|
|
905
|
+
this._tooltipMarker.material.dispose();
|
|
906
|
+
}
|
|
907
|
+
// Dispose all scene objects
|
|
908
|
+
if (this._scene) {
|
|
909
|
+
this._scene.traverse((object) => {
|
|
910
|
+
if (object instanceof THREE.Mesh) {
|
|
911
|
+
object.geometry?.dispose();
|
|
912
|
+
if (object.material) {
|
|
913
|
+
if (Array.isArray(object.material)) {
|
|
914
|
+
object.material.forEach((material) => material.dispose());
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
object.material.dispose();
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (object instanceof THREE.Line || object instanceof THREE.LineSegments) {
|
|
922
|
+
object.geometry?.dispose();
|
|
923
|
+
if (object.material) {
|
|
924
|
+
object.material.dispose();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (object instanceof THREE.Sprite) {
|
|
928
|
+
if (object.material) {
|
|
929
|
+
const spriteMaterial = object.material;
|
|
930
|
+
spriteMaterial.map?.dispose();
|
|
931
|
+
spriteMaterial.dispose();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
// Dispose controls
|
|
937
|
+
if (this._controls) {
|
|
938
|
+
this._controls.dispose();
|
|
939
|
+
}
|
|
940
|
+
// Dispose renderer
|
|
941
|
+
if (this._renderer) {
|
|
942
|
+
this._renderer.dispose();
|
|
943
|
+
}
|
|
944
|
+
// Clear arrays
|
|
945
|
+
this._wellMeshes = [];
|
|
946
|
+
}
|
|
947
|
+
updateTooltipMarker(intersectionPoint, curve) {
|
|
948
|
+
// Remove old marker if exists
|
|
949
|
+
if (this._tooltipMarker) {
|
|
950
|
+
this._scene.remove(this._tooltipMarker);
|
|
951
|
+
this._tooltipMarker.geometry.dispose();
|
|
952
|
+
this._tooltipMarker.material.dispose();
|
|
953
|
+
}
|
|
954
|
+
// Find the closest point on the curve using service
|
|
955
|
+
const pointOnCurve = this._tooltipService.findClosestPointOnCurve(intersectionPoint, curve);
|
|
956
|
+
// Find parameter t for tangent calculation
|
|
957
|
+
let closestT = 0;
|
|
958
|
+
let minDistance = Infinity;
|
|
959
|
+
const samples = 100;
|
|
960
|
+
for (let i = 0; i <= samples; i++) {
|
|
961
|
+
const t = i / samples;
|
|
962
|
+
const curvePoint = curve.getPoint(t);
|
|
963
|
+
const distance = pointOnCurve.distanceTo(curvePoint);
|
|
964
|
+
if (distance < minDistance) {
|
|
965
|
+
minDistance = distance;
|
|
966
|
+
closestT = t;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
// Get tangent at this point to orient the ring
|
|
970
|
+
const tangent = curve.getTangent(closestT).normalize();
|
|
971
|
+
const markerGeometry = new THREE.TorusGeometry(.85, 0.25, 16, 32);
|
|
972
|
+
const markerMaterial = new THREE.MeshBasicMaterial({
|
|
973
|
+
color: 0xff4444,
|
|
974
|
+
transparent: true,
|
|
975
|
+
opacity: 0.85,
|
|
976
|
+
depthTest: false,
|
|
977
|
+
side: THREE.DoubleSide,
|
|
978
|
+
});
|
|
979
|
+
this._tooltipMarker = new THREE.Mesh(markerGeometry, markerMaterial);
|
|
980
|
+
// Position the marker at the point on curve
|
|
981
|
+
this._tooltipMarker.position.copy(pointOnCurve);
|
|
982
|
+
// Orient the ring perpendicular to the tangent
|
|
983
|
+
const up = new THREE.Vector3(0, 0, 1);
|
|
984
|
+
const quaternion = new THREE.Quaternion().setFromUnitVectors(up, tangent);
|
|
985
|
+
this._tooltipMarker.setRotationFromQuaternion(quaternion);
|
|
986
|
+
this._tooltipMarker.renderOrder = 999;
|
|
987
|
+
this._tooltipMarker.visible = true;
|
|
988
|
+
this._scene.add(this._tooltipMarker);
|
|
989
|
+
}
|
|
990
|
+
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 }); }
|
|
991
|
+
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
992
|
}
|
|
711
993
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: Chart3dComponent, decorators: [{
|
|
712
994
|
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: [{
|
|
995
|
+
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"] }]
|
|
996
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: ThemeSwitchService }, { type: i0.ChangeDetectorRef }, { type: Chart3dTooltipService }], propDecorators: { canvasRef: [{
|
|
715
997
|
type: ViewChild,
|
|
716
998
|
args: ['canvas']
|
|
999
|
+
}], tooltipRef: [{
|
|
1000
|
+
type: ViewChild,
|
|
1001
|
+
args: ['tooltip']
|
|
717
1002
|
}], config: [{
|
|
718
1003
|
type: Input
|
|
719
1004
|
}] } });
|