@welldot/render 0.0.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/dist/index.js ADDED
@@ -0,0 +1,2036 @@
1
+ // src/configs/render.configs.ts
2
+ import { easeCubic } from "d3";
3
+ var DEFAULT_WELL_THEME = {
4
+ lithology: { stroke: "#101010", strokeWidth: 1 },
5
+ lithologyTexture: { size: 150, strokeWidth: 0.8, stroke: "#303030" },
6
+ cave: {
7
+ dryStroke: "#333333",
8
+ wetStroke: "#1a6fa8",
9
+ fillOpacity: 0.6,
10
+ contactStrokeWidth: 1.2
11
+ },
12
+ fracture: {
13
+ dryStroke: "#000000",
14
+ wetStroke: "#1a6fa8",
15
+ swarm: {
16
+ centralStrokeWidth: 1.2,
17
+ sideStrokeWidthBase: 0.6,
18
+ sideStrokeWidthVariance: 0.6
19
+ },
20
+ single: {
21
+ mainStrokeWidth: 1.8,
22
+ crackStrokeWidth: 0.7
23
+ }
24
+ },
25
+ cementPad: { stroke: "#303030", strokeWidth: 2 },
26
+ boreHole: {
27
+ fill: "#ffffff",
28
+ stroke: "#191919",
29
+ strokeDasharray: "4, 3",
30
+ opacity: 0.7,
31
+ strokeWidth: 1.2
32
+ },
33
+ surfaceCase: { stroke: "#000000", strokeWidth: 4 },
34
+ holeFill: { stroke: "#303030", strokeWidth: 2 },
35
+ wellCase: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
36
+ wellScreen: { stroke: "#303030", strokeWidth: 2 },
37
+ conflict: { stroke: "#e52117", strokeWidth: 4 },
38
+ unitLabels: {
39
+ geologicFill: "#f0f0f0",
40
+ aquiferFill: "#dff0ff",
41
+ stroke: "#303030",
42
+ strokeWidth: 0.4,
43
+ fontSize: 5.5,
44
+ fontFamily: "sans-serif",
45
+ fontWeight: 400
46
+ },
47
+ constructionLabels: {
48
+ fontSize: 7.5,
49
+ labelFill: "#ffffff",
50
+ labelColor: "#303030",
51
+ fontFamily: "sans-serif",
52
+ fontWeight: 400
53
+ },
54
+ labels: {
55
+ dividerStroke: "#888888",
56
+ dividerStrokeWidth: 0.5,
57
+ dividerStrokeDasharray: "2, 2",
58
+ fontSize: 7,
59
+ color: "#333",
60
+ headerFont: "monospace",
61
+ bodyColor: "#555",
62
+ bodyFont: "sans-serif",
63
+ scaleFont: "monospace",
64
+ depthTipFill: "#ffffff",
65
+ depthTipRadius: 2,
66
+ annotationBg: "#ffffff",
67
+ annotationBgOpacity: 0.85,
68
+ annotationRadius: 2,
69
+ headerFontWeight: 600,
70
+ bodyFontWeight: 400
71
+ },
72
+ legend: {
73
+ borderStrokeWidth: 0.8,
74
+ fractureStrokeWidth: 1.5,
75
+ fractureSideStrokeWidth: 0.8,
76
+ itemStrokeWidth: 0.8,
77
+ fontSize: 7,
78
+ fontFamily: "sans-serif",
79
+ titleFontWeight: "bold",
80
+ labelFontWeight: 400
81
+ }
82
+ };
83
+ var DEFAULT_HIGHLIGHT_CONFIG = {
84
+ stroke: "#e52117",
85
+ strokeWidth: 2,
86
+ fill: "none",
87
+ fillOpacity: 0,
88
+ padding: 2,
89
+ strokeDasharray: void 0,
90
+ labelFontSize: 7,
91
+ labelPadding: 3,
92
+ labelBackground: "#e52117",
93
+ labelColor: "#ffffff",
94
+ labelRadius: 2
95
+ };
96
+ var STATIC_RENDER_CONFIG = {
97
+ zoom: true,
98
+ pan: true,
99
+ zoomLevel: 1,
100
+ animation: { duration: 600, ease: easeCubic },
101
+ geologic: { xLeft: 20, xRightInset: 300 },
102
+ layout: {
103
+ pocoWidthRatio: 0.21,
104
+ // construction column occupies 21% of total SVG width
105
+ pocoCenterRatio: -0.39
106
+ // construction center x-offset (negative = shift left of SVG centre)
107
+ },
108
+ caves: {
109
+ pathSteps: 40,
110
+ amplitude: {
111
+ ratio: 0.12,
112
+ // cave contact waviness: 12% of the cave band pixel height
113
+ min: 1,
114
+ max: 5.5
115
+ }
116
+ },
117
+ fractures: {
118
+ widthMultiplier: 1.2,
119
+ hitBuffer: { single: 8, swarm: 15 },
120
+ swarm: {
121
+ lineCountBase: 3,
122
+ lineCountVariance: 11,
123
+ spread: 18
124
+ }
125
+ },
126
+ construction: {
127
+ cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
128
+ surfaceCase: { diameterPaddingRatio: 0.1 }
129
+ },
130
+ labels: {
131
+ active: true,
132
+ typeLabels: { fracture: "fratura", cave: "caverna" },
133
+ depthTipHeight: 11,
134
+ depthTipPadX: 2,
135
+ descriptionXOffset: 20,
136
+ descriptionMaxWidth: 250,
137
+ stackingLineHeight: 10,
138
+ stackingGap: 0,
139
+ fractureLabelLeaderGap: 5,
140
+ lithology: true
141
+ },
142
+ unitLabels: {
143
+ active: true,
144
+ xOffset: 2,
145
+ stripWidth: 8,
146
+ minHeightForText: 8,
147
+ innerDividerWidth: 1.5,
148
+ outerEdgeWidth: 0.6
149
+ },
150
+ constructionLabels: {
151
+ active: true,
152
+ xOffset: -12,
153
+ labelRadius: 2,
154
+ labelMaxWidth: 90,
155
+ labels: {
156
+ wellCasePrefix: "Revest.",
157
+ wellScreenPrefix: "Filtro",
158
+ wellScreenSlotPrefix: "Ranhura:"
159
+ }
160
+ },
161
+ textures: {
162
+ pad: { background: "#ffffff", stroke: "#303030" },
163
+ conflict: { stroke: "#E52117" },
164
+ cave_dry: { size: 8, stroke: "#333333", background: "#ffffff" },
165
+ cave_wet: { size: 8, stroke: "#1a6fa8", background: "#ffffff" },
166
+ seal: { background: "#ffffff" },
167
+ gravel_pack: { background: "#ffffff" },
168
+ well_screen: { size: 40, strokeWidth: 2, background: "#ffffff" },
169
+ surface_case: {
170
+ orientation: ["vertical", "horizontal"],
171
+ size: 4,
172
+ strokeWidth: 1,
173
+ shapeRendering: "crispEdges",
174
+ stroke: "#303030",
175
+ background: "#fff"
176
+ }
177
+ },
178
+ legend: {
179
+ title: "LEGENDA",
180
+ itemWidth: 110,
181
+ height: 44,
182
+ padding: 4,
183
+ maxWidth: 700,
184
+ borderRadius: 3,
185
+ labels: {
186
+ fractureSingle: "Fratura simples",
187
+ fractureSwarm: "Enxame de fraturas",
188
+ fractureWater: "Entrada d'\xE1gua",
189
+ caveDry: "Caverna seca",
190
+ caveWet: "Caverna c/ \xE1gua",
191
+ boreHole: "Perfura\xE7\xE3o",
192
+ surfaceCase: "Tubo guia",
193
+ holeFillGravel: "Pr\xE9-filtro",
194
+ holeFillSeal: "Veda\xE7\xE3o",
195
+ wellCase: "Revestimento",
196
+ wellScreen: "Filtro",
197
+ cementPad: "Laje de cimento",
198
+ conflict: "Conflito"
199
+ }
200
+ },
201
+ highlights: DEFAULT_HIGHLIGHT_CONFIG
202
+ };
203
+ var INTERACTIVE_RENDER_CONFIG = {
204
+ zoom: true,
205
+ pan: true,
206
+ zoomLevel: 1,
207
+ animation: { duration: 600, ease: easeCubic },
208
+ geologic: { xLeft: 6, xRightInset: 56 },
209
+ layout: {
210
+ pocoWidthRatio: 0.25,
211
+ // construction column occupies 25% of total SVG width
212
+ pocoCenterRatio: 0.11
213
+ // construction center x-offset (positive = shift right of SVG centre)
214
+ },
215
+ caves: {
216
+ pathSteps: 40,
217
+ amplitude: {
218
+ ratio: 0.12,
219
+ // cave contact waviness: 12% of the cave band pixel height
220
+ min: 1,
221
+ max: 5.5
222
+ }
223
+ },
224
+ fractures: {
225
+ widthMultiplier: 1.2,
226
+ hitBuffer: { single: 8, swarm: 15 },
227
+ swarm: {
228
+ lineCountBase: 3,
229
+ lineCountVariance: 11,
230
+ spread: 18
231
+ }
232
+ },
233
+ construction: {
234
+ cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
235
+ surfaceCase: { diameterPaddingRatio: 0.1 }
236
+ },
237
+ labels: {
238
+ active: false,
239
+ typeLabels: { fracture: "fratura", cave: "caverna" },
240
+ depthTipHeight: 11,
241
+ depthTipPadX: 2,
242
+ descriptionXOffset: 78,
243
+ descriptionMaxWidth: 226,
244
+ stackingLineHeight: 11,
245
+ stackingGap: 0
246
+ },
247
+ unitLabels: {
248
+ active: false,
249
+ xOffset: 0,
250
+ stripWidth: 8,
251
+ minHeightForText: 8,
252
+ innerDividerWidth: 1.5,
253
+ outerEdgeWidth: 0.6
254
+ },
255
+ constructionLabels: {
256
+ active: false,
257
+ xOffset: 10,
258
+ labelRadius: 2,
259
+ labels: {
260
+ wellCasePrefix: "Revest.",
261
+ wellScreenPrefix: "Filtro",
262
+ wellScreenSlotPrefix: "Ranhura:"
263
+ }
264
+ },
265
+ textures: {
266
+ pad: { background: "#ffffff", stroke: "#303030" },
267
+ conflict: { stroke: "#E52117" },
268
+ cave_dry: { size: 8, stroke: "#333333", background: "#ffffff" },
269
+ cave_wet: { size: 8, stroke: "#1a6fa8", background: "#ffffff" },
270
+ seal: { background: "#ffffff" },
271
+ gravel_pack: { background: "#ffffff" },
272
+ well_screen: { size: 40, strokeWidth: 2, background: "#ffffff" },
273
+ surface_case: {
274
+ orientation: ["vertical", "horizontal"],
275
+ size: 4,
276
+ strokeWidth: 1,
277
+ shapeRendering: "crispEdges",
278
+ stroke: "#303030",
279
+ background: "#fff"
280
+ }
281
+ },
282
+ legend: {
283
+ title: "LEGENDA",
284
+ itemWidth: 110,
285
+ height: 44,
286
+ padding: 4,
287
+ maxWidth: 700,
288
+ borderRadius: 3,
289
+ labels: {
290
+ fractureSingle: "Fratura simples",
291
+ fractureSwarm: "Enxame de fraturas",
292
+ fractureWater: "Entrada d'\xE1gua",
293
+ caveDry: "Caverna seca",
294
+ caveWet: "Caverna c/ \xE1gua",
295
+ boreHole: "Perfura\xE7\xE3o",
296
+ surfaceCase: "Tubo guia",
297
+ holeFillGravel: "Pr\xE9-filtro",
298
+ holeFillSeal: "Veda\xE7\xE3o",
299
+ wellCase: "Revestimento",
300
+ wellScreen: "Filtro",
301
+ cementPad: "Laje de cimento",
302
+ conflict: "Conflito"
303
+ }
304
+ },
305
+ highlights: DEFAULT_HIGHLIGHT_CONFIG
306
+ };
307
+
308
+ // src/utils/fgdcTextures.ts
309
+ async function importFgdcTextures() {
310
+ const fgdcTextures = await import("./fgdcTextures-5IIUV7QV.js");
311
+ return fgdcTextures.default;
312
+ }
313
+
314
+ // src/Renderer.ts
315
+ import * as d3module3 from "d3";
316
+ import d3tip2 from "d3-tip";
317
+ import { defu as defu2 } from "defu";
318
+
319
+ // src/configs/render.textures.ts
320
+ import textures from "textures";
321
+ function createWellTextures(cfg) {
322
+ const pad = {
323
+ background: "#ffffff",
324
+ stroke: "#303030",
325
+ ...cfg?.pad
326
+ };
327
+ const conflict = {
328
+ stroke: "#E52117",
329
+ ...cfg?.conflict
330
+ };
331
+ const cave_dry = {
332
+ size: 8,
333
+ stroke: "#333333",
334
+ background: "#ffffff",
335
+ ...cfg?.cave_dry
336
+ };
337
+ const cave_wet = {
338
+ size: 8,
339
+ stroke: "#1a6fa8",
340
+ background: "#ffffff",
341
+ ...cfg?.cave_wet
342
+ };
343
+ const seal = {
344
+ background: "#ffffff",
345
+ ...cfg?.seal
346
+ };
347
+ const gravel_pack = {
348
+ size: 16,
349
+ background: "#ffffff",
350
+ ...cfg?.gravel_pack
351
+ };
352
+ const well_screen = {
353
+ size: 40,
354
+ strokeWidth: 2,
355
+ background: "#ffffff",
356
+ ...cfg?.well_screen
357
+ };
358
+ const surface_case = {
359
+ orientation: ["vertical", "horizontal"],
360
+ size: 4,
361
+ strokeWidth: 1,
362
+ shapeRendering: "crispEdges",
363
+ stroke: "#303030",
364
+ background: "#fff",
365
+ ...cfg?.surface_case
366
+ };
367
+ return {
368
+ pad: textures.lines().heavier(10).thinner(1.5).stroke(pad.stroke).background(pad.background),
369
+ conflict: textures.lines().heavier().stroke(conflict.stroke),
370
+ cave_dry: textures.lines().size(cave_dry.size).orientation("6/8").heavier(0.3).thinner(0.8).background(cave_dry.background).stroke(cave_dry.stroke),
371
+ cave_wet: textures.lines().size(cave_wet.size).orientation("6/8").heavier(0.3).thinner(0.8).background(cave_wet.background).stroke(cave_wet.stroke),
372
+ seal: textures.lines().thicker().background(seal.background),
373
+ gravel_pack: textures.circles().size(gravel_pack.size).complement().background(gravel_pack.background),
374
+ well_screen: textures.paths().d((s) => `M ${s / 4} ${s / 4} l ${s / 2} 0 `).size(well_screen.size).strokeWidth(well_screen.strokeWidth).thicker(2).background(well_screen.background),
375
+ surface_case: textures.lines().orientation(...surface_case.orientation).size(surface_case.size).strokeWidth(surface_case.strokeWidth).shapeRendering(surface_case.shapeRendering).stroke(surface_case.stroke).background(surface_case.background)
376
+ };
377
+ }
378
+
379
+ // src/Renderer.ts
380
+ import {
381
+ isWellEmpty
382
+ } from "@welldot/core";
383
+ import { getProfileLastItemsDepths } from "@welldot/utils";
384
+
385
+ // src/utils/format.utils.ts
386
+ var MM_TO_INCHES = 0.0393701;
387
+ var M_TO_FT = 3.28084;
388
+ function formatLength(m, units) {
389
+ return units === "ft" ? (m * M_TO_FT).toFixed(1) : String(m);
390
+ }
391
+ function formatDiameter(mm, units) {
392
+ return units === "inches" ? (mm * MM_TO_INCHES).toFixed(2) : String(mm);
393
+ }
394
+ function getLengthUnit(units) {
395
+ return units === "ft" ? "ft" : "m";
396
+ }
397
+ function getDiameterUnit(units) {
398
+ return units === "inches" ? '"' : "mm";
399
+ }
400
+
401
+ // src/configs/render.classnames.ts
402
+ var DEFAULT_COMPONENTS_CLASS_NAMES = {
403
+ tooltip: {
404
+ root: "wp-tooltip",
405
+ title: "wp-tooltip-title",
406
+ primaryInfo: "wp-tooltip-primary",
407
+ secondaryInfo: "wp-tooltip-secondary"
408
+ },
409
+ yAxis: "wp-y-axis",
410
+ wellGroup: "wp-well-group",
411
+ geologicGroup: "wp-geologic-group",
412
+ lithology: {
413
+ group: "lithology-group",
414
+ rect: "lithology-rect"
415
+ },
416
+ fractures: {
417
+ group: "fractures-group",
418
+ item: "fracture-group",
419
+ hitArea: "fracture-hit",
420
+ line: "fracture-line",
421
+ polyline: "fracture-poly"
422
+ },
423
+ caves: {
424
+ group: "caves-group",
425
+ item: "cave-group",
426
+ fill: "cave-fill",
427
+ contact: "cave-contact"
428
+ },
429
+ constructionGroup: "construction-group",
430
+ constructionLabels: {
431
+ group: "construction-labels-group"
432
+ },
433
+ cementPad: {
434
+ group: "cement-pad",
435
+ item: "cement-pad-rect"
436
+ },
437
+ boreHole: {
438
+ group: "hole",
439
+ rect: "bore-hole-rect"
440
+ },
441
+ surfaceCase: {
442
+ group: "surface-case",
443
+ rect: "surface-case-rect"
444
+ },
445
+ holeFill: {
446
+ group: "hole-fill",
447
+ rect: "hole-fill-rect"
448
+ },
449
+ wellCase: {
450
+ group: "well-case",
451
+ rect: "well-case-rect"
452
+ },
453
+ wellScreen: {
454
+ group: "well-screen",
455
+ rect: "well-screen-rect"
456
+ },
457
+ conflict: {
458
+ group: "conflict",
459
+ rect: "conflict-rect"
460
+ },
461
+ unitLabels: {
462
+ group: "unit-labels-group",
463
+ geoRect: "unit-geo-rect",
464
+ aqRect: "unit-aq-rect",
465
+ text: "unit-label-text"
466
+ },
467
+ labels: {
468
+ lithology: {
469
+ group: "lithology-labels-group",
470
+ depth: "lithology-depth-tip",
471
+ label: "lithology-label",
472
+ divider: "lithology-label-divider"
473
+ }
474
+ },
475
+ legend: {
476
+ border: "wp-legend-border",
477
+ title: "wp-legend-title",
478
+ item: "wp-legend-item",
479
+ label: "wp-legend-label",
480
+ fracturePoly: "fracture-poly",
481
+ caveFill: "cave-fill",
482
+ constructionRect: "construction-rect"
483
+ },
484
+ highlights: {
485
+ geologicGroup: "wp-highlights-geologic",
486
+ constructionGroup: "wp-highlights-construction",
487
+ fracturesGroup: "wp-highlights-fractures",
488
+ item: "wp-highlight-item",
489
+ rect: "wp-highlight-rect",
490
+ label: "wp-highlight-label",
491
+ labelBg: "wp-highlight-label-bg"
492
+ }
493
+ };
494
+
495
+ // src/utils/render.utils.ts
496
+ import textures2 from "textures";
497
+
498
+ // src/utils/d3.utils.ts
499
+ function mergeEnter(enter, update) {
500
+ return enter.merge(update);
501
+ }
502
+ function withTransition(selection, transition) {
503
+ return selection.transition(transition);
504
+ }
505
+ function asSvgElement(sel) {
506
+ return sel;
507
+ }
508
+ var getYAxisFunctions = (yScale, clamp) => {
509
+ const clampFrom = clamp?.from ?? -Infinity;
510
+ const clampTo = clamp?.to ?? Infinity;
511
+ return {
512
+ getHeight: ({ from, to }) => Math.max(
513
+ 0,
514
+ yScale(Math.min(to, clampTo)) - yScale(Math.max(from, clampFrom))
515
+ ),
516
+ getYPos: ({ from }) => yScale(Math.max(from, clampFrom))
517
+ };
518
+ };
519
+ function filterByDepth(depthFrom, depthTo) {
520
+ return (l) => !(l.to <= depthFrom || l.from >= depthTo);
521
+ }
522
+
523
+ // src/utils/geometry.utils.ts
524
+ var wavyContact = (xLeft, xRight, baseY, amp, steps, rng) => {
525
+ const pts = [];
526
+ let offset = 0;
527
+ for (let i = 0; i < steps; i++) {
528
+ const t = i / (steps - 1);
529
+ const x = xLeft + t * (xRight - xLeft);
530
+ offset += (rng() - 0.5) * amp * 0.6;
531
+ offset *= 0.75;
532
+ pts.push([x, baseY + offset]);
533
+ }
534
+ return pts;
535
+ };
536
+ var ptsToSmoothPath = (pts, options = {}) => {
537
+ if (pts.length < 2) return "";
538
+ const n = pts.length;
539
+ const tension = 0.35;
540
+ const segments = [];
541
+ for (let i = 0; i < n - 1; i++) {
542
+ const p0 = pts[Math.max(0, i - 1)];
543
+ const p1 = pts[i];
544
+ const p2 = pts[i + 1];
545
+ const p3 = pts[Math.min(n - 1, i + 2)];
546
+ const cp1x = p1[0] + (p2[0] - p0[0]) * tension;
547
+ const cp1y = p1[1] + (p2[1] - p0[1]) * tension;
548
+ const cp2x = p2[0] - (p3[0] - p1[0]) * tension;
549
+ const cp2y = p2[1] - (p3[1] - p1[1]) * tension;
550
+ segments.push(
551
+ `C ${cp1x.toFixed(1)},${cp1y.toFixed(1)} ${cp2x.toFixed(1)},${cp2y.toFixed(1)} ${p2[0].toFixed(1)},${p2[1].toFixed(1)}`
552
+ );
553
+ }
554
+ const body = segments.join(" ");
555
+ return options.omitMoveTo ? body : `M ${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)} ${body}`;
556
+ };
557
+ var makeSeededPrng = (seed, scramble = 6271) => {
558
+ let s = Math.abs(seed * scramble) | 1;
559
+ return () => {
560
+ s = s * 16807 % 2147483647;
561
+ return (s - 1) / 2147483646;
562
+ };
563
+ };
564
+
565
+ // src/utils/tooltips.utils.ts
566
+ import * as d3module from "d3";
567
+ import d3tip from "d3-tip";
568
+ import sanitizeHtml from "sanitize-html";
569
+ var d3 = Object.assign(d3module, { tip: d3tip });
570
+ var esc = (v) => sanitizeHtml(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
571
+ var populateTooltips = (svg, customClasses, units, tooltipConfig) => {
572
+ const svgEl = typeof svg.node === "function" ? svg.node() : null;
573
+ if (svgEl?.getAttribute("data-tooltips-init") === "true") {
574
+ return {};
575
+ }
576
+ const tipsText = {
577
+ geology: (_, d) => `
578
+ <span class="${customClasses.tooltip.title}">Litologia</span>
579
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
580
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>
581
+ ${d.geologic_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Unidade geol\xF3gica:</strong> ${esc(d.geologic_unit)}</span>` : ""}
582
+ ${d.aquifer_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Unidade aqu\xEDfera:</strong> ${esc(d.aquifer_unit)}</span>` : ""}
583
+ `,
584
+ hole: (_, d) => `
585
+ <span class="${customClasses.tooltip.title}">FURO</span>
586
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
587
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
588
+ `,
589
+ surfaceCase: (_, d) => `
590
+ <span class="${customClasses.tooltip.title}">TUBO DE BOCA</span>
591
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
592
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
593
+ `,
594
+ holeFill: (_, d) => `
595
+ <span class="${customClasses.tooltip.title}">ESP. ANULAR</span>
596
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
597
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
598
+ <span class="${customClasses.tooltip.secondaryInfo}">
599
+ <strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}
600
+ </span>
601
+ `,
602
+ wellCase: (_, d) => `
603
+ <span class="${customClasses.tooltip.title}">REVESTIMENTO</span>
604
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
605
+ <span class="${customClasses.tooltip.secondaryInfo}">
606
+ <strong>Di\xE2metro:</strong> ${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}
607
+ </span>
608
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Tipo:</strong> ${esc(d.type)}</span>
609
+ `,
610
+ wellScreen: (_, d) => `
611
+ <span class="${customClasses.tooltip.title}">FILTROS</span>
612
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
613
+ <span class="${customClasses.tooltip.secondaryInfo}">
614
+ <strong>Di\xE2metro:</strong> ${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
615
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Tipo:</strong> ${esc(d.type)}</span>
616
+ <span class="${customClasses.tooltip.secondaryInfo}">
617
+ <strong>Ranhura:</strong> ${esc(d.screen_slot_mm)} mm
618
+ </span>
619
+ `,
620
+ conflict: (_, d) => `
621
+ <span class="${customClasses.tooltip.title}">CONFLITO</span>
622
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
623
+ `,
624
+ fracture: (_, d) => {
625
+ const title = d.swarm ? "ENXAME DE FRATURAS" : "FRATURA";
626
+ return `
627
+ <span class="${customClasses.tooltip.title}">${title}</span>
628
+ <span class="${customClasses.tooltip.primaryInfo}"><strong>Profundidade:</strong> ${esc(formatLength(d.depth, units.length))} ${esc(units.length)}</span>
629
+ ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Entrada d'\xE1gua:</strong> ${esc(formatLength(d.depth, units.length))} ${esc(units.length)}</span>` : ""}
630
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Mergulho:</strong> ${esc(d.dip)}\xB0</span>
631
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Azimute:</strong> ${esc(d.azimuth)}\xB0</span>
632
+ ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>` : ""}
633
+ `;
634
+ },
635
+ cementPad: (_, d) => `
636
+ <span class="${customClasses.tooltip.title}">LAJE DE PROTE\xC7\xC3O</span>
637
+ <span class="${customClasses.tooltip.primaryInfo}">${esc(d.type)}</span>
638
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>Espessura:</strong>
639
+ ${esc(formatLength(d.thickness, units.length))} ${esc(units.length)}</span>
640
+ <span class="${customClasses.tooltip.secondaryInfo}">
641
+ <strong>Largura:</strong> ${esc(formatLength(d.width, units.length))} ${esc(units.length)}
642
+ </span>
643
+ <span class="${customClasses.tooltip.secondaryInfo}">
644
+ <strong>Comprimento:</strong> ${esc(formatLength(d.length, units.length))} ${esc(units.length)}
645
+ </span>
646
+ `,
647
+ cave: (_, d) => `
648
+ <span class="${customClasses.tooltip.title}">CAVERNA</span>
649
+ <span class="${customClasses.tooltip.primaryInfo}">De ${esc(formatLength(d.from, units.length))} ${esc(units.length)} at\xE9 ${esc(formatLength(d.to, units.length))} ${esc(units.length)}</span>
650
+ ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Entrada d'\xE1gua</strong></span>` : ""}
651
+ ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>` : ""}
652
+ `
653
+ };
654
+ const noop = { show: () => {
655
+ }, hide: () => {
656
+ } };
657
+ const tooltips = {};
658
+ Object.getOwnPropertyNames(tipsText).forEach((tipTextKey) => {
659
+ const enabled = tooltipConfig === void 0 ? true : tooltipConfig !== false && tooltipConfig.includes(tipTextKey);
660
+ if (!enabled) {
661
+ tooltips[tipTextKey] = noop;
662
+ return;
663
+ }
664
+ tooltips[tipTextKey] = d3.tip().attr("class", customClasses.tooltip.root).direction("e").html(tipsText[tipTextKey]);
665
+ svg.call(
666
+ tooltips[tipTextKey]
667
+ );
668
+ });
669
+ svgEl?.setAttribute("data-tooltips-init", "true");
670
+ return tooltips;
671
+ };
672
+
673
+ // src/utils/render.utils.ts
674
+ var _fgdcTextures = {};
675
+ var _fgdcTexturesPromise = null;
676
+ var preloadFgdcTextures = () => {
677
+ if (!_fgdcTexturesPromise) {
678
+ _fgdcTexturesPromise = importFgdcTextures().then((t) => {
679
+ _fgdcTextures = t;
680
+ });
681
+ }
682
+ return _fgdcTexturesPromise;
683
+ };
684
+ var getLithologicalFillList = (data, opts) => {
685
+ const uniqueTextureCodes = [...new Set(data.map((d) => d.fgdc_texture))];
686
+ const texturesLoaded = Object.fromEntries(
687
+ uniqueTextureCodes.filter((code) => _fgdcTextures[code]).map((code) => [code, _fgdcTextures[code]])
688
+ );
689
+ return Object.fromEntries(
690
+ data.map((d) => [
691
+ `${d.fgdc_texture}.${d.from}`,
692
+ textures2.paths().d(() => texturesLoaded[d.fgdc_texture]).size(opts.size).strokeWidth(opts.strokeWidth).stroke(opts.stroke).background(d.color)
693
+ ])
694
+ );
695
+ };
696
+ var getLithologyFill = (geologyData, svg, opts) => {
697
+ const lithologicalFill = getLithologicalFillList(geologyData, opts);
698
+ return (d) => {
699
+ const fill = lithologicalFill[`${d.fgdc_texture}.${d.from}`];
700
+ if (!fill.url) return fill;
701
+ svg.call(fill);
702
+ return fill.url();
703
+ };
704
+ };
705
+ var getConflictAreas = (array1, array2) => array1.flatMap(
706
+ (item1) => array2.filter((item2) => item2.from < item1.to && item2.to > item1.from).map((item2) => ({
707
+ from: Math.max(item2.from, item1.from),
708
+ to: Math.min(item2.to, item1.to),
709
+ diameter: Math.max(item1.diameter, item2.diameter)
710
+ }))
711
+ );
712
+ var mergeConflicts = (conflicts, buffer) => {
713
+ if (conflicts.length === 0) return [];
714
+ return [...conflicts].sort((a, b) => a.from - b.from).reduce((merged, current) => {
715
+ const last = merged[merged.length - 1];
716
+ if (last && current.from <= last.to + buffer) {
717
+ last.to = Math.max(last.to, current.to);
718
+ return merged;
719
+ }
720
+ merged.push({ ...current });
721
+ return merged;
722
+ }, []);
723
+ };
724
+ function wrapText(text, maxChars) {
725
+ const words = text.split(" ");
726
+ const lines = [];
727
+ let line = "";
728
+ for (const word of words) {
729
+ const candidate = line ? `${line} ${word}` : word;
730
+ if (candidate.length > maxChars && line) {
731
+ lines.push(line);
732
+ line = word;
733
+ } else {
734
+ line = candidate;
735
+ }
736
+ }
737
+ if (line) lines.push(line);
738
+ return lines.length ? lines : [""];
739
+ }
740
+
741
+ // src/renderers/annotation-labels.renderer.ts
742
+ function activeLabelsSet(cfg) {
743
+ if (cfg === false) return /* @__PURE__ */ new Set();
744
+ if (cfg === true) return /* @__PURE__ */ new Set(["lithology", "fractures", "caves"]);
745
+ return new Set(cfg);
746
+ }
747
+ function lithologySubSet(cfg) {
748
+ if (cfg === false) return /* @__PURE__ */ new Set();
749
+ if (!cfg || cfg === true)
750
+ return /* @__PURE__ */ new Set(["depth", "description", "dividers"]);
751
+ return new Set(cfg);
752
+ }
753
+ function drawAnnotationLabels(ctx, data) {
754
+ if (ctx.renderConfig.labels.active === false) return;
755
+ const active = activeLabelsSet(ctx.renderConfig.labels.active);
756
+ const show = lithologySubSet(ctx.renderConfig.labels.lithology);
757
+ const s = ctx.renderConfig.labels;
758
+ const st = ctx.theme.labels;
759
+ ctx.groups.lithologyLabelsGroup.selectAll("*").remove();
760
+ if (active.has("lithology") && show.has("depth")) {
761
+ data.lithology.filter((d) => d.to <= ctx.depthTo).forEach((d) => {
762
+ const text = `${d.to}`;
763
+ const approxW = text.length * (st.fontSize * 0.52) + s.depthTipPadX * 2;
764
+ const y = ctx.yScale(d.to) - s.depthTipHeight / 2;
765
+ const tipG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.depth);
766
+ tipG.append("rect").attr("x", ctx.geoXLeft + 1).attr("y", y).attr("width", approxW).attr("height", s.depthTipHeight).attr("rx", st.depthTipRadius ?? 2).attr("fill", st.depthTipFill ?? "white").attr("stroke", ctx.theme.lithology.stroke).attr("stroke-width", 0.5);
767
+ tipG.append("text").attr("class", "wp-depth-tip-text").attr("fill", ctx.theme.labels.color).attr("font-family", ctx.theme.labels.bodyFont ?? "sans-serif").attr("x", ctx.geoXLeft + 1 + s.depthTipPadX).attr("y", y + s.depthTipHeight / 2).attr("dominant-baseline", "middle").attr("font-size", st.fontSize).text(text);
768
+ });
769
+ }
770
+ const items = [];
771
+ if (active.has("lithology")) {
772
+ data.lithology.filter((d) => d.from < ctx.depthTo).forEach((d) => {
773
+ items.push({
774
+ sortDepth: d.from,
775
+ baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
776
+ originX: ctx.geoXRight,
777
+ originY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
778
+ header: `${d.from}\u2013${d.to} m`,
779
+ description: d.description || ""
780
+ });
781
+ });
782
+ }
783
+ if (active.has("fractures")) {
784
+ data.fractures.filter((d) => d.depth >= ctx.depthFrom && d.depth < ctx.depthTo).forEach((d) => {
785
+ const typeLabel = d.water_intake ? "fratura aberta" : "fratura";
786
+ items.push({
787
+ sortDepth: d.depth,
788
+ baseY: ctx.yScale(d.depth),
789
+ originX: ctx.pocoCenterX,
790
+ originY: ctx.yScale(d.depth),
791
+ header: `${d.depth} m \xB7 ${typeLabel}`,
792
+ description: d.description || ""
793
+ });
794
+ });
795
+ }
796
+ if (active.has("caves")) {
797
+ data.caves.filter((d) => d.from < ctx.depthTo).forEach((d) => {
798
+ const midDepth = (d.from + d.to) / 2;
799
+ const typeLabel = d.water_intake ? "caverna \xFAmida" : "caverna";
800
+ items.push({
801
+ sortDepth: d.from,
802
+ baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
803
+ originX: ctx.geoXRight,
804
+ originY: ctx.yScale(Math.max(midDepth, ctx.depthFrom)),
805
+ header: `${d.from}\u2013${d.to} m \xB7 ${typeLabel}`,
806
+ description: d.description || ""
807
+ });
808
+ });
809
+ }
810
+ items.sort((a, b) => a.sortDepth - b.sortDepth);
811
+ const labelPositions = [];
812
+ let currY = 0;
813
+ const charsPerLine = Math.max(
814
+ 1,
815
+ Math.floor(s.descriptionMaxWidth / (st.fontSize * 0.6))
816
+ );
817
+ items.forEach((item) => {
818
+ const descLines = Math.ceil((item.description.length || 1) / charsPerLine);
819
+ const estH = (1 + descLines) * s.stackingLineHeight + 4;
820
+ const yLabel = Math.max(item.baseY, currY + s.stackingGap);
821
+ currY = yLabel + estH;
822
+ labelPositions.push({ item, yLabel, estH });
823
+ });
824
+ const labelX = ctx.geoXRight + s.descriptionXOffset;
825
+ if (show.has("dividers") && show.has("description")) {
826
+ labelPositions.forEach(({ item, yLabel }) => {
827
+ ctx.groups.lithologyLabelsGroup.append("path").attr("class", ctx.classes.labels.lithology.divider).attr("fill", "none").attr("stroke", ctx.theme.labels.dividerStroke).attr("stroke-width", ctx.theme.labels.dividerStrokeWidth).attr("stroke-dasharray", ctx.theme.labels.dividerStrokeDasharray).attr(
828
+ "d",
829
+ `M ${item.originX},${item.originY} L ${ctx.geoXRight + 3},${item.originY} L ${labelX},${yLabel}`
830
+ );
831
+ });
832
+ }
833
+ if (show.has("description")) {
834
+ const lineH = s.stackingLineHeight;
835
+ const bgFill = st.annotationBg ?? "white";
836
+ labelPositions.forEach(({ item, yLabel, estH }) => {
837
+ const labelG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.label).attr("transform", `translate(${labelX},${yLabel})`);
838
+ labelG.append("rect").attr("x", 0).attr("y", 0).attr("width", s.descriptionMaxWidth).attr("height", estH).attr("rx", st.annotationRadius ?? 2).attr("fill", bgFill).attr("fill-opacity", st.annotationBgOpacity ?? 0.85).attr("stroke", st.annotationBorderColor ?? "none");
839
+ labelG.append("text").attr("class", "wp-annotation-header").attr("fill", ctx.theme.labels.color).attr("font-weight", st.headerFontWeight ?? 600).attr("font-family", ctx.theme.labels.headerFont).attr("x", 0).attr("y", lineH * 0.85).attr("font-size", st.fontSize).text(item.header);
840
+ if (item.description) {
841
+ const lines = wrapText(item.description, charsPerLine);
842
+ const bodyEl = labelG.append("text").attr("class", "wp-annotation-body").attr("fill", ctx.theme.labels.bodyColor).attr("font-weight", st.bodyFontWeight ?? 400).attr("font-family", ctx.theme.labels.bodyFont ?? "sans-serif").attr("x", 0).attr("y", lineH * 0.85 + lineH).attr("font-size", st.fontSize);
843
+ lines.forEach((line, i) => {
844
+ bodyEl.append("tspan").attr("x", 0).attr("dy", i === 0 ? 0 : lineH).text(line);
845
+ });
846
+ }
847
+ });
848
+ }
849
+ }
850
+
851
+ // src/renderers/caves.renderer.ts
852
+ function drawCaves(ctx, data) {
853
+ if (!data.length) return;
854
+ const {
855
+ groups: { cavesGroup },
856
+ geoXLeft,
857
+ geoXRight,
858
+ depthFrom,
859
+ depthTo,
860
+ yScale,
861
+ renderConfig,
862
+ theme,
863
+ textures: textures3,
864
+ classes,
865
+ tooltips
866
+ } = ctx;
867
+ cavesGroup.selectAll(`g.${classes.caves.item}`).remove();
868
+ const rc = renderConfig.caves;
869
+ data.forEach((cave) => {
870
+ const seedTop = cave.from * 100 + cave.to;
871
+ const seedBot = cave.to * 100 + cave.from + 999;
872
+ const rngTop = makeSeededPrng(seedTop);
873
+ const rngBot = makeSeededPrng(seedBot);
874
+ const yTop = yScale(Math.max(cave.from, depthFrom));
875
+ const yBot = yScale(Math.min(cave.to, depthTo));
876
+ const span = yBot - yTop;
877
+ const amp = Math.max(
878
+ rc.amplitude.min,
879
+ Math.min(span * rc.amplitude.ratio, rc.amplitude.max)
880
+ );
881
+ const topCut = cave.from < depthFrom;
882
+ const botCut = cave.to > depthTo;
883
+ const topPts = topCut ? [
884
+ [geoXLeft, yTop],
885
+ [geoXRight, yTop]
886
+ ] : wavyContact(geoXLeft, geoXRight, yTop, amp, rc.pathSteps, rngTop);
887
+ const botPts = botCut ? [
888
+ [geoXLeft, yBot],
889
+ [geoXRight, yBot]
890
+ ] : wavyContact(geoXLeft, geoXRight, yBot, amp, rc.pathSteps, rngBot);
891
+ const topPath = topCut ? `M ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} L ${geoXRight.toFixed(1)},${yTop.toFixed(1)}` : ptsToSmoothPath(topPts);
892
+ const botPath = botCut ? `M ${geoXLeft.toFixed(1)},${yBot.toFixed(1)} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath(botPts);
893
+ const botReversed = botCut ? `L ${geoXLeft.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath([...botPts].reverse(), { omitMoveTo: true });
894
+ const closedPath = `${topPath} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}${botReversed} L ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} Z`;
895
+ const caveTexture = cave.water_intake ? textures3.cave_wet : textures3.cave_dry;
896
+ const g = cavesGroup.append("g").attr("class", classes.caves.item).datum(cave).style("cursor", "pointer").on("mouseover", tooltips.cave.show).on("mouseout", tooltips.cave.hide);
897
+ const caveStroke = cave.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
898
+ g.append("path").attr("class", classes.caves.fill).attr("d", closedPath).attr("fill", caveTexture.url()).attr("fill-opacity", theme.cave.fillOpacity).attr("stroke", "none");
899
+ g.append("path").attr("class", classes.caves.contact).attr("d", topPath).attr("fill", "none").attr("stroke", caveStroke).attr("stroke-width", theme.cave.contactStrokeWidth).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
900
+ g.append("path").attr("class", classes.caves.contact).attr("d", botPath).attr("fill", "none").attr("stroke", caveStroke).attr("stroke-width", theme.cave.contactStrokeWidth).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
901
+ });
902
+ }
903
+
904
+ // src/renderers/construction-labels.renderer.ts
905
+ import * as d32 from "d3";
906
+ import { getProfileDiamValues } from "@welldot/utils";
907
+ function drawConstructionLabels(ctx, data) {
908
+ if (!ctx.renderConfig.constructionLabels.active) return;
909
+ ctx.groups.constructionLabelsGroup.selectAll("*").remove();
910
+ const clc = ctx.renderConfig.constructionLabels;
911
+ const clct = ctx.theme.constructionLabels;
912
+ const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter);
913
+ const dUnit = getDiameterUnit(ctx.units.diameter);
914
+ const maxXValues = getProfileDiamValues(ctx.constructionData);
915
+ const xScale = d32.scaleLinear().domain([0, d32.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
916
+ const occupied = [];
917
+ const placeLabel = (d, text) => {
918
+ const clampedFrom = Math.max(d.from, ctx.depthFrom);
919
+ const clampedTo = Math.min(d.to, ctx.depthTo);
920
+ if (clampedFrom >= clampedTo) return;
921
+ const midY = ctx.yScale((clampedFrom + clampedTo) / 2);
922
+ const leftEdgeX = (ctx.POCO_CENTER - xScale(d.diameter)) / 2;
923
+ const labelRightX = leftEdgeX - clc.xOffset;
924
+ const charW = clct.fontSize * 0.52;
925
+ const lineH = clct.fontSize * 1.35;
926
+ const vertPad = 6;
927
+ let lines;
928
+ let boxW;
929
+ if (clc.labelMaxWidth !== void 0) {
930
+ const maxChars = Math.max(
931
+ 1,
932
+ Math.floor((clc.labelMaxWidth - 8) / charW)
933
+ );
934
+ lines = wrapText(text, maxChars);
935
+ boxW = clc.labelMaxWidth;
936
+ } else {
937
+ lines = [text];
938
+ boxW = text.length * charW + 8;
939
+ }
940
+ const labelH = lines.length * lineH + vertPad;
941
+ let labelY = midY - labelH / 2;
942
+ for (const pos of occupied) {
943
+ if (labelY < pos.bot && labelY + labelH > pos.top) labelY = pos.bot + 2;
944
+ }
945
+ occupied.push({ top: labelY, bot: labelY + labelH });
946
+ ctx.groups.constructionLabelsGroup.append("rect").attr("x", labelRightX - boxW).attr("y", labelY).attr("width", boxW).attr("height", labelH).attr("rx", clc.labelRadius).attr("fill", clct.labelFill ?? "white").attr("stroke", clct.labelColor ?? "#303030").attr("stroke-width", 0.5);
947
+ const textEl = ctx.groups.constructionLabelsGroup.append("text").attr("x", labelRightX - 4).attr("y", labelY + lineH * 0.85).attr("font-size", clct.fontSize).attr("font-family", clct.fontFamily ?? "sans-serif").attr("font-weight", clct.fontWeight ?? 400).attr("text-anchor", "end").attr("fill", clct.labelColor ?? "#303030");
948
+ lines.forEach((line, i) => {
949
+ textEl.append("tspan").attr("x", labelRightX - 4).attr("dy", i === 0 ? 0 : lineH).text(line);
950
+ });
951
+ };
952
+ let lastDiam = 0;
953
+ data.well_case.filter((item) => {
954
+ if (item.diameter !== lastDiam) {
955
+ lastDiam = item.diameter;
956
+ return true;
957
+ }
958
+ return false;
959
+ }).forEach(
960
+ (d) => placeLabel(
961
+ d,
962
+ `${clc.labels.wellCasePrefix} ${fmtD(d.diameter)}${dUnit} ${d.type}`
963
+ )
964
+ );
965
+ lastDiam = 0;
966
+ data.well_screen.filter((item) => {
967
+ if (item.diameter !== lastDiam) {
968
+ lastDiam = item.diameter;
969
+ return true;
970
+ }
971
+ return false;
972
+ }).forEach(
973
+ (d) => placeLabel(
974
+ d,
975
+ `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${d.type} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot_mm)}${dUnit}`
976
+ )
977
+ );
978
+ }
979
+
980
+ // src/renderers/construction.renderer.ts
981
+ import * as d33 from "d3";
982
+ import { getProfileDiamValues as getProfileDiamValues2 } from "@welldot/utils";
983
+ function drawConstructive(ctx, data) {
984
+ const { getHeight, getYPos } = getYAxisFunctions(ctx.yScale, {
985
+ from: ctx.depthFrom,
986
+ to: ctx.depthTo
987
+ });
988
+ const maxXValues = getProfileDiamValues2(ctx.constructionData);
989
+ const maxXValueConstruction = d33.max(maxXValues) || 0;
990
+ const xScale = d33.scaleLinear().domain([0, maxXValueConstruction]).range([0, ctx.POCO_WIDTH]);
991
+ const rcc = ctx.renderConfig.construction;
992
+ ctx.groups.constructionGroup.selectAll(`.${ctx.classes.cementPad.item}`).remove();
993
+ if (data.cement_pad && data.cement_pad.thickness && ctx.depthFrom === 0) {
994
+ const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad]);
995
+ cementPad.exit().remove();
996
+ const newCementPad = cementPad.enter().append("rect").attr("class", ctx.classes.cementPad.item).attr(
997
+ "x",
998
+ (d) => (ctx.POCO_CENTER - xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)) / 2
999
+ ).attr("y", (d) => {
1000
+ return ctx.yScale(0) - ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1001
+ }).attr(
1002
+ "width",
1003
+ (d) => xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)
1004
+ ).attr("height", (d) => {
1005
+ return ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1006
+ }).attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth);
1007
+ newCementPad.on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1008
+ }
1009
+ const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole);
1010
+ hole.exit().remove();
1011
+ const newHole = hole.enter().append("rect").attr("class", ctx.classes.boreHole.rect).attr("fill", ctx.theme.boreHole.fill).attr("stroke", ctx.theme.boreHole.stroke).attr("opacity", ctx.theme.boreHole.opacity).attr("stroke-width", ctx.theme.boreHole.strokeWidth).attr("stroke-dasharray", ctx.theme.boreHole.strokeDasharray).on("mouseover", ctx.tooltips.hole.show).on("mouseout", ctx.tooltips.hole.hide);
1012
+ const mergedHole = mergeEnter(newHole, hole).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1013
+ withTransition(mergedHole, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1014
+ const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case);
1015
+ surfaceCaseGs.exit().transition(ctx.transition).style("opacity", 0).remove();
1016
+ const newSC = surfaceCaseGs.enter().append("g").attr("class", ctx.classes.surfaceCase.rect).on("mouseover", ctx.tooltips.surfaceCase.show).on("mouseout", ctx.tooltips.surfaceCase.hide);
1017
+ newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none");
1018
+ newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1019
+ newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1020
+ const mergedSC = mergeEnter(newSC, surfaceCaseGs);
1021
+ mergedSC.each((d, i, nodes) => {
1022
+ const x = (ctx.POCO_CENTER - xScale(
1023
+ d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1024
+ )) / 2;
1025
+ const w = xScale(
1026
+ d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1027
+ );
1028
+ const g = d33.select(nodes[i]);
1029
+ g.select(".surface-case-fill").attr("x", x).attr("width", w).attr("fill", ctx.textures.surface_case.url());
1030
+ const sideNodes = g.selectAll(".surface-case-side").nodes();
1031
+ d33.select(sideNodes[0]).attr("x1", x).attr("x2", x);
1032
+ d33.select(sideNodes[1]).attr("x1", x + w).attr("x2", x + w);
1033
+ });
1034
+ withTransition(
1035
+ mergedSC.select(".surface-case-fill"),
1036
+ ctx.transition
1037
+ ).attr("y", (d) => getYPos(d)).attr("height", (d) => getHeight(d));
1038
+ mergedSC.selectAll(".surface-case-side").attr("y1", (d) => getYPos(d)).attr(
1039
+ "y2",
1040
+ (d) => getYPos(d) + getHeight(d)
1041
+ );
1042
+ const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill);
1043
+ holeFill.exit().remove();
1044
+ const newHoleFill = holeFill.enter().append("rect").attr("class", ctx.classes.holeFill.rect).attr("stroke", ctx.theme.holeFill.stroke).attr("stroke-width", ctx.theme.holeFill.strokeWidth).on("mouseover", ctx.tooltips.holeFill.show).on("mouseout", ctx.tooltips.holeFill.hide);
1045
+ const mergedHoleFill = mergeEnter(newHoleFill, holeFill).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1046
+ withTransition(mergedHoleFill, ctx.transition).attr("y", getYPos).attr("height", getHeight).attr("fill", (d) => ctx.textures[d.type].url());
1047
+ const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case);
1048
+ wellCase.exit().remove();
1049
+ const newWellCase = wellCase.enter().append("rect").attr("class", ctx.classes.wellCase.rect).attr("fill", ctx.theme.wellCase.fill).attr("stroke", ctx.theme.wellCase.stroke).attr("stroke-width", ctx.theme.wellCase.strokeWidth).on("mouseover", ctx.tooltips.wellCase.show).on("mouseout", ctx.tooltips.wellCase.hide);
1050
+ const mergedWellCase = mergeEnter(newWellCase, wellCase).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1051
+ withTransition(mergedWellCase, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1052
+ const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen);
1053
+ wellScreen.exit().remove();
1054
+ const newWellScreen = wellScreen.enter().append("rect").attr("class", ctx.classes.wellScreen.rect).attr("fill", ctx.textures.well_screen.url()).attr("stroke", ctx.theme.wellScreen.stroke).attr("stroke-width", ctx.theme.wellScreen.strokeWidth).on("mouseover", ctx.tooltips.wellScreen.show).on("mouseout", ctx.tooltips.wellScreen.hide);
1055
+ const mergedWellScreen = mergeEnter(newWellScreen, wellScreen).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1056
+ withTransition(mergedWellScreen, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1057
+ const conflictAreas = [];
1058
+ conflictAreas.push(...getConflictAreas(data.well_case, data.well_screen));
1059
+ conflictAreas.push(...getConflictAreas(data.well_screen, data.well_case));
1060
+ const mergedConflicts = mergeConflicts(conflictAreas, 1);
1061
+ const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts);
1062
+ conflict.exit().remove();
1063
+ const newConflict = conflict.enter().append("rect").attr("class", ctx.classes.conflict.rect).attr("fill", ctx.textures.conflict.url()).attr("stroke", ctx.theme.conflict.stroke).attr("stroke-width", ctx.theme.conflict.strokeWidth).on("mouseover", ctx.tooltips.conflict.show).on("mouseout", ctx.tooltips.conflict.hide);
1064
+ const mergedConflict = mergeEnter(newConflict, conflict).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1065
+ withTransition(mergedConflict, ctx.transition).attr("y", ctx.yScale(0)).attr("height", getHeight);
1066
+ }
1067
+
1068
+ // src/renderers/fractures.renderer.ts
1069
+ function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1070
+ const pts = [];
1071
+ for (let i = 0; i < steps; i++) {
1072
+ const t = i / (steps - 1);
1073
+ const normX = startInset + t * (1 - startInset - endInset);
1074
+ const yOffset = baseY + (rng() * 2 - 1) * jitter;
1075
+ pts.push([normX, yOffset]);
1076
+ }
1077
+ return pts;
1078
+ }
1079
+ function drawFractures(ctx, data) {
1080
+ if (!data.length) return;
1081
+ const {
1082
+ groups: { fracturesGroup },
1083
+ POCO_WIDTH,
1084
+ pocoCenterX,
1085
+ yScale,
1086
+ renderConfig,
1087
+ theme,
1088
+ classes,
1089
+ tooltips
1090
+ } = ctx;
1091
+ fracturesGroup.selectAll(`g.${classes.fractures.item}`).remove();
1092
+ const rf = renderConfig.fractures;
1093
+ const halfWidth = POCO_WIDTH * rf.widthMultiplier / 2;
1094
+ const xa = pocoCenterX - halfWidth;
1095
+ const w = halfWidth * 2;
1096
+ const normToAbsX = (normX) => xa + normX * w;
1097
+ data.forEach((fracture) => {
1098
+ const rng = makeSeededPrng(fracture.depth, 7919);
1099
+ const cy = yScale(fracture.depth);
1100
+ const g = fracturesGroup.append("g").attr("class", classes.fractures.item).datum(fracture).attr(
1101
+ "transform",
1102
+ `translate(0,${cy}) rotate(${fracture.dip},${pocoCenterX},0)`
1103
+ ).on("mouseover", tooltips.fracture.show).on("mouseout", tooltips.fracture.hide).style("cursor", "pointer");
1104
+ const hitBuffer = fracture.swarm ? rf.hitBuffer.swarm : rf.hitBuffer.single;
1105
+ g.append("rect").attr("class", classes.fractures.hitArea).attr("x", xa).attr("y", -hitBuffer).attr("width", w).attr("height", hitBuffer * 2).attr("fill", "transparent").style("pointer-events", "all");
1106
+ const fractureStroke = fracture.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
1107
+ const appendLine = (x1, y1, x2, y2, sw) => g.append("line").attr("class", classes.fractures.line).attr("x1", x1).attr("y1", y1).attr("x2", x2).attr("y2", y2).attr("stroke", fractureStroke).attr("stroke-width", sw).attr("stroke-linecap", "round");
1108
+ const appendPolyline = (pts, sw) => g.append("polyline").attr("class", classes.fractures.polyline).attr(
1109
+ "points",
1110
+ pts.map(([nx, dy]) => `${normToAbsX(nx)},${dy}`).join(" ")
1111
+ ).attr("stroke", fractureStroke).attr("stroke-width", sw).attr("fill", "none").attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
1112
+ if (fracture.swarm) {
1113
+ const lineCount = rf.swarm.lineCountBase + Math.round(rng() * rf.swarm.lineCountVariance);
1114
+ const halfSpread = rf.swarm.spread / 2;
1115
+ const bases = Array.from(
1116
+ { length: lineCount },
1117
+ () => (rng() * 2 - 1) * halfSpread
1118
+ ).sort((a, b) => a - b);
1119
+ bases.forEach((base, idx) => {
1120
+ const isCentral = idx === Math.floor(lineCount / 2);
1121
+ const sw = isCentral ? theme.fracture.swarm.centralStrokeWidth : theme.fracture.swarm.sideStrokeWidthBase + rng() * theme.fracture.swarm.sideStrokeWidthVariance;
1122
+ const steps = 6 + Math.round(rng() * 3);
1123
+ const jitter = 0.8 + rng() * 1.2;
1124
+ const insetL = 0.04 + rng() * 0.12;
1125
+ const insetR = 0.04 + rng() * 0.12;
1126
+ appendPolyline(wavyLine(rng, steps, base, jitter, insetL, insetR), sw);
1127
+ });
1128
+ const bridgeCount = 2 + Math.round(rng() * 2);
1129
+ for (let b = 0; b < bridgeCount; b++) {
1130
+ const nx = 0.15 + rng() * 0.7;
1131
+ const pairIdx = Math.floor(rng() * (bases.length - 1));
1132
+ appendLine(
1133
+ normToAbsX(nx),
1134
+ bases[pairIdx],
1135
+ normToAbsX(nx + (rng() - 0.5) * 0.06),
1136
+ bases[pairIdx + 1],
1137
+ 0.6
1138
+ );
1139
+ }
1140
+ const primaryBase = bases[Math.floor(lineCount / 2)];
1141
+ for (let wc = 0; wc < 2; wc++) {
1142
+ const nx = 0.2 + rng() * 0.6;
1143
+ const len = 3 + rng() * 3;
1144
+ const dir = rng() > 0.5 ? -1 : 1;
1145
+ appendLine(
1146
+ normToAbsX(nx),
1147
+ primaryBase,
1148
+ normToAbsX(nx + (rng() - 0.5) * 0.04),
1149
+ primaryBase + dir * len,
1150
+ 0.8
1151
+ );
1152
+ }
1153
+ } else {
1154
+ const steps = 7 + Math.round(rng() * 3);
1155
+ const insetL = 0.03 + rng() * 0.08;
1156
+ const insetR = 0.03 + rng() * 0.08;
1157
+ appendPolyline(
1158
+ wavyLine(rng, steps, 0, 2, insetL, insetR),
1159
+ theme.fracture.single.mainStrokeWidth
1160
+ );
1161
+ const crackCount = 2 + Math.round(rng() * 2);
1162
+ for (let c = 0; c < crackCount; c++) {
1163
+ const nx = insetL + rng() * (1 - insetL - insetR);
1164
+ const len = 3.5 + rng() * 3.5;
1165
+ const dir = rng() > 0.5 ? 1 : -1;
1166
+ appendLine(
1167
+ normToAbsX(nx),
1168
+ (rng() * 2 - 1) * 1.5,
1169
+ normToAbsX(nx + (rng() - 0.5) * 0.03),
1170
+ dir * len,
1171
+ theme.fracture.single.crackStrokeWidth
1172
+ );
1173
+ }
1174
+ }
1175
+ });
1176
+ }
1177
+
1178
+ // src/renderers/highlights.renderer.ts
1179
+ import * as d34 from "d3";
1180
+ import { getProfileDiamValues as getProfileDiamValues3 } from "@welldot/utils";
1181
+ function drawHighlightItem(ctx, group, x, y, w, h, label) {
1182
+ if (h <= 0 || w <= 0) return;
1183
+ const hlConfig = ctx.renderConfig.highlights;
1184
+ const pad = hlConfig.padding;
1185
+ const g = group.append("g").attr("class", ctx.classes.highlights.item);
1186
+ g.append("rect").attr("class", ctx.classes.highlights.rect).attr("x", x - pad).attr("y", y - pad).attr("width", w + pad * 2).attr("height", h + pad * 2).attr("fill", hlConfig.fill).attr("fill-opacity", hlConfig.fillOpacity).attr("stroke", hlConfig.stroke).attr("stroke-width", hlConfig.strokeWidth).attr("stroke-dasharray", hlConfig.strokeDasharray ?? null).style("pointer-events", "none");
1187
+ if (label) {
1188
+ const charW = hlConfig.labelFontSize * 0.55;
1189
+ const labelW = label.length * charW + hlConfig.labelPadding * 2;
1190
+ const labelH = hlConfig.labelFontSize + hlConfig.labelPadding * 2;
1191
+ const lx = x - pad;
1192
+ const ly = y - pad - labelH;
1193
+ g.append("rect").attr("class", ctx.classes.highlights.labelBg).attr("x", lx).attr("y", ly).attr("width", labelW).attr("height", labelH).attr("rx", hlConfig.labelRadius).attr("fill", hlConfig.labelBackground).style("pointer-events", "none");
1194
+ g.append("text").attr("class", ctx.classes.highlights.label).attr("x", lx + hlConfig.labelPadding).attr("y", ly + labelH - hlConfig.labelPadding).attr("font-size", hlConfig.labelFontSize).attr("fill", hlConfig.labelColor).attr("font-family", "sans-serif").style("pointer-events", "none").text(label);
1195
+ }
1196
+ }
1197
+ function drawHighlights(ctx, hl) {
1198
+ ctx.groups.highlightsGeologicGroup.selectAll("*").remove();
1199
+ ctx.groups.highlightsConstructionGroup.selectAll("*").remove();
1200
+ ctx.groups.highlightsFracturesGroup.selectAll("*").remove();
1201
+ const drawGeoHighlights = (items) => {
1202
+ items?.forEach((h) => {
1203
+ if (h.from === void 0 || h.to === void 0) return;
1204
+ const from = Math.max(h.from, ctx.depthFrom);
1205
+ const to = Math.min(h.to, ctx.depthTo);
1206
+ if (from >= to) return;
1207
+ const y = ctx.yScale(from);
1208
+ drawHighlightItem(
1209
+ ctx,
1210
+ ctx.groups.highlightsGeologicGroup,
1211
+ ctx.geoXLeft,
1212
+ y,
1213
+ ctx.geoXRight - ctx.geoXLeft,
1214
+ ctx.yScale(to) - y,
1215
+ h.label
1216
+ );
1217
+ });
1218
+ };
1219
+ drawGeoHighlights(hl.lithology);
1220
+ drawGeoHighlights(hl.caves);
1221
+ const fractureConfig = ctx.renderConfig.fractures;
1222
+ const halfFractureWidth = ctx.POCO_WIDTH * fractureConfig.widthMultiplier / 2;
1223
+ const fracXa = ctx.pocoCenterX - halfFractureWidth;
1224
+ const fracW = halfFractureWidth * 2;
1225
+ hl.fractures?.forEach((h) => {
1226
+ if (h.depth === void 0) return;
1227
+ if (h.depth < ctx.depthFrom || h.depth > ctx.depthTo) return;
1228
+ const cy = ctx.yScale(h.depth);
1229
+ drawHighlightItem(
1230
+ ctx,
1231
+ ctx.groups.highlightsFracturesGroup,
1232
+ fracXa,
1233
+ cy - fractureConfig.hitBuffer.single,
1234
+ fracW,
1235
+ fractureConfig.hitBuffer.single * 2,
1236
+ h.label
1237
+ );
1238
+ });
1239
+ const maxXVals = getProfileDiamValues3(ctx.constructionData);
1240
+ const xScaleConstr = d34.scaleLinear().domain([0, d34.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1241
+ const constructionConfig = ctx.renderConfig.construction;
1242
+ if (hl.cement_pad && ctx.depthFrom === 0 && ctx.constructionData.cement_pad?.thickness) {
1243
+ const cp = ctx.constructionData.cement_pad;
1244
+ const cpPadWidth = cp.width * constructionConfig.cementPad.widthMultiplier * 1e3 / 2;
1245
+ const cpX = (ctx.POCO_CENTER - xScaleConstr(cpPadWidth)) / 2;
1246
+ const cpY = ctx.yScale(0) - ctx.yScale(
1247
+ cp.thickness * constructionConfig.cementPad.thicknessMultiplier
1248
+ );
1249
+ const cpW = xScaleConstr(cpPadWidth);
1250
+ const cpH = ctx.yScale(
1251
+ cp.thickness * constructionConfig.cementPad.thicknessMultiplier
1252
+ );
1253
+ drawHighlightItem(
1254
+ ctx,
1255
+ ctx.groups.highlightsConstructionGroup,
1256
+ cpX,
1257
+ cpY,
1258
+ cpW,
1259
+ cpH,
1260
+ hl.cement_pad.label
1261
+ );
1262
+ }
1263
+ const drawConstrHighlights = (items, data, getX, getW) => {
1264
+ items?.forEach((h) => {
1265
+ if (h.from === void 0 || h.to === void 0) return;
1266
+ data.filter(
1267
+ (d) => d.from < h.to && d.to > h.from && (h.diameter === void 0 || d.diameter === h.diameter)
1268
+ ).forEach((d) => {
1269
+ const from = Math.max(h.from, ctx.depthFrom);
1270
+ const to = Math.min(h.to, ctx.depthTo);
1271
+ if (from >= to) return;
1272
+ const y = ctx.yScale(from);
1273
+ drawHighlightItem(
1274
+ ctx,
1275
+ ctx.groups.highlightsConstructionGroup,
1276
+ getX(d),
1277
+ y,
1278
+ getW(d),
1279
+ ctx.yScale(to) - y,
1280
+ h.label
1281
+ );
1282
+ });
1283
+ });
1284
+ };
1285
+ const stdX = (d) => (ctx.POCO_CENTER - xScaleConstr(d.diameter)) / 2;
1286
+ const stdW = (d) => xScaleConstr(d.diameter);
1287
+ const scPaddingRatio = constructionConfig.surfaceCase.diameterPaddingRatio;
1288
+ const scX = (d) => (ctx.POCO_CENTER - xScaleConstr(d.diameter + d.diameter * scPaddingRatio)) / 2;
1289
+ const scW = (d) => xScaleConstr(d.diameter + d.diameter * scPaddingRatio);
1290
+ drawConstrHighlights(
1291
+ hl.bore_hole,
1292
+ ctx.constructionData.bore_hole,
1293
+ stdX,
1294
+ stdW
1295
+ );
1296
+ drawConstrHighlights(
1297
+ hl.surface_case,
1298
+ ctx.constructionData.surface_case,
1299
+ scX,
1300
+ scW
1301
+ );
1302
+ drawConstrHighlights(
1303
+ hl.hole_fill,
1304
+ ctx.constructionData.hole_fill,
1305
+ stdX,
1306
+ stdW
1307
+ );
1308
+ drawConstrHighlights(
1309
+ hl.well_case,
1310
+ ctx.constructionData.well_case,
1311
+ stdX,
1312
+ stdW
1313
+ );
1314
+ drawConstrHighlights(
1315
+ hl.well_screen,
1316
+ ctx.constructionData.well_screen,
1317
+ stdX,
1318
+ stdW
1319
+ );
1320
+ }
1321
+
1322
+ // src/renderers/legend.renderer.ts
1323
+ import * as d3module2 from "d3";
1324
+ import { defu } from "defu";
1325
+ var d35 = { ...d3module2 };
1326
+ function drawWellLegend(selector, profile, options = {}) {
1327
+ const fractures = profile.fractures ?? [];
1328
+ const caves = profile.caves ?? [];
1329
+ const boreHoles = profile.bore_hole ?? [];
1330
+ const wellCases = profile.well_case ?? [];
1331
+ const wellScreens = profile.well_screen ?? [];
1332
+ const holeFills = profile.hole_fill ?? [];
1333
+ const hasSimple = fractures.some((f) => !f.swarm && !f.water_intake);
1334
+ const hasSwarm = fractures.some((f) => f.swarm);
1335
+ const hasWaterFx = fractures.some((f) => f.water_intake);
1336
+ const hasCaveDry = caves.some((c) => !c.water_intake);
1337
+ const hasCaveWet = caves.some((c) => c.water_intake);
1338
+ const hasBoreHole = boreHoles.length > 0;
1339
+ const hasSurfaceCase = (profile.surface_case ?? []).length > 0;
1340
+ const hasHoleFillGravel = holeFills.some((h) => h.type === "gravel_pack");
1341
+ const hasHoleFillSeal = holeFills.some((h) => h.type === "seal");
1342
+ const hasWellCase = wellCases.length > 0;
1343
+ const hasWellScreen = wellScreens.length > 0;
1344
+ const hasCementPad = !!profile.cement_pad?.thickness;
1345
+ const hasConflict = mergeConflicts(
1346
+ [
1347
+ ...getConflictAreas(wellCases, wellScreens),
1348
+ ...getConflictAreas(wellScreens, wellCases)
1349
+ ],
1350
+ 1
1351
+ ).length > 0;
1352
+ const cfg = defu(
1353
+ options.config ?? {},
1354
+ INTERACTIVE_RENDER_CONFIG.legend
1355
+ );
1356
+ const theme = defu(options.theme ?? {}, DEFAULT_WELL_THEME);
1357
+ const cls = options.classNames ? { ...DEFAULT_COMPONENTS_CLASS_NAMES.legend, ...options.classNames } : DEFAULT_COMPONENTS_CLASS_NAMES.legend;
1358
+ const items = [];
1359
+ if (hasSimple)
1360
+ items.push({
1361
+ kind: "fracture",
1362
+ label: cfg.labels.fractureSingle,
1363
+ swarm: false,
1364
+ water_intake: false
1365
+ });
1366
+ if (hasSwarm)
1367
+ items.push({
1368
+ kind: "fracture",
1369
+ label: cfg.labels.fractureSwarm,
1370
+ swarm: true,
1371
+ water_intake: false
1372
+ });
1373
+ if (hasWaterFx)
1374
+ items.push({
1375
+ kind: "fracture",
1376
+ label: cfg.labels.fractureWater,
1377
+ swarm: false,
1378
+ water_intake: true
1379
+ });
1380
+ if (hasCaveDry)
1381
+ items.push({
1382
+ kind: "cave",
1383
+ label: cfg.labels.caveDry,
1384
+ water_intake: false
1385
+ });
1386
+ if (hasCaveWet)
1387
+ items.push({ kind: "cave", label: cfg.labels.caveWet, water_intake: true });
1388
+ if (hasCementPad)
1389
+ items.push({
1390
+ kind: "construction",
1391
+ label: cfg.labels.cementPad,
1392
+ subKind: "cementPad"
1393
+ });
1394
+ if (hasBoreHole)
1395
+ items.push({
1396
+ kind: "construction",
1397
+ label: cfg.labels.boreHole,
1398
+ subKind: "boreHole"
1399
+ });
1400
+ if (hasSurfaceCase)
1401
+ items.push({
1402
+ kind: "construction",
1403
+ label: cfg.labels.surfaceCase,
1404
+ subKind: "surfaceCase"
1405
+ });
1406
+ if (hasHoleFillGravel)
1407
+ items.push({
1408
+ kind: "construction",
1409
+ label: cfg.labels.holeFillGravel,
1410
+ subKind: "holeFillGravel"
1411
+ });
1412
+ if (hasHoleFillSeal)
1413
+ items.push({
1414
+ kind: "construction",
1415
+ label: cfg.labels.holeFillSeal,
1416
+ subKind: "holeFillSeal"
1417
+ });
1418
+ if (hasWellCase)
1419
+ items.push({
1420
+ kind: "construction",
1421
+ label: cfg.labels.wellCase,
1422
+ subKind: "wellCase"
1423
+ });
1424
+ if (hasWellScreen)
1425
+ items.push({
1426
+ kind: "construction",
1427
+ label: cfg.labels.wellScreen,
1428
+ subKind: "wellScreen"
1429
+ });
1430
+ if (hasConflict)
1431
+ items.push({
1432
+ kind: "construction",
1433
+ label: cfg.labels.conflict,
1434
+ subKind: "conflict"
1435
+ });
1436
+ const svgEl = d35.select(selector);
1437
+ svgEl.selectAll("*").remove();
1438
+ if (items.length === 0) {
1439
+ svgEl.attr("width", 0).attr("height", 0);
1440
+ return;
1441
+ }
1442
+ const legendTextures = createWellTextures(options.textures);
1443
+ Object.values(legendTextures).forEach((t) => svgEl.call(t));
1444
+ const itemsPerRow = cfg.maxWidth ? Math.max(1, Math.floor((cfg.maxWidth - cfg.padding * 2) / cfg.itemWidth)) : items.length;
1445
+ const numRows = Math.ceil(items.length / itemsPerRow);
1446
+ const rowWidth = Math.min(items.length, itemsPerRow) * cfg.itemWidth;
1447
+ const totalWidth = cfg.padding * 2 + rowWidth;
1448
+ const totalHeight = numRows * cfg.height;
1449
+ svgEl.attr("width", totalWidth).attr("height", totalHeight);
1450
+ const RC = "round";
1451
+ const g = svgEl.append("g").attr("transform", `translate(${cfg.padding}, 2)`);
1452
+ g.append("rect").attr("class", cls.border).attr("x", 0).attr("y", 0).attr("width", totalWidth - cfg.padding).attr("height", totalHeight - 4).attr("fill", "white").attr("rx", cfg.borderRadius).attr("stroke-width", theme.legend.borderStrokeWidth);
1453
+ g.append("text").attr("class", cls.title).attr("x", 4).attr("y", 11).attr("font-size", theme.legend.fontSize).attr("font-family", theme.legend.fontFamily ?? "sans-serif").attr("font-weight", theme.legend.titleFontWeight ?? "bold").text(`${cfg.title}:`);
1454
+ const rw = 24;
1455
+ const rh = 12;
1456
+ items.forEach((item, i) => {
1457
+ const col = i % itemsPerRow;
1458
+ const row = Math.floor(i / itemsPerRow);
1459
+ const rowSymY = row * cfg.height + (cfg.height - 4) / 2;
1460
+ const cx = col * cfg.itemWidth;
1461
+ const symG = g.append("g").attr("class", cls.item);
1462
+ if (item.kind === "fracture") {
1463
+ const color = item.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
1464
+ if (item.swarm) {
1465
+ [-4, 0, 4].forEach((offset) => {
1466
+ symG.append("polyline").attr("class", cls.fracturePoly).attr(
1467
+ "points",
1468
+ `${cx},${rowSymY + offset} ${cx + 6},${rowSymY + offset - 1} ${cx + 12},${rowSymY + offset + 1} ${cx + 18},${rowSymY + offset - 0.5} ${cx + 24},${rowSymY + offset}`
1469
+ ).attr("stroke", color).attr(
1470
+ "stroke-width",
1471
+ offset !== 0 ? theme.legend.fractureSideStrokeWidth : theme.legend.fractureStrokeWidth
1472
+ ).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
1473
+ });
1474
+ } else {
1475
+ symG.append("polyline").attr("class", cls.fracturePoly).attr(
1476
+ "points",
1477
+ `${cx},${rowSymY} ${cx + 5},${rowSymY - 1.5} ${cx + 11},${rowSymY + 1} ${cx + 17},${rowSymY - 0.5} ${cx + 24},${rowSymY}`
1478
+ ).attr("stroke", color).attr("stroke-width", theme.legend.fractureStrokeWidth).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
1479
+ }
1480
+ } else if (item.kind === "cave") {
1481
+ const color = item.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
1482
+ const texture = item.water_intake ? legendTextures.cave_wet : legendTextures.cave_dry;
1483
+ symG.append("rect").attr("class", cls.caveFill).attr("x", cx).attr("y", rowSymY - rh / 2).attr("width", rw).attr("height", rh).attr("fill", texture.url()).attr("stroke", color).attr("stroke-width", theme.legend.itemStrokeWidth);
1484
+ } else if (item.subKind === "surfaceCase") {
1485
+ symG.append("rect").attr("class", cls.constructionRect).attr("x", cx).attr("y", rowSymY - rh / 2).attr("width", rw).attr("height", rh).attr("fill", legendTextures.surface_case.url()).attr("stroke", "none");
1486
+ symG.append("line").attr("class", cls.constructionRect).attr("x1", cx).attr("x2", cx).attr("y1", rowSymY - rh / 2).attr("y2", rowSymY + rh / 2).attr("stroke", theme.surfaceCase.stroke).attr("stroke-width", 2);
1487
+ symG.append("line").attr("class", cls.constructionRect).attr("x1", cx + rw).attr("x2", cx + rw).attr("y1", rowSymY - rh / 2).attr("y2", rowSymY + rh / 2).attr("stroke", theme.surfaceCase.stroke).attr("stroke-width", 2);
1488
+ } else {
1489
+ const symMap = {
1490
+ boreHole: {
1491
+ fill: theme.boreHole.fill,
1492
+ stroke: theme.boreHole.stroke,
1493
+ dasharray: theme.boreHole.strokeDasharray
1494
+ },
1495
+ holeFillGravel: {
1496
+ fill: legendTextures.gravel_pack.url(),
1497
+ stroke: theme.holeFill.stroke
1498
+ },
1499
+ holeFillSeal: {
1500
+ fill: legendTextures.seal.url(),
1501
+ stroke: theme.holeFill.stroke
1502
+ },
1503
+ wellCase: { fill: theme.wellCase.fill, stroke: theme.wellCase.stroke },
1504
+ wellScreen: {
1505
+ fill: legendTextures.well_screen.url(),
1506
+ stroke: theme.wellScreen.stroke
1507
+ },
1508
+ cementPad: {
1509
+ fill: legendTextures.pad.url(),
1510
+ stroke: theme.cementPad.stroke
1511
+ },
1512
+ conflict: {
1513
+ fill: legendTextures.conflict.url(),
1514
+ stroke: theme.conflict.stroke
1515
+ }
1516
+ };
1517
+ const sym = symMap[item.subKind];
1518
+ const symRect = symG.append("rect").attr("class", cls.constructionRect).attr("x", cx).attr("y", rowSymY - rh / 2).attr("width", rw).attr("height", rh).attr("fill", sym.fill).attr("stroke", sym.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
1519
+ if (sym.dasharray) symRect.attr("stroke-dasharray", sym.dasharray);
1520
+ }
1521
+ g.append("text").attr("class", cls.label).attr("x", cx + 28).attr("y", rowSymY + 3).attr("font-size", theme.legend.fontSize).attr("font-family", theme.legend.fontFamily ?? "sans-serif").attr("font-weight", theme.legend.labelFontWeight ?? 400).text(item.label);
1522
+ });
1523
+ }
1524
+
1525
+ // src/renderers/lithology.renderer.ts
1526
+ function drawLithology(ctx, data) {
1527
+ if (!data.length) return;
1528
+ const {
1529
+ groups: { lithologyGroup },
1530
+ svg,
1531
+ geoXLeft,
1532
+ geoXRight,
1533
+ depthFrom,
1534
+ depthTo,
1535
+ yScale,
1536
+ theme,
1537
+ classes,
1538
+ tooltips,
1539
+ transition
1540
+ } = ctx;
1541
+ const { getHeight, getYPos } = getYAxisFunctions(yScale, {
1542
+ from: depthFrom,
1543
+ to: depthTo
1544
+ });
1545
+ const rects = lithologyGroup.selectAll(`.${classes.lithology.rect}`).data(data);
1546
+ rects.exit().remove();
1547
+ const newLayers = rects.enter().append("rect").attr("class", classes.lithology.rect).attr("x", geoXLeft).attr("width", geoXRight - geoXLeft).attr("stroke", theme.lithology.stroke).attr("stroke-width", theme.lithology.strokeWidth).on("mouseover", tooltips.geology.show).on("mouseout", tooltips.geology.hide);
1548
+ const merged = mergeEnter(newLayers, rects).attr("y", getYPos);
1549
+ withTransition(merged, transition).attr("height", getHeight).attr("fill", getLithologyFill(data, svg, ctx.theme.lithologyTexture));
1550
+ }
1551
+
1552
+ // src/renderers/unit-labels.renderer.ts
1553
+ function drawUnitLabels(ctx, data) {
1554
+ if (!ctx.renderConfig.unitLabels.active || !data.length) return;
1555
+ const {
1556
+ groups: { unitLabelsGroup },
1557
+ depthFrom,
1558
+ depthTo,
1559
+ yScale,
1560
+ renderConfig,
1561
+ theme,
1562
+ classes
1563
+ } = ctx;
1564
+ unitLabelsGroup.selectAll("*").remove();
1565
+ const ru = renderConfig.unitLabels;
1566
+ const rut = theme.unitLabels;
1567
+ const sw = ru.stripWidth;
1568
+ const groupByField = (field) => {
1569
+ const groups = [];
1570
+ for (const layer of data) {
1571
+ const unit = layer[field];
1572
+ if (!unit) continue;
1573
+ const from = Math.max(layer.from, depthFrom);
1574
+ const to = Math.min(layer.to, depthTo);
1575
+ if (from >= to) continue;
1576
+ const last = groups[groups.length - 1];
1577
+ if (last && last.unit === unit) {
1578
+ last.to = to;
1579
+ } else groups.push({ unit, from, to });
1580
+ }
1581
+ return groups;
1582
+ };
1583
+ const hasGeo = data.some((d) => d.geologic_unit);
1584
+ const hasAq = data.some((d) => d.aquifer_unit);
1585
+ const geoX = ru.xOffset;
1586
+ const aqX = hasGeo && hasAq ? ru.xOffset + sw + 1 : ru.xOffset;
1587
+ const drawStrip = (groups, stripX, rectClass, fillColor) => {
1588
+ groups.forEach((group, idx) => {
1589
+ const yTop = yScale(group.from);
1590
+ const yBot = yScale(group.to);
1591
+ const height = yBot - yTop;
1592
+ if (height < 1) return;
1593
+ const isFirst = idx === 0;
1594
+ const isLast = idx === groups.length - 1;
1595
+ unitLabelsGroup.append("rect").attr("class", rectClass).attr("x", stripX).attr("y", yTop).attr("width", sw).attr("height", height).attr("fill", fillColor).attr("stroke", rut.stroke).attr("stroke-width", rut.strokeWidth);
1596
+ const appendEdge = (y, strokeWidth) => unitLabelsGroup.append("line").attr("class", "unit-divider-line").attr("x1", stripX).attr("x2", stripX + sw).attr("y1", y).attr("y2", y).attr("stroke", rut.stroke).attr("stroke-width", strokeWidth);
1597
+ appendEdge(yTop, isFirst ? ru.outerEdgeWidth : ru.innerDividerWidth);
1598
+ appendEdge(yBot, isLast ? ru.outerEdgeWidth : ru.innerDividerWidth);
1599
+ if (height >= ru.minHeightForText) {
1600
+ const maxChars = Math.floor(height / (rut.fontSize * 0.55));
1601
+ const label = group.unit.length > maxChars ? `${group.unit.slice(0, maxChars - 1)}\u2026` : group.unit;
1602
+ unitLabelsGroup.append("text").attr("class", classes.unitLabels.text).attr(
1603
+ "transform",
1604
+ `translate(${(stripX + sw / 2).toFixed(1)},${((yTop + yBot) / 2).toFixed(1)}) rotate(-90)`
1605
+ ).attr("text-anchor", "middle").attr("dominant-baseline", "middle").attr("font-size", rut.fontSize).attr("font-family", rut.fontFamily ?? "sans-serif").attr("font-weight", rut.fontWeight ?? 400).attr("fill", rut.stroke).text(label);
1606
+ }
1607
+ });
1608
+ };
1609
+ if (hasGeo)
1610
+ drawStrip(
1611
+ groupByField("geologic_unit"),
1612
+ geoX,
1613
+ classes.unitLabels.geoRect,
1614
+ rut.geologicFill
1615
+ );
1616
+ if (hasAq)
1617
+ drawStrip(
1618
+ groupByField("aquifer_unit"),
1619
+ aqX,
1620
+ classes.unitLabels.aqRect,
1621
+ rut.aquiferFill
1622
+ );
1623
+ }
1624
+
1625
+ // src/utils/render.styles.ts
1626
+ import sanitizeHtml2 from "sanitize-html";
1627
+ var sanitizeCssValue = (v) => sanitizeHtml2(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
1628
+ function buildSvgStyleBlock(theme) {
1629
+ const { labels, lithology } = theme;
1630
+ const s = sanitizeCssValue;
1631
+ return [
1632
+ `.wp-lithology-depth-tip{border:0.5px solid ${s(lithology.stroke)};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`,
1633
+ `.wp-annotation-label{font-size:${s(labels.fontSize)}px}`,
1634
+ `.wp-annotation-header{font-weight:${s(labels.headerFontWeight ?? 600)};font-family:${s(labels.headerFont)};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`,
1635
+ `.wp-annotation-body{font-weight:${s(labels.bodyFontWeight ?? 400)};font-family:${s(labels.bodyFont ?? "sans-serif")};font-size:${s(labels.fontSize)}px;color:${s(labels.bodyColor)}}`,
1636
+ `.wp-depth-tip-text{font-family:${s(labels.bodyFont ?? "sans-serif")};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`
1637
+ ].join("");
1638
+ }
1639
+
1640
+ // src/Renderer.ts
1641
+ var d36 = Object.assign(d3module3, { tip: d3tip2 });
1642
+ var WellRenderer = class {
1643
+ /**
1644
+ * @param svgs - One entry per SVG panel; multi-entry for split-panel layouts.
1645
+ * @param options.classNames - Override default BEM CSS class names.
1646
+ * @param options.units - Active measurement units (`length` and `diameter`).
1647
+ * @param options.renderConfig - Rendering behaviour (zoom, pan, animation, layout). Defaults to `INTERACTIVE_RENDER_CONFIG`.
1648
+ * @param options.theme - Visual theme overrides. Merged on top of `DEFAULT_WELL_THEME`.
1649
+ * @param options.onError - Called when a draw error occurs (e.g., a renderer throws). Falls back to `console.error` if omitted.
1650
+ */
1651
+ constructor(svgs, options = {}) {
1652
+ this.svgInstances = [];
1653
+ this.instanceStates = [];
1654
+ this.textures = createWellTextures();
1655
+ this.classes = DEFAULT_COMPONENTS_CLASS_NAMES;
1656
+ this.renderConfig = INTERACTIVE_RENDER_CONFIG;
1657
+ this.theme = DEFAULT_WELL_THEME;
1658
+ this.units = {
1659
+ length: "m",
1660
+ diameter: "mm"
1661
+ };
1662
+ if (svgs.length === 0) {
1663
+ console.warn(
1664
+ "[WellRenderer] No SVG instances provided. The renderer will be a no-op."
1665
+ );
1666
+ return;
1667
+ }
1668
+ this.svgInstances = svgs;
1669
+ if (options.onError) this.onError = options.onError;
1670
+ if (options.classNames) {
1671
+ this.classes = defu2(
1672
+ options.classNames,
1673
+ DEFAULT_COMPONENTS_CLASS_NAMES
1674
+ );
1675
+ }
1676
+ if (options.units) {
1677
+ this.units = { ...this.units, ...options.units };
1678
+ }
1679
+ if (options.renderConfig) {
1680
+ this.renderConfig = defu2(
1681
+ options.renderConfig,
1682
+ INTERACTIVE_RENDER_CONFIG
1683
+ );
1684
+ }
1685
+ this.theme = defu2(options.theme ?? {}, DEFAULT_WELL_THEME);
1686
+ this.textures = createWellTextures(this.renderConfig.textures);
1687
+ }
1688
+ initInstanceSvg(inst, index) {
1689
+ const clipId = `fractures-clip-${index}`;
1690
+ const clipRectId = `fractures-clip-rect-${index}`;
1691
+ const { selector, height, width, margins } = inst;
1692
+ const svgWidth = width + margins.left + margins.right;
1693
+ const svgHeight = height + margins.top + margins.bottom;
1694
+ const svg = d36.select(selector);
1695
+ svg.selectAll("*").remove();
1696
+ svg.attr("height", svgHeight).attr("width", svgWidth);
1697
+ const defs = svg.append("defs");
1698
+ defs.append("style").text(buildSvgStyleBlock(this.theme));
1699
+ defs.append("clipPath").attr("id", clipId).append("rect").attr("id", clipRectId).attr("x", 0).attr("y", 0).attr("width", 1e5).attr("height", 1e4);
1700
+ const poco = svg.append("g").attr("class", this.classes.wellGroup);
1701
+ const geologic = poco.append("g").attr("class", this.classes.geologicGroup).attr("transform", `translate(${margins.left}, ${margins.top})`);
1702
+ geologic.append("g").attr("class", this.classes.yAxis);
1703
+ geologic.append("g").attr("class", this.classes.unitLabels.group);
1704
+ geologic.append("g").attr("class", this.classes.lithology.group);
1705
+ geologic.append("g").attr("class", this.classes.caves.group);
1706
+ geologic.append("g").attr("class", this.classes.labels.lithology.group);
1707
+ geologic.append("g").attr("class", this.classes.highlights.geologicGroup);
1708
+ const construction = poco.append("g").attr("class", this.classes.constructionGroup).attr(
1709
+ "transform",
1710
+ `translate(${margins.left + width / 2}, ${margins.top})`
1711
+ );
1712
+ const fracturesGroup = poco.append("g").attr("class", this.classes.fractures.group).attr("transform", `translate(${margins.left}, ${margins.top})`).attr("clip-path", `url(#${clipId})`);
1713
+ fracturesGroup.append("g").attr("class", this.classes.highlights.fracturesGroup);
1714
+ construction.append("g").attr("class", this.classes.constructionLabels.group);
1715
+ construction.append("g").attr("class", this.classes.cementPad.group);
1716
+ construction.append("g").attr("class", this.classes.boreHole.group);
1717
+ construction.append("g").attr("class", this.classes.surfaceCase.group);
1718
+ construction.append("g").attr("class", this.classes.holeFill.group);
1719
+ construction.append("g").attr("class", this.classes.wellCase.group);
1720
+ construction.append("g").attr("class", this.classes.wellScreen.group);
1721
+ construction.append("g").attr("class", this.classes.conflict.group);
1722
+ construction.append("g").attr("class", this.classes.highlights.constructionGroup);
1723
+ Object.values(this.textures).forEach((texture) => svg.call(texture));
1724
+ return { svg, height, width, margins, clipId, clipRectId };
1725
+ }
1726
+ /** Renders a standalone legend into the element matched by `selector`. */
1727
+ renderLegend(selector, profile) {
1728
+ drawWellLegend(selector, profile, {
1729
+ config: this.renderConfig.legend,
1730
+ theme: this.theme,
1731
+ classNames: this.classes.legend,
1732
+ textures: this.renderConfig.textures
1733
+ });
1734
+ }
1735
+ /**
1736
+ * Initialises the SVG DOM structure and preloads FGDC textures.
1737
+ * Must be called once before the first `draw()`.
1738
+ */
1739
+ async prepareSvg() {
1740
+ await preloadFgdcTextures();
1741
+ this.instanceStates = this.svgInstances.map(
1742
+ (inst, i) => this.initInstanceSvg(inst, i)
1743
+ );
1744
+ }
1745
+ /**
1746
+ * Renders the well profile into all configured SVG panels.
1747
+ * @param profile - The well data to render.
1748
+ * @param options.units - Override the instance-level measurement units for this render.
1749
+ * @param options.highlights - Highlight overlays to display on top of the profile.
1750
+ */
1751
+ draw(profile, options = {}) {
1752
+ if (isWellEmpty(profile)) return;
1753
+ this.units = { ...this.units, ...options.units };
1754
+ const highlights = options.highlights ?? {};
1755
+ const maxValues = getProfileLastItemsDepths(profile);
1756
+ const maxYValues = d36.max(maxValues) || 0;
1757
+ if (this.instanceStates.length === 1) {
1758
+ this.drawLogToInstance(
1759
+ this.instanceStates[0],
1760
+ profile,
1761
+ 0,
1762
+ maxYValues,
1763
+ highlights
1764
+ );
1765
+ return;
1766
+ }
1767
+ const totalHeight = this.instanceStates.reduce((s, st) => s + st.height, 0) || 1;
1768
+ const yScaleGlobal = d36.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
1769
+ let currentDepth = 0;
1770
+ for (const state of this.instanceStates) {
1771
+ const maxSvgDepth = yScaleGlobal.invert(
1772
+ yScaleGlobal(currentDepth) + state.height
1773
+ );
1774
+ this.drawLogToInstance(
1775
+ state,
1776
+ profile,
1777
+ currentDepth,
1778
+ maxSvgDepth,
1779
+ highlights
1780
+ );
1781
+ currentDepth = maxSvgDepth;
1782
+ }
1783
+ }
1784
+ drawLogToInstance(state, profile, depthFrom, depthTo, highlights = {}) {
1785
+ const svg = state.svg;
1786
+ const pocoGroup = svg.select(`.${this.classes.wellGroup}`);
1787
+ const lithologyGroup = svg.select(`.${this.classes.lithology.group}`);
1788
+ const fracturesGroup = svg.select(`.${this.classes.fractures.group}`);
1789
+ const cavesGroup = svg.select(`.${this.classes.caves.group}`);
1790
+ const lithologyLabelsGroup = svg.select(
1791
+ `.${this.classes.labels.lithology.group}`
1792
+ );
1793
+ const constructionGroup = svg.select(`.${this.classes.constructionGroup}`);
1794
+ const cementPadGroup = svg.select(`.${this.classes.cementPad.group}`);
1795
+ const holeGroup = svg.select(`.${this.classes.boreHole.group}`);
1796
+ const surfaceCaseGroup = svg.select(`.${this.classes.surfaceCase.group}`);
1797
+ const holeFillGroup = svg.select(`.${this.classes.holeFill.group}`);
1798
+ const wellCaseGroup = svg.select(`.${this.classes.wellCase.group}`);
1799
+ const wellScreenGroup = svg.select(`.${this.classes.wellScreen.group}`);
1800
+ const conflictGroup = svg.select(`.${this.classes.conflict.group}`);
1801
+ const constructionLabelsGroup = svg.select(
1802
+ `.${this.classes.constructionLabels.group}`
1803
+ );
1804
+ const highlightsGeologicGroup = svg.select(
1805
+ `.${this.classes.highlights.geologicGroup}`
1806
+ );
1807
+ const highlightsConstructionGroup = svg.select(
1808
+ `.${this.classes.highlights.constructionGroup}`
1809
+ );
1810
+ const highlightsFracturesGroup = svg.select(
1811
+ `.${this.classes.highlights.fracturesGroup}`
1812
+ );
1813
+ const unitLabelsGroup = svg.select(`.${this.classes.unitLabels.group}`);
1814
+ const svgWidth = state.width + state.margins.left + state.margins.right;
1815
+ const svgHeight = state.height + state.margins.top + state.margins.bottom;
1816
+ const { pocoWidthRatio, pocoCenterRatio } = this.renderConfig.layout;
1817
+ const POCO_WIDTH = svgWidth * pocoWidthRatio;
1818
+ const POCO_CENTER = svgWidth * pocoCenterRatio;
1819
+ const { duration, ease } = this.renderConfig.animation;
1820
+ const transition = d36.transition().duration(duration).ease(ease);
1821
+ const tooltips = populateTooltips(
1822
+ svg,
1823
+ this.classes,
1824
+ this.units,
1825
+ this.renderConfig.tooltips
1826
+ );
1827
+ const { xLeft: geoXLeft, xRightInset: geoXRightInset } = this.renderConfig.geologic;
1828
+ const geoXRight = svgWidth - geoXRightInset;
1829
+ const pocoCenterX = state.width / 2 + POCO_CENTER / 2;
1830
+ const geologicData = {
1831
+ lithology: profile.lithology,
1832
+ fractures: profile.fractures,
1833
+ caves: profile.caves
1834
+ };
1835
+ const constructionData = {
1836
+ cement_pad: profile.cement_pad,
1837
+ bore_hole: profile.bore_hole,
1838
+ hole_fill: profile.hole_fill,
1839
+ surface_case: profile.surface_case,
1840
+ well_case: profile.well_case,
1841
+ well_screen: profile.well_screen,
1842
+ reduction: profile.reduction,
1843
+ fractures: profile.fractures
1844
+ };
1845
+ const contentHeight = svgHeight - state.margins.top - state.margins.bottom;
1846
+ const yScaleLocal = d36.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
1847
+ const depthFactor = this.units.length === "ft" ? 3.28084 : 1;
1848
+ const yScaleAxis = d36.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
1849
+ const yAxis = d36.axisLeft(yScaleAxis).tickFormat(
1850
+ (d) => `${d}${getLengthUnit(this.units.length)}`
1851
+ );
1852
+ const gY = svg.select(`.${this.classes.yAxis}`).call(yAxis);
1853
+ gY.select(".domain").attr("stroke", this.theme.labels.color).attr("fill", "none");
1854
+ gY.selectAll(".tick line").attr("stroke", this.theme.labels.color);
1855
+ gY.selectAll(".tick text").attr("fill", this.theme.labels.color).attr("stroke", "none").attr(
1856
+ "font-size",
1857
+ this.theme.labels.scaleFontSize ?? this.theme.labels.fontSize
1858
+ ).attr(
1859
+ "font-family",
1860
+ this.theme.labels.scaleFont ?? this.theme.labels.bodyFont ?? "sans-serif"
1861
+ );
1862
+ const spanY = (d) => {
1863
+ if (d.thickness) return yScaleLocal(0) - yScaleLocal(d.thickness);
1864
+ return yScaleLocal(Math.max(d.from ?? 0, depthFrom));
1865
+ };
1866
+ const spanH = (d) => {
1867
+ if (d.thickness) return yScaleLocal(d.thickness) - yScaleLocal(0);
1868
+ return Math.max(
1869
+ 0,
1870
+ yScaleLocal(Math.min(d.to ?? 0, depthTo)) - yScaleLocal(Math.max(d.from ?? 0, depthFrom))
1871
+ );
1872
+ };
1873
+ const groups = {
1874
+ lithologyGroup,
1875
+ unitLabelsGroup,
1876
+ cavesGroup,
1877
+ fracturesGroup,
1878
+ lithologyLabelsGroup,
1879
+ constructionGroup,
1880
+ constructionLabelsGroup,
1881
+ cementPadGroup,
1882
+ holeGroup,
1883
+ surfaceCaseGroup,
1884
+ holeFillGroup,
1885
+ wellCaseGroup,
1886
+ wellScreenGroup,
1887
+ conflictGroup,
1888
+ highlightsGeologicGroup,
1889
+ highlightsConstructionGroup,
1890
+ highlightsFracturesGroup
1891
+ };
1892
+ const ctx = {
1893
+ svg,
1894
+ state,
1895
+ svgWidth,
1896
+ svgHeight,
1897
+ POCO_WIDTH,
1898
+ POCO_CENTER,
1899
+ pocoCenterX,
1900
+ geoXLeft,
1901
+ geoXRight,
1902
+ depthFrom,
1903
+ depthTo,
1904
+ yScale: yScaleLocal,
1905
+ transition,
1906
+ tooltips,
1907
+ renderConfig: this.renderConfig,
1908
+ theme: this.theme,
1909
+ classes: this.classes,
1910
+ textures: this.textures,
1911
+ units: this.units,
1912
+ constructionData,
1913
+ groups
1914
+ };
1915
+ const inDepth = filterByDepth(depthFrom, depthTo);
1916
+ const filteredConstruction = {
1917
+ ...constructionData,
1918
+ bore_hole: constructionData.bore_hole.filter(inDepth),
1919
+ hole_fill: constructionData.hole_fill.filter(inDepth),
1920
+ surface_case: constructionData.surface_case.filter(inDepth),
1921
+ well_case: constructionData.well_case.filter(inDepth),
1922
+ well_screen: constructionData.well_screen.filter(inDepth),
1923
+ reduction: constructionData.reduction?.filter(inDepth) ?? []
1924
+ };
1925
+ const zooming = (e) => {
1926
+ const transform = e.transform;
1927
+ const zoomedCtx = {
1928
+ ...ctx,
1929
+ yScale: transform.rescaleY(yScaleLocal)
1930
+ };
1931
+ gY.call(yAxis.scale(transform.rescaleY(yScaleAxis)));
1932
+ pocoGroup.selectAll("rect").attr("y", (d) => {
1933
+ if (!d) return null;
1934
+ return transform.applyY(spanY(d));
1935
+ }).attr("height", (d) => {
1936
+ if (!d) return null;
1937
+ return transform.k * spanH(d);
1938
+ });
1939
+ surfaceCaseGroup.selectAll(`g.${this.classes.surfaceCase.rect}`).each((d, i, nodes) => {
1940
+ if (!d) return;
1941
+ const sc = d;
1942
+ const g = d36.select(nodes[i]);
1943
+ const y = transform.applyY(spanY(sc));
1944
+ const h = transform.k * spanH(sc);
1945
+ g.select(".surface-case-fill").attr("y", y).attr("height", h);
1946
+ g.selectAll(".surface-case-side").attr("y1", y).attr("y2", y + h);
1947
+ });
1948
+ fracturesGroup.selectAll(`g.${this.classes.fractures.item}`).attr("transform", (d) => {
1949
+ if (!d) return null;
1950
+ const f = d;
1951
+ const cx = pocoCenterX;
1952
+ const cy = transform.applyY(yScaleLocal(f.depth));
1953
+ return `translate(0,${cy}) rotate(${f.dip},${cx},0)`;
1954
+ });
1955
+ drawCaves(zoomedCtx, geologicData.caves?.filter(inDepth) ?? []);
1956
+ drawUnitLabels(zoomedCtx, profile.lithology?.filter(inDepth) ?? []);
1957
+ drawAnnotationLabels(zoomedCtx, {
1958
+ lithology: profile.lithology?.filter(inDepth) ?? [],
1959
+ fractures: profile.fractures?.filter(
1960
+ (f) => f.depth >= depthFrom && f.depth < depthTo
1961
+ ) ?? [],
1962
+ caves: profile.caves?.filter(inDepth) ?? []
1963
+ });
1964
+ drawConstructionLabels(zoomedCtx, {
1965
+ well_case: constructionData.well_case.filter(inDepth),
1966
+ well_screen: constructionData.well_screen.filter(inDepth)
1967
+ });
1968
+ drawHighlights(zoomedCtx, highlights);
1969
+ };
1970
+ const drawProfile = () => {
1971
+ drawLithology(ctx, profile.lithology?.filter(inDepth) ?? []);
1972
+ drawUnitLabels(ctx, profile.lithology?.filter(inDepth) ?? []);
1973
+ drawAnnotationLabels(ctx, {
1974
+ lithology: profile.lithology?.filter(inDepth) ?? [],
1975
+ fractures: profile.fractures?.filter(
1976
+ (f) => f.depth >= depthFrom && f.depth < depthTo
1977
+ ) ?? [],
1978
+ caves: profile.caves?.filter(inDepth) ?? []
1979
+ });
1980
+ drawCaves(ctx, profile.caves?.filter(inDepth) ?? []);
1981
+ drawFractures(
1982
+ ctx,
1983
+ profile.fractures?.filter(
1984
+ // exclusive upper bound: depthTo is the start of the next panel
1985
+ (f) => f.depth >= depthFrom && f.depth < depthTo
1986
+ ) ?? []
1987
+ );
1988
+ drawConstructive(ctx, filteredConstruction);
1989
+ drawConstructionLabels(ctx, {
1990
+ well_case: filteredConstruction.well_case,
1991
+ well_screen: filteredConstruction.well_screen
1992
+ });
1993
+ svg.select(`#${state.clipRectId}`).attr("y", 0).attr("height", svgHeight);
1994
+ drawHighlights(ctx, highlights);
1995
+ };
1996
+ try {
1997
+ drawProfile();
1998
+ } catch (err) {
1999
+ const error = err instanceof Error ? err : new Error(String(err));
2000
+ if (this.onError) {
2001
+ this.onError(error);
2002
+ } else {
2003
+ console.error("[WellRenderer] drawProfile failed:", error);
2004
+ }
2005
+ }
2006
+ const { zoom: zoomEnabled, pan: panEnabled } = this.renderConfig;
2007
+ if (zoomEnabled || panEnabled) {
2008
+ const zoomNode = d36.zoom();
2009
+ if (zoomEnabled || panEnabled) zoomNode.on("zoom", zooming);
2010
+ if (!zoomEnabled) zoomNode.scaleExtent([1, 1]);
2011
+ if (!panEnabled)
2012
+ zoomNode.filter((e) => e.type === "wheel" || e.type === "dblclick");
2013
+ asSvgElement(svg).call(zoomNode);
2014
+ const initialZoom = this.renderConfig.zoomLevel ?? 1;
2015
+ if (initialZoom !== 1) {
2016
+ asSvgElement(svg).call(
2017
+ zoomNode.transform,
2018
+ d36.zoomIdentity.scale(initialZoom)
2019
+ );
2020
+ }
2021
+ }
2022
+ }
2023
+ };
2024
+ export {
2025
+ DEFAULT_WELL_THEME,
2026
+ INTERACTIVE_RENDER_CONFIG,
2027
+ STATIC_RENDER_CONFIG,
2028
+ WellRenderer,
2029
+ drawWellLegend,
2030
+ formatDiameter,
2031
+ formatLength,
2032
+ getDiameterUnit,
2033
+ getLengthUnit,
2034
+ importFgdcTextures
2035
+ };
2036
+ //# sourceMappingURL=index.js.map