@welldot/render 0.1.0 → 0.2.3

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 CHANGED
@@ -1,5 +1,146 @@
1
1
  // src/configs/render.configs.ts
2
2
  import { easeCubic } from "d3";
3
+
4
+ // src/utils/format.utils.ts
5
+ import { formatNumber } from "@welldot/utils";
6
+ var MM_TO_INCHES = 0.0393701;
7
+ var M_TO_FT = 3.28084;
8
+ function formatLength(m, units, locale) {
9
+ return units === "ft" ? formatNumber(m * M_TO_FT, { maximumFractionDigits: 1, locale }) : formatNumber(m, { maximumFractionDigits: 2, locale });
10
+ }
11
+ function formatDiameter(mm, units, locale) {
12
+ return units === "inches" ? formatNumber(mm * MM_TO_INCHES, { maximumFractionDigits: 2, locale }) : formatNumber(mm, { maximumFractionDigits: 2, locale });
13
+ }
14
+ function getLengthUnit(units) {
15
+ return units === "ft" ? "ft" : "m";
16
+ }
17
+ function getDiameterUnit(units, locale) {
18
+ if (units !== "inches") return "mm";
19
+ return locale === "en" ? "in." : '"';
20
+ }
21
+ function resolveRenderLabel(value, locale) {
22
+ if (typeof value === "string") return value;
23
+ return locale === "en" && value.en ? value.en : value.pt;
24
+ }
25
+
26
+ // src/configs/render.configs.ts
27
+ var RENDER_LABELS = {
28
+ constructionLabels: {
29
+ wellCasePrefix: { pt: "Revest.", en: "Casing" },
30
+ wellScreenPrefix: { pt: "Filtro", en: "Screen" },
31
+ wellScreenSlotPrefix: { pt: "Ranhura:", en: "Slot:" }
32
+ },
33
+ legend: {
34
+ title: { pt: "LEGENDA", en: "LEGEND" },
35
+ labels: {
36
+ fractureSingle: { pt: "Fratura simples", en: "Single fracture" },
37
+ fractureSwarm: { pt: "Enxame de fraturas", en: "Fracture swarm" },
38
+ fractureWater: { pt: "Entrada d'\xE1gua", en: "Water intake" },
39
+ caveDry: { pt: "Caverna seca", en: "Dry cave" },
40
+ caveWet: { pt: "Caverna c/ \xE1gua", en: "Wet cave" },
41
+ boreHole: { pt: "Perfura\xE7\xE3o", en: "Borehole" },
42
+ surfaceCase: { pt: "Tubo guia", en: "Surface casing" },
43
+ holeFillGravel: { pt: "Pr\xE9-filtro", en: "Gravel pack" },
44
+ holeFillSeal: { pt: "Veda\xE7\xE3o", en: "Seal" },
45
+ wellCase: { pt: "Revestimento", en: "Casing" },
46
+ wellScreen: { pt: "Filtro", en: "Screen" },
47
+ reduction: { pt: "Redu\xE7\xE3o", en: "Reduction" },
48
+ cementPad: { pt: "Laje de cimento", en: "Cement pad" },
49
+ conflict: { pt: "Conflito", en: "Conflict" }
50
+ }
51
+ },
52
+ tooltipLabels: {
53
+ common: {
54
+ from: { pt: "De", en: "From" },
55
+ to: { pt: "at\xE9", en: "to" },
56
+ description: { pt: "Descri\xE7\xE3o:", en: "Description:" },
57
+ diameter: { pt: "Di\xE2metro:", en: "Diameter:" },
58
+ type: { pt: "Tipo:", en: "Type:" }
59
+ },
60
+ geology: {
61
+ title: { pt: "Litologia", en: "Lithology" },
62
+ geologicUnit: { pt: "Unidade geol\xF3gica:", en: "Geologic unit:" },
63
+ aquiferUnit: { pt: "Unidade aqu\xEDfera:", en: "Aquifer unit:" }
64
+ },
65
+ hole: { title: { pt: "FURO", en: "BOREHOLE" } },
66
+ surfaceCase: { title: { pt: "TUBO DE BOCA", en: "SURFACE CASING" } },
67
+ holeFill: { title: { pt: "ESP. ANULAR", en: "ANNULAR SPACE" } },
68
+ wellCase: { title: { pt: "REVESTIMENTO", en: "CASING" } },
69
+ wellScreen: {
70
+ title: { pt: "FILTROS", en: "SCREENS" },
71
+ slot: { pt: "Ranhura:", en: "Slot:" }
72
+ },
73
+ reduction: { title: { pt: "REDU\xC7\xC3O", en: "REDUCTION" } },
74
+ conflict: { title: { pt: "CONFLITO", en: "CONFLICT" } },
75
+ fracture: {
76
+ title: { pt: "FRATURA", en: "FRACTURE" },
77
+ titleSwarm: { pt: "ENXAME DE FRATURAS", en: "FRACTURE SWARM" },
78
+ depth: { pt: "Profundidade:", en: "Depth:" },
79
+ waterIntake: { pt: "Entrada d'\xE1gua:", en: "Water intake:" },
80
+ dip: { pt: "Mergulho:", en: "Dip:" },
81
+ azimuth: { pt: "Azimute:", en: "Azimuth:" }
82
+ },
83
+ cementPad: {
84
+ title: { pt: "LAJE DE PROTE\xC7\xC3O", en: "PROTECTION SLAB" },
85
+ thickness: { pt: "Espessura:", en: "Thickness:" },
86
+ width: { pt: "Largura:", en: "Width:" },
87
+ length: { pt: "Comprimento:", en: "Length:" }
88
+ },
89
+ cave: {
90
+ title: { pt: "CAVERNA", en: "CAVE" },
91
+ waterIntake: { pt: "Entrada d'\xE1gua", en: "Water intake" }
92
+ }
93
+ },
94
+ typeLabels: {
95
+ fracture: { pt: "fratura", en: "fracture" },
96
+ fractureWater: { pt: "fratura aberta", en: "open fracture" },
97
+ cave: { pt: "caverna", en: "cave" },
98
+ caveWater: { pt: "caverna \xFAmida", en: "wet cave" }
99
+ }
100
+ };
101
+ function resolveGroup(group, locale) {
102
+ const result = {};
103
+ for (const key in group) {
104
+ result[key] = resolveRenderLabel(group[key], locale);
105
+ }
106
+ return result;
107
+ }
108
+ function resolveTooltipLabels(locale) {
109
+ const t = RENDER_LABELS.tooltipLabels;
110
+ return {
111
+ common: resolveGroup(t.common, locale),
112
+ geology: resolveGroup(t.geology, locale),
113
+ hole: resolveGroup(t.hole, locale),
114
+ surfaceCase: resolveGroup(t.surfaceCase, locale),
115
+ holeFill: resolveGroup(t.holeFill, locale),
116
+ wellCase: resolveGroup(t.wellCase, locale),
117
+ wellScreen: resolveGroup(t.wellScreen, locale),
118
+ reduction: resolveGroup(t.reduction, locale),
119
+ conflict: resolveGroup(t.conflict, locale),
120
+ fracture: resolveGroup(t.fracture, locale),
121
+ cementPad: resolveGroup(t.cementPad, locale),
122
+ cave: resolveGroup(t.cave, locale)
123
+ };
124
+ }
125
+ function applyRenderLocale(config, locale) {
126
+ return {
127
+ ...config,
128
+ constructionLabels: {
129
+ ...config.constructionLabels,
130
+ labels: resolveGroup(RENDER_LABELS.constructionLabels, locale)
131
+ },
132
+ legend: {
133
+ ...config.legend,
134
+ title: resolveRenderLabel(RENDER_LABELS.legend.title, locale),
135
+ labels: resolveGroup(RENDER_LABELS.legend.labels, locale)
136
+ },
137
+ tooltipLabels: resolveTooltipLabels(locale),
138
+ labels: {
139
+ ...config.labels,
140
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, locale)
141
+ }
142
+ };
143
+ }
3
144
  var DEFAULT_WELL_THEME = {
4
145
  lithology: { stroke: "#101010", strokeWidth: 1 },
5
146
  lithologyTexture: { size: 150, strokeWidth: 0.8, stroke: "#303030" },
@@ -34,6 +175,7 @@ var DEFAULT_WELL_THEME = {
34
175
  holeFill: { stroke: "#303030", strokeWidth: 2 },
35
176
  wellCase: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
36
177
  wellScreen: { stroke: "#303030", strokeWidth: 2 },
178
+ reduction: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
37
179
  conflict: { stroke: "#e52117", strokeWidth: 4 },
38
180
  unitLabels: {
39
181
  geologicFill: "#f0f0f0",
@@ -97,6 +239,8 @@ var STATIC_RENDER_CONFIG = {
97
239
  zoom: true,
98
240
  pan: true,
99
241
  zoomLevel: 1,
242
+ maxZoomScale: 150,
243
+ minZoomScale: 0,
100
244
  animation: { duration: 600, ease: easeCubic },
101
245
  geologic: { xLeft: 20, xRightInset: 300 },
102
246
  layout: {
@@ -127,9 +271,10 @@ var STATIC_RENDER_CONFIG = {
127
271
  cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
128
272
  surfaceCase: { diameterPaddingRatio: 0.1 }
129
273
  },
274
+ tooltipLabels: resolveTooltipLabels("pt"),
130
275
  labels: {
131
276
  active: true,
132
- typeLabels: { fracture: "fratura", cave: "caverna" },
277
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, "pt"),
133
278
  depthTipHeight: 11,
134
279
  depthTipPadX: 2,
135
280
  descriptionXOffset: 20,
@@ -194,6 +339,7 @@ var STATIC_RENDER_CONFIG = {
194
339
  holeFillSeal: "Veda\xE7\xE3o",
195
340
  wellCase: "Revestimento",
196
341
  wellScreen: "Filtro",
342
+ reduction: "Redu\xE7\xE3o",
197
343
  cementPad: "Laje de cimento",
198
344
  conflict: "Conflito"
199
345
  }
@@ -204,6 +350,8 @@ var INTERACTIVE_RENDER_CONFIG = {
204
350
  zoom: true,
205
351
  pan: true,
206
352
  zoomLevel: 1,
353
+ maxZoomScale: 150,
354
+ minZoomScale: 0,
207
355
  animation: { duration: 600, ease: easeCubic },
208
356
  geologic: { xLeft: 6, xRightInset: 56 },
209
357
  layout: {
@@ -234,9 +382,10 @@ var INTERACTIVE_RENDER_CONFIG = {
234
382
  cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
235
383
  surfaceCase: { diameterPaddingRatio: 0.1 }
236
384
  },
385
+ tooltipLabels: resolveTooltipLabels("pt"),
237
386
  labels: {
238
387
  active: false,
239
- typeLabels: { fracture: "fratura", cave: "caverna" },
388
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, "pt"),
240
389
  depthTipHeight: 11,
241
390
  depthTipPadX: 2,
242
391
  descriptionXOffset: 78,
@@ -298,6 +447,7 @@ var INTERACTIVE_RENDER_CONFIG = {
298
447
  holeFillSeal: "Veda\xE7\xE3o",
299
448
  wellCase: "Revestimento",
300
449
  wellScreen: "Filtro",
450
+ reduction: "Redu\xE7\xE3o",
301
451
  cementPad: "Laje de cimento",
302
452
  conflict: "Conflito"
303
453
  }
@@ -307,12 +457,12 @@ var INTERACTIVE_RENDER_CONFIG = {
307
457
 
308
458
  // src/utils/fgdcTextures.ts
309
459
  async function importFgdcTextures() {
310
- const fgdcTextures = await import("./fgdcTextures-5IIUV7QV.js");
460
+ const fgdcTextures = await import("./fgdcTextures-P5DTFNPT.js");
311
461
  return fgdcTextures.default;
312
462
  }
313
463
 
314
464
  // src/Renderer.ts
315
- import * as d3module3 from "d3";
465
+ import * as d3module2 from "d3";
316
466
  import d3tip2 from "d3-tip";
317
467
  import { defu as defu2 } from "defu";
318
468
 
@@ -380,23 +530,11 @@ function createWellTextures(cfg) {
380
530
  import {
381
531
  isWellEmpty
382
532
  } 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
- }
533
+ import {
534
+ formatNumber as formatNumber3,
535
+ getProfileDiamValues as getProfileDiamValues4,
536
+ getProfileLastItemsDepths
537
+ } from "@welldot/utils";
400
538
 
401
539
  // src/configs/render.classnames.ts
402
540
  var DEFAULT_COMPONENTS_CLASS_NAMES = {
@@ -454,6 +592,10 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
454
592
  group: "well-screen",
455
593
  rect: "well-screen-rect"
456
594
  },
595
+ reduction: {
596
+ group: "reduction",
597
+ item: "reduction-poly"
598
+ },
457
599
  conflict: {
458
600
  group: "conflict",
459
601
  rect: "conflict-rect"
@@ -496,6 +638,9 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
496
638
  import textures2 from "textures";
497
639
 
498
640
  // src/utils/d3.utils.ts
641
+ function mergeEnter(enter, update) {
642
+ return enter.merge(update);
643
+ }
499
644
  function withTransition(selection, transition) {
500
645
  return selection.transition(transition);
501
646
  }
@@ -513,18 +658,6 @@ var getYAxisFunctions = (yScale, clamp) => {
513
658
  getYPos: ({ from }) => yScale(Math.max(from, clampFrom))
514
659
  };
515
660
  };
516
- function hashStr(s) {
517
- let h = 5381;
518
- for (let i = 0; i < s.length; i++) {
519
- h = Math.imul(h, 33) ^ s.charCodeAt(i);
520
- }
521
- return (h >>> 0).toString(36);
522
- }
523
- function stableLayerKey(d) {
524
- const { from: _f, to: _t, ...rest } = d;
525
- const sortedKeys = Object.keys(rest).sort();
526
- return hashStr(JSON.stringify(rest, sortedKeys));
527
- }
528
661
  function filterByDepth(depthFrom, depthTo) {
529
662
  return (l) => !(l.to <= depthFrom || l.from >= depthTo);
530
663
  }
@@ -572,93 +705,102 @@ var makeSeededPrng = (seed, scramble = 6271) => {
572
705
  };
573
706
 
574
707
  // src/utils/tooltips.utils.ts
575
- import * as d3module from "d3";
576
708
  import d3tip from "d3-tip";
577
709
  import sanitizeHtml from "sanitize-html";
578
- var d3 = Object.assign(d3module, { tip: d3tip });
710
+ import { formatNumber as formatNumber2 } from "@welldot/utils";
579
711
  var esc = (v) => sanitizeHtml(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
580
712
  var _tooltipCache = /* @__PURE__ */ new WeakMap();
581
- var populateTooltips = (svg, customClasses, units, tooltipConfig) => {
713
+ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale) => {
582
714
  const svgEl = typeof svg.node === "function" ? svg.node() : null;
583
- if (svgEl && _tooltipCache.has(svgEl)) {
584
- return _tooltipCache.get(svgEl);
715
+ const signature = JSON.stringify({ units, tooltipConfig, labels, locale });
716
+ const cached = svgEl ? _tooltipCache.get(svgEl) : void 0;
717
+ if (cached && cached.signature === signature) {
718
+ return cached.tooltips;
585
719
  }
586
720
  const tipsText = {
587
721
  geology: (_, d) => `
588
- <span class="${customClasses.tooltip.title}">Litologia</span>
589
- <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>
590
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>
591
- ${d.geologic_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Unidade geol\xF3gica:</strong> ${esc(d.geologic_unit)}</span>` : ""}
592
- ${d.aquifer_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Unidade aqu\xEDfera:</strong> ${esc(d.aquifer_unit)}</span>` : ""}
722
+ <span class="${customClasses.tooltip.title}">${labels.geology.title}</span>
723
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
724
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>
725
+ ${d.geologic_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.geology.geologicUnit}</strong> ${esc(d.geologic_unit)}</span>` : ""}
726
+ ${d.aquifer_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.geology.aquiferUnit}</strong> ${esc(d.aquifer_unit)}</span>` : ""}
593
727
  `,
594
728
  hole: (_, d) => `
595
- <span class="${customClasses.tooltip.title}">FURO</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>
729
+ <span class="${customClasses.tooltip.title}">${labels.hole.title}</span>
730
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
731
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.diameter}</strong>${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
598
732
  `,
599
733
  surfaceCase: (_, d) => `
600
- <span class="${customClasses.tooltip.title}">TUBO DE BOCA</span>
601
- <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>
602
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
734
+ <span class="${customClasses.tooltip.title}">${labels.surfaceCase.title}</span>
735
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
736
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.diameter}</strong>${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
603
737
  `,
604
738
  holeFill: (_, d) => `
605
- <span class="${customClasses.tooltip.title}">ESP. ANULAR</span>
606
- <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>
607
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
739
+ <span class="${customClasses.tooltip.title}">${labels.holeFill.title}</span>
740
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
741
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.diameter}</strong>${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
608
742
  <span class="${customClasses.tooltip.secondaryInfo}">
609
- <strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}
743
+ <strong>${labels.common.description}</strong> ${esc(d.description)}
610
744
  </span>
611
745
  `,
612
746
  wellCase: (_, d) => `
613
- <span class="${customClasses.tooltip.title}">REVESTIMENTO</span>
614
- <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>
747
+ <span class="${customClasses.tooltip.title}">${labels.wellCase.title}</span>
748
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
615
749
  <span class="${customClasses.tooltip.secondaryInfo}">
616
- <strong>Di\xE2metro:</strong> ${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}
750
+ <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}
617
751
  </span>
618
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Tipo:</strong> ${esc(d.type)}</span>
752
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
619
753
  `,
620
754
  wellScreen: (_, d) => `
621
- <span class="${customClasses.tooltip.title}">FILTROS</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>
755
+ <span class="${customClasses.tooltip.title}">${labels.wellScreen.title}</span>
756
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
623
757
  <span class="${customClasses.tooltip.secondaryInfo}">
624
- <strong>Di\xE2metro:</strong> ${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
625
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Tipo:</strong> ${esc(d.type)}</span>
758
+ <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
759
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
626
760
  <span class="${customClasses.tooltip.secondaryInfo}">
627
- <strong>Ranhura:</strong> ${esc(d.screen_slot_mm)} mm
761
+ <strong>${labels.wellScreen.slot}</strong> ${esc(formatNumber2(d.screen_slot, { maximumFractionDigits: 2, locale }))} mm
628
762
  </span>
629
763
  `,
764
+ reduction: (_, d) => `
765
+ <span class="${customClasses.tooltip.title}">${labels.reduction.title}</span>
766
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
767
+ <span class="${customClasses.tooltip.secondaryInfo}">
768
+ <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diam_from, units.diameter, locale))} \u2192 ${esc(formatDiameter(d.diam_to, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}
769
+ </span>
770
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
771
+ `,
630
772
  conflict: (_, d) => `
631
- <span class="${customClasses.tooltip.title}">CONFLITO</span>
632
- <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>
773
+ <span class="${customClasses.tooltip.title}">${labels.conflict.title}</span>
774
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
633
775
  `,
634
776
  fracture: (_, d) => {
635
- const title = d.swarm ? "ENXAME DE FRATURAS" : "FRATURA";
777
+ const title = d.swarm ? labels.fracture.titleSwarm : labels.fracture.title;
636
778
  return `
637
779
  <span class="${customClasses.tooltip.title}">${title}</span>
638
- <span class="${customClasses.tooltip.primaryInfo}"><strong>Profundidade:</strong> ${esc(formatLength(d.depth, units.length))} ${esc(units.length)}</span>
639
- ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Entrada d'\xE1gua:</strong> ${esc(formatLength(d.depth, units.length))} ${esc(units.length)}</span>` : ""}
640
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Mergulho:</strong> ${esc(d.dip)}\xB0</span>
641
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Azimute:</strong> ${esc(d.azimuth)}\xB0</span>
642
- ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>` : ""}
780
+ <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.fracture.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
781
+ ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.waterIntake}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>` : ""}
782
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.dip}</strong> ${esc(d.dip)}\xB0</span>
783
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.azimuth}</strong> ${esc(d.azimuth)}\xB0</span>
784
+ ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
643
785
  `;
644
786
  },
645
787
  cementPad: (_, d) => `
646
- <span class="${customClasses.tooltip.title}">LAJE DE PROTE\xC7\xC3O</span>
788
+ <span class="${customClasses.tooltip.title}">${labels.cementPad.title}</span>
647
789
  <span class="${customClasses.tooltip.primaryInfo}">${esc(d.type)}</span>
648
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Espessura:</strong>
649
- ${esc(formatLength(d.thickness, units.length))} ${esc(units.length)}</span>
790
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cementPad.thickness}</strong>
791
+ ${esc(formatLength(d.thickness, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
650
792
  <span class="${customClasses.tooltip.secondaryInfo}">
651
- <strong>Largura:</strong> ${esc(formatLength(d.width, units.length))} ${esc(units.length)}
793
+ <strong>${labels.cementPad.width}</strong> ${esc(formatLength(d.width, units.length, locale))} ${esc(getLengthUnit(units.length))}
652
794
  </span>
653
795
  <span class="${customClasses.tooltip.secondaryInfo}">
654
- <strong>Comprimento:</strong> ${esc(formatLength(d.length, units.length))} ${esc(units.length)}
796
+ <strong>${labels.cementPad.length}</strong> ${esc(formatLength(d.length, units.length, locale))} ${esc(getLengthUnit(units.length))}
655
797
  </span>
656
798
  `,
657
799
  cave: (_, d) => `
658
- <span class="${customClasses.tooltip.title}">CAVERNA</span>
659
- <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>
660
- ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Entrada d'\xE1gua</strong></span>` : ""}
661
- ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>` : ""}
800
+ <span class="${customClasses.tooltip.title}">${labels.cave.title}</span>
801
+ <span class="${customClasses.tooltip.primaryInfo}">${labels.common.from} ${esc(formatLength(d.from, units.length, locale))} ${esc(getLengthUnit(units.length))} ${labels.common.to} ${esc(formatLength(d.to, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
802
+ ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cave.waterIntake}</strong></span>` : ""}
803
+ ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
662
804
  `
663
805
  };
664
806
  const noop = { show: () => {
@@ -671,13 +813,13 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig) => {
671
813
  tooltips[tipTextKey] = noop;
672
814
  return;
673
815
  }
674
- tooltips[tipTextKey] = d3.tip().attr("class", customClasses.tooltip.root).direction("e").html(tipsText[tipTextKey]);
816
+ tooltips[tipTextKey] = d3tip().attr("class", customClasses.tooltip.root).direction("e").html(tipsText[tipTextKey]);
675
817
  svg.call(
676
818
  tooltips[tipTextKey]
677
819
  );
678
820
  });
679
821
  svgEl?.setAttribute("data-tooltips-init", "true");
680
- if (svgEl) _tooltipCache.set(svgEl, tooltips);
822
+ if (svgEl) _tooltipCache.set(svgEl, { signature, tooltips });
681
823
  return tooltips;
682
824
  };
683
825
 
@@ -693,7 +835,9 @@ var preloadFgdcTextures = () => {
693
835
  return _fgdcTexturesPromise;
694
836
  };
695
837
  var getLithologicalFillList = (data, opts) => {
696
- const uniqueTextureCodes = [...new Set(data.map((d) => d.fgdc_texture))];
838
+ const textureKey = (d) => String(d.texture.code);
839
+ const isFgdc = (d) => !d.texture.vocabulary || d.texture.vocabulary === "fgdc";
840
+ const uniqueTextureCodes = [...new Set(data.filter(isFgdc).map(textureKey))];
697
841
  const texturesLoaded = Object.fromEntries(
698
842
  uniqueTextureCodes.filter((code) => {
699
843
  const path = _fgdcTextures[code];
@@ -702,10 +846,11 @@ var getLithologicalFillList = (data, opts) => {
702
846
  );
703
847
  return Object.fromEntries(
704
848
  data.map((d) => {
705
- const pathData = texturesLoaded[d.fgdc_texture];
706
- if (!pathData) return [`${d.fgdc_texture}.${d.from}`, d.color];
849
+ const key = textureKey(d);
850
+ const pathData = isFgdc(d) ? texturesLoaded[key] : void 0;
851
+ if (!pathData) return [`${key}.${d.from}`, d.color];
707
852
  return [
708
- `${d.fgdc_texture}.${d.from}`,
853
+ `${key}.${d.from}`,
709
854
  textures2.paths().d(() => pathData).size(opts.size).strokeWidth(opts.strokeWidth).stroke(opts.stroke).background(d.color)
710
855
  ];
711
856
  })
@@ -714,7 +859,7 @@ var getLithologicalFillList = (data, opts) => {
714
859
  var getLithologyFill = (geologyData, svg, opts) => {
715
860
  const lithologicalFill = getLithologicalFillList(geologyData, opts);
716
861
  return (d) => {
717
- const fill = lithologicalFill[`${d.fgdc_texture}.${d.from}`];
862
+ const fill = lithologicalFill[`${String(d.texture.code)}.${d.from}`];
718
863
  if (!fill.url) return fill;
719
864
  svg.call(fill);
720
865
  return fill.url();
@@ -777,7 +922,7 @@ function drawAnnotationLabels(ctx, data) {
777
922
  ctx.groups.lithologyLabelsGroup.selectAll("*").remove();
778
923
  if (active.has("lithology") && show.has("depth")) {
779
924
  data.lithology.filter((d) => d.to <= ctx.depthTo).forEach((d) => {
780
- const text = `${d.to}`;
925
+ const text = `${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`;
781
926
  const approxW = text.length * (st.fontSize * 0.52) + s.depthTipPadX * 2;
782
927
  const y = ctx.yScale(d.to) - s.depthTipHeight / 2;
783
928
  const tipG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.depth);
@@ -793,20 +938,20 @@ function drawAnnotationLabels(ctx, data) {
793
938
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
794
939
  originX: ctx.geoXRight,
795
940
  originY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
796
- header: `${d.from}\u2013${d.to} m`,
941
+ header: `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`,
797
942
  description: d.description || ""
798
943
  });
799
944
  });
800
945
  }
801
946
  if (active.has("fractures")) {
802
947
  data.fractures.filter((d) => d.depth >= ctx.depthFrom && d.depth < ctx.depthTo).forEach((d) => {
803
- const typeLabel = d.water_intake ? "fratura aberta" : "fratura";
948
+ const typeLabel = d.water_intake ? ctx.renderConfig.labels.typeLabels?.fractureWater ?? "fratura aberta" : ctx.renderConfig.labels.typeLabels?.fracture ?? "fratura";
804
949
  items.push({
805
950
  sortDepth: d.depth,
806
951
  baseY: ctx.yScale(d.depth),
807
952
  originX: ctx.pocoCenterX,
808
953
  originY: ctx.yScale(d.depth),
809
- header: `${d.depth} m \xB7 ${typeLabel}`,
954
+ header: `${formatLength(d.depth, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
810
955
  description: d.description || ""
811
956
  });
812
957
  });
@@ -814,13 +959,13 @@ function drawAnnotationLabels(ctx, data) {
814
959
  if (active.has("caves")) {
815
960
  data.caves.filter((d) => d.from < ctx.depthTo).forEach((d) => {
816
961
  const midDepth = (d.from + d.to) / 2;
817
- const typeLabel = d.water_intake ? "caverna \xFAmida" : "caverna";
962
+ const typeLabel = d.water_intake ? ctx.renderConfig.labels.typeLabels?.caveWater ?? "caverna \xFAmida" : ctx.renderConfig.labels.typeLabels?.cave ?? "caverna";
818
963
  items.push({
819
964
  sortDepth: d.from,
820
965
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
821
966
  originX: ctx.geoXRight,
822
967
  originY: ctx.yScale(Math.max(midDepth, ctx.depthFrom)),
823
- header: `${d.from}\u2013${d.to} m \xB7 ${typeLabel}`,
968
+ header: `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
824
969
  description: d.description || ""
825
970
  });
826
971
  });
@@ -866,6 +1011,19 @@ function drawAnnotationLabels(ctx, data) {
866
1011
  }
867
1012
  }
868
1013
 
1014
+ // src/renderers/caves.renderer.ts
1015
+ import * as d3 from "d3";
1016
+
1017
+ // src/utils/key.utils.ts
1018
+ var makeIntervalKey = (type) => (d, _i) => {
1019
+ const { key, from, to } = d;
1020
+ return key ? `${key}` : `${type}:${from}:${to}`;
1021
+ };
1022
+ var makePointKey = (type) => (d, i) => {
1023
+ const { key, depth } = d;
1024
+ return key ? `${key}` : `${type}:${depth}:${i}`;
1025
+ };
1026
+
869
1027
  // src/renderers/caves.renderer.ts
870
1028
  function drawCaves(ctx, data) {
871
1029
  if (!data.length) return;
@@ -882,9 +1040,14 @@ function drawCaves(ctx, data) {
882
1040
  classes,
883
1041
  tooltips
884
1042
  } = ctx;
885
- cavesGroup.selectAll(`g.${classes.caves.item}`).remove();
886
1043
  const rc = renderConfig.caves;
887
- data.forEach((cave) => {
1044
+ const groups = cavesGroup.selectAll(`g.${classes.caves.item}`).data(data, makeIntervalKey("cave"));
1045
+ groups.exit().transition(ctx.transition).style("opacity", 0).remove();
1046
+ const entered = groups.enter().append("g").attr("class", classes.caves.item).style("cursor", "pointer").on("mouseover", tooltips.cave.show).on("mouseout", tooltips.cave.hide).style("opacity", 0);
1047
+ const merged = mergeEnter(entered, groups);
1048
+ merged.each(function(cave) {
1049
+ const g = d3.select(this);
1050
+ g.selectAll("*").remove();
888
1051
  const seedTop = cave.from * 100 + cave.to;
889
1052
  const seedBot = cave.to * 100 + cave.from + 999;
890
1053
  const rngTop = makeSeededPrng(seedTop);
@@ -911,12 +1074,12 @@ function drawCaves(ctx, data) {
911
1074
  const botReversed = botCut ? `L ${geoXLeft.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath([...botPts].reverse(), { omitMoveTo: true });
912
1075
  const closedPath = `${topPath} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}${botReversed} L ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} Z`;
913
1076
  const caveTexture = cave.water_intake ? textures3.cave_wet : textures3.cave_dry;
914
- 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);
915
1077
  const caveStroke = cave.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
916
1078
  g.append("path").attr("class", classes.caves.fill).attr("d", closedPath).attr("fill", caveTexture.url()).attr("fill-opacity", theme.cave.fillOpacity).attr("stroke", "none");
917
1079
  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");
918
1080
  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");
919
1081
  });
1082
+ merged.transition(ctx.transition).style("opacity", 1);
920
1083
  }
921
1084
 
922
1085
  // src/renderers/construction-labels.renderer.ts
@@ -927,8 +1090,8 @@ function drawConstructionLabels(ctx, data) {
927
1090
  ctx.groups.constructionLabelsGroup.selectAll("*").remove();
928
1091
  const clc = ctx.renderConfig.constructionLabels;
929
1092
  const clct = ctx.theme.constructionLabels;
930
- const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter);
931
- const dUnit = getDiameterUnit(ctx.units.diameter);
1093
+ const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter, ctx.locale);
1094
+ const dUnit = getDiameterUnit(ctx.units.diameter, ctx.locale);
932
1095
  const maxXValues = getProfileDiamValues(ctx.constructionData);
933
1096
  const xScale = d32.scaleLinear().domain([0, d32.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
934
1097
  const occupied = [];
@@ -990,7 +1153,7 @@ function drawConstructionLabels(ctx, data) {
990
1153
  }).forEach(
991
1154
  (d) => placeLabel(
992
1155
  d,
993
- `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${d.type} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot_mm)}${dUnit}`
1156
+ `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${d.type} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot)}${dUnit}`
994
1157
  )
995
1158
  );
996
1159
  }
@@ -1007,9 +1170,10 @@ function drawConstructive(ctx, data) {
1007
1170
  const maxXValueConstruction = d33.max(maxXValues) || 0;
1008
1171
  const xScale = d33.scaleLinear().domain([0, maxXValueConstruction]).range([0, ctx.POCO_WIDTH]);
1009
1172
  const rcc = ctx.renderConfig.construction;
1173
+ const yPos = getYPos;
1010
1174
  ctx.groups.constructionGroup.selectAll(`.${ctx.classes.cementPad.item}`).remove();
1011
1175
  if (data.cement_pad && data.cement_pad.thickness && ctx.depthFrom === 0) {
1012
- const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad]);
1176
+ const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad], () => "cement_pad");
1013
1177
  cementPad.exit().remove();
1014
1178
  const newCementPad = cementPad.enter().append("rect").attr("class", ctx.classes.cementPad.item).attr(
1015
1179
  "x",
@@ -1024,36 +1188,19 @@ function drawConstructive(ctx, data) {
1024
1188
  }).attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth);
1025
1189
  newCementPad.on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1026
1190
  }
1027
- const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, stableLayerKey);
1191
+ const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, makeIntervalKey("bore_hole"));
1028
1192
  hole.exit().transition(ctx.transition).attr("height", 0).remove();
1029
- 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("stroke-width", ctx.theme.boreHole.strokeWidth).attr("stroke-dasharray", ctx.theme.boreHole.strokeDasharray).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter)).attr("y", getYPos).attr("height", getHeight).attr("opacity", 0).on("mouseover", ctx.tooltips.hole.show).on("mouseout", ctx.tooltips.hole.hide);
1030
- withTransition(newHole, ctx.transition).attr(
1031
- "opacity",
1032
- ctx.theme.boreHole.opacity
1033
- );
1034
- hole.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1035
- withTransition(hole, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1036
- const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case, stableLayerKey);
1193
+ 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).attr("y", yPos).attr("height", 0);
1194
+ const mergedHole = mergeEnter(newHole, hole).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1195
+ withTransition(mergedHole, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1196
+ const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case, makeIntervalKey("surface_case"));
1037
1197
  surfaceCaseGs.exit().transition(ctx.transition).style("opacity", 0).remove();
1038
- const newSC = surfaceCaseGs.enter().append("g").attr("class", ctx.classes.surfaceCase.rect).style("opacity", 0).on("mouseover", ctx.tooltips.surfaceCase.show).on("mouseout", ctx.tooltips.surfaceCase.hide);
1039
- newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none");
1198
+ const newSC = surfaceCaseGs.enter().append("g").attr("class", ctx.classes.surfaceCase.rect).on("mouseover", ctx.tooltips.surfaceCase.show).on("mouseout", ctx.tooltips.surfaceCase.hide);
1199
+ newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none").attr("y", yPos).attr("height", 0);
1040
1200
  newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1041
1201
  newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1042
- newSC.each((d, i, nodes) => {
1043
- const x = (ctx.POCO_CENTER - xScale(
1044
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1045
- )) / 2;
1046
- const w = xScale(
1047
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1048
- );
1049
- const g = d33.select(nodes[i]);
1050
- g.select(".surface-case-fill").attr("x", x).attr("width", w).attr("fill", ctx.textures.surface_case.url()).attr("y", getYPos(d)).attr("height", getHeight(d));
1051
- const sideNodes = g.selectAll(".surface-case-side").nodes();
1052
- d33.select(sideNodes[0]).attr("x1", x).attr("x2", x).attr("y1", getYPos(d)).attr("y2", getYPos(d) + getHeight(d));
1053
- d33.select(sideNodes[1]).attr("x1", x + w).attr("x2", x + w).attr("y1", getYPos(d)).attr("y2", getYPos(d) + getHeight(d));
1054
- });
1055
- withTransition(newSC, ctx.transition).style("opacity", 1);
1056
- surfaceCaseGs.each((d, i, nodes) => {
1202
+ const mergedSC = mergeEnter(newSC, surfaceCaseGs);
1203
+ mergedSC.each((d, i, nodes) => {
1057
1204
  const x = (ctx.POCO_CENTER - xScale(
1058
1205
  d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1059
1206
  )) / 2;
@@ -1067,47 +1214,61 @@ function drawConstructive(ctx, data) {
1067
1214
  d33.select(sideNodes[1]).attr("x1", x + w).attr("x2", x + w);
1068
1215
  });
1069
1216
  withTransition(
1070
- surfaceCaseGs.select(".surface-case-fill"),
1217
+ mergedSC.select(".surface-case-fill"),
1071
1218
  ctx.transition
1072
1219
  ).attr("y", (d) => getYPos(d)).attr("height", (d) => getHeight(d));
1073
- withTransition(
1074
- surfaceCaseGs.selectAll(".surface-case-side"),
1075
- ctx.transition
1076
- ).attr("y1", (d) => getYPos(d)).attr(
1220
+ mergedSC.selectAll(".surface-case-side").attr("y1", (d) => getYPos(d)).attr(
1077
1221
  "y2",
1078
1222
  (d) => getYPos(d) + getHeight(d)
1079
1223
  );
1080
- const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, stableLayerKey);
1224
+ const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, makeIntervalKey("hole_fill"));
1081
1225
  holeFill.exit().transition(ctx.transition).attr("height", 0).remove();
1082
- 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).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter)).attr("y", getYPos).attr("height", getHeight).attr("fill", (d) => ctx.textures[d.type].url()).attr("opacity", 0).on("mouseover", ctx.tooltips.holeFill.show).on("mouseout", ctx.tooltips.holeFill.hide);
1083
- withTransition(newHoleFill, ctx.transition).attr("opacity", 1);
1084
- holeFill.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter)).attr("fill", (d) => ctx.textures[d.type].url());
1085
- withTransition(holeFill, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1086
- const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case, stableLayerKey);
1226
+ 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).attr("y", yPos).attr("height", 0);
1227
+ const mergedHoleFill = mergeEnter(newHoleFill, holeFill).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1228
+ withTransition(mergedHoleFill, ctx.transition).attr("y", getYPos).attr("height", getHeight).attr("fill", (d) => ctx.textures[d.type].url());
1229
+ const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case, makeIntervalKey("well_case"));
1087
1230
  wellCase.exit().transition(ctx.transition).attr("height", 0).remove();
1088
- 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).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter)).attr("y", getYPos).attr("height", getHeight).attr("opacity", 0).on("mouseover", ctx.tooltips.wellCase.show).on("mouseout", ctx.tooltips.wellCase.hide);
1089
- withTransition(newWellCase, ctx.transition).attr("opacity", 1);
1090
- wellCase.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1091
- withTransition(wellCase, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1092
- const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen, stableLayerKey);
1231
+ 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).attr("y", yPos).attr("height", 0);
1232
+ const mergedWellCase = mergeEnter(newWellCase, wellCase).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1233
+ withTransition(mergedWellCase, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1234
+ const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen, makeIntervalKey("well_screen"));
1093
1235
  wellScreen.exit().transition(ctx.transition).attr("height", 0).remove();
1094
- 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).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter)).attr("y", getYPos).attr("height", getHeight).attr("opacity", 0).on("mouseover", ctx.tooltips.wellScreen.show).on("mouseout", ctx.tooltips.wellScreen.hide);
1095
- withTransition(newWellScreen, ctx.transition).attr("opacity", 1);
1096
- wellScreen.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1097
- withTransition(wellScreen, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1236
+ 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).attr("y", yPos).attr("height", 0);
1237
+ const mergedWellScreen = mergeEnter(newWellScreen, wellScreen).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1238
+ withTransition(mergedWellScreen, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1239
+ const reductionPoints = (d, yTop, yBot) => {
1240
+ const xTopLeft = (ctx.POCO_CENTER - xScale(d.diam_from)) / 2;
1241
+ const xTopRight = (ctx.POCO_CENTER + xScale(d.diam_from)) / 2;
1242
+ const xBotLeft = (ctx.POCO_CENTER - xScale(d.diam_to)) / 2;
1243
+ const xBotRight = (ctx.POCO_CENTER + xScale(d.diam_to)) / 2;
1244
+ return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
1245
+ };
1246
+ const reduction = ctx.groups.reductionGroup.selectAll(`.${ctx.classes.reduction.item}`).data(data.reduction, makeIntervalKey("reduction"));
1247
+ reduction.exit().transition(ctx.transition).style("opacity", 0).remove();
1248
+ const collapsedPoints = (d) => {
1249
+ const r = d;
1250
+ const y = getYPos(r);
1251
+ return reductionPoints(r, y, y);
1252
+ };
1253
+ const newReduction = reduction.enter().append("polygon").attr("class", ctx.classes.reduction.item).attr("fill", ctx.theme.reduction.fill).attr("stroke", ctx.theme.reduction.stroke).attr("stroke-width", ctx.theme.reduction.strokeWidth).on("mouseover", ctx.tooltips.reduction.show).on("mouseout", ctx.tooltips.reduction.hide).attr("points", collapsedPoints);
1254
+ const mergedReduction = mergeEnter(newReduction, reduction);
1255
+ withTransition(mergedReduction, ctx.transition).attr(
1256
+ "points",
1257
+ (d) => reductionPoints(d, getYPos(d), getYPos(d) + getHeight(d))
1258
+ );
1098
1259
  const conflictAreas = [];
1099
1260
  conflictAreas.push(...getConflictAreas(data.well_case, data.well_screen));
1100
1261
  conflictAreas.push(...getConflictAreas(data.well_screen, data.well_case));
1101
1262
  const mergedConflicts = mergeConflicts(conflictAreas, 1);
1102
- const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts, (d) => `${d.from}_${d.to}_${d.diameter}`);
1263
+ const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts, makeIntervalKey("conflict"));
1103
1264
  conflict.exit().transition(ctx.transition).attr("height", 0).remove();
1104
- 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).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter)).attr("y", getYPos).attr("height", getHeight).attr("opacity", 0).on("mouseover", ctx.tooltips.conflict.show).on("mouseout", ctx.tooltips.conflict.hide);
1105
- withTransition(newConflict, ctx.transition).attr("opacity", 1);
1106
- conflict.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1107
- withTransition(conflict, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1265
+ 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).attr("y", ctx.yScale(0)).attr("height", 0);
1266
+ const mergedConflict = mergeEnter(newConflict, conflict).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1267
+ withTransition(mergedConflict, ctx.transition).attr("y", ctx.yScale(0)).attr("height", getHeight);
1108
1268
  }
1109
1269
 
1110
1270
  // src/renderers/fractures.renderer.ts
1271
+ import * as d34 from "d3";
1111
1272
  function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1112
1273
  const pts = [];
1113
1274
  for (let i = 0; i < steps; i++) {
@@ -1130,19 +1291,23 @@ function drawFractures(ctx, data) {
1130
1291
  classes,
1131
1292
  tooltips
1132
1293
  } = ctx;
1133
- fracturesGroup.selectAll(`g.${classes.fractures.item}`).remove();
1134
1294
  const rf = renderConfig.fractures;
1135
1295
  const halfWidth = POCO_WIDTH * rf.widthMultiplier / 2;
1136
1296
  const xa = pocoCenterX - halfWidth;
1137
1297
  const w = halfWidth * 2;
1138
1298
  const normToAbsX = (normX) => xa + normX * w;
1139
- data.forEach((fracture) => {
1299
+ const groups = fracturesGroup.selectAll(`g.${classes.fractures.item}`).data(data, makePointKey("fracture"));
1300
+ groups.exit().transition(ctx.transition).style("opacity", 0).remove();
1301
+ const entered = groups.enter().append("g").attr("class", classes.fractures.item).on("mouseover", tooltips.fracture.show).on("mouseout", tooltips.fracture.hide).style("cursor", "pointer").style("opacity", 0);
1302
+ const merged = mergeEnter(entered, groups);
1303
+ merged.attr(
1304
+ "transform",
1305
+ (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fracture.dip},${pocoCenterX},0)`
1306
+ );
1307
+ merged.each(function(fracture) {
1308
+ const g = d34.select(this);
1309
+ g.selectAll("*").remove();
1140
1310
  const rng = makeSeededPrng(fracture.depth, 7919);
1141
- const cy = yScale(fracture.depth);
1142
- const g = fracturesGroup.append("g").attr("class", classes.fractures.item).datum(fracture).attr(
1143
- "transform",
1144
- `translate(0,${cy}) rotate(${fracture.dip},${pocoCenterX},0)`
1145
- ).on("mouseover", tooltips.fracture.show).on("mouseout", tooltips.fracture.hide).style("cursor", "pointer");
1146
1311
  const hitBuffer = fracture.swarm ? rf.hitBuffer.swarm : rf.hitBuffer.single;
1147
1312
  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");
1148
1313
  const fractureStroke = fracture.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
@@ -1215,10 +1380,11 @@ function drawFractures(ctx, data) {
1215
1380
  }
1216
1381
  }
1217
1382
  });
1383
+ merged.transition(ctx.transition).style("opacity", 1);
1218
1384
  }
1219
1385
 
1220
1386
  // src/renderers/highlights.renderer.ts
1221
- import * as d34 from "d3";
1387
+ import * as d35 from "d3";
1222
1388
  import { getProfileDiamValues as getProfileDiamValues3 } from "@welldot/utils";
1223
1389
  function drawHighlightItem(ctx, group, x, y, w, h, label) {
1224
1390
  if (h <= 0 || w <= 0) return;
@@ -1279,7 +1445,7 @@ function drawHighlights(ctx, hl) {
1279
1445
  );
1280
1446
  });
1281
1447
  const maxXVals = getProfileDiamValues3(ctx.constructionData);
1282
- const xScaleConstr = d34.scaleLinear().domain([0, d34.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1448
+ const xScaleConstr = d35.scaleLinear().domain([0, d35.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1283
1449
  const constructionConfig = ctx.renderConfig.construction;
1284
1450
  if (hl.cement_pad && ctx.depthFrom === 0 && ctx.constructionData.cement_pad?.thickness) {
1285
1451
  const cp = ctx.constructionData.cement_pad;
@@ -1359,18 +1525,29 @@ function drawHighlights(ctx, hl) {
1359
1525
  stdX,
1360
1526
  stdW
1361
1527
  );
1528
+ drawConstrHighlights(
1529
+ hl.reduction,
1530
+ ctx.constructionData.reduction.map((d) => ({
1531
+ from: d.from,
1532
+ to: d.to,
1533
+ diameter: Math.max(d.diam_from, d.diam_to)
1534
+ })),
1535
+ stdX,
1536
+ stdW
1537
+ );
1362
1538
  }
1363
1539
 
1364
1540
  // src/renderers/legend.renderer.ts
1365
- import * as d3module2 from "d3";
1541
+ import * as d3module from "d3";
1366
1542
  import { defu } from "defu";
1367
- var d35 = { ...d3module2 };
1543
+ var d36 = { ...d3module };
1368
1544
  function drawWellLegend(selector, profile, options = {}) {
1369
1545
  const fractures = profile.fractures ?? [];
1370
1546
  const caves = profile.caves ?? [];
1371
1547
  const boreHoles = profile.bore_hole ?? [];
1372
1548
  const wellCases = profile.well_case ?? [];
1373
1549
  const wellScreens = profile.well_screen ?? [];
1550
+ const reductions = profile.reduction ?? [];
1374
1551
  const holeFills = profile.hole_fill ?? [];
1375
1552
  const hasSimple = fractures.some((f) => !f.swarm && !f.water_intake);
1376
1553
  const hasSwarm = fractures.some((f) => f.swarm);
@@ -1383,6 +1560,7 @@ function drawWellLegend(selector, profile, options = {}) {
1383
1560
  const hasHoleFillSeal = holeFills.some((h) => h.type === "seal");
1384
1561
  const hasWellCase = wellCases.length > 0;
1385
1562
  const hasWellScreen = wellScreens.length > 0;
1563
+ const hasReduction = reductions.length > 0;
1386
1564
  const hasCementPad = !!profile.cement_pad?.thickness;
1387
1565
  const hasConflict = mergeConflicts(
1388
1566
  [
@@ -1469,13 +1647,19 @@ function drawWellLegend(selector, profile, options = {}) {
1469
1647
  label: cfg.labels.wellScreen,
1470
1648
  subKind: "wellScreen"
1471
1649
  });
1650
+ if (hasReduction)
1651
+ items.push({
1652
+ kind: "construction",
1653
+ label: cfg.labels.reduction,
1654
+ subKind: "reduction"
1655
+ });
1472
1656
  if (hasConflict)
1473
1657
  items.push({
1474
1658
  kind: "construction",
1475
1659
  label: cfg.labels.conflict,
1476
1660
  subKind: "conflict"
1477
1661
  });
1478
- const svgEl = d35.select(selector);
1662
+ const svgEl = d36.select(selector);
1479
1663
  svgEl.selectAll("*").remove();
1480
1664
  if (items.length === 0) {
1481
1665
  svgEl.attr("width", 0).attr("height", 0);
@@ -1527,6 +1711,15 @@ function drawWellLegend(selector, profile, options = {}) {
1527
1711
  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");
1528
1712
  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);
1529
1713
  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);
1714
+ } else if (item.subKind === "reduction") {
1715
+ const topW = rw * 0.5;
1716
+ const y1 = rowSymY - rh / 2;
1717
+ const y2 = rowSymY + rh / 2;
1718
+ const midX = cx + rw / 2;
1719
+ symG.append("polygon").attr("class", cls.constructionRect).attr(
1720
+ "points",
1721
+ `${midX - topW / 2},${y1} ${midX + topW / 2},${y1} ${cx + rw},${y2} ${cx},${y2}`
1722
+ ).attr("fill", theme.reduction.fill).attr("stroke", theme.reduction.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
1530
1723
  } else {
1531
1724
  const symMap = {
1532
1725
  boreHole: {
@@ -1584,13 +1777,11 @@ function drawLithology(ctx, data) {
1584
1777
  from: depthFrom,
1585
1778
  to: depthTo
1586
1779
  });
1587
- const fillFn = getLithologyFill(data, svg, ctx.theme.lithologyTexture);
1588
- const rects = lithologyGroup.selectAll(`.${classes.lithology.rect}`).data(data, stableLayerKey);
1780
+ const rects = lithologyGroup.selectAll(`.${classes.lithology.rect}`).data(data, makeIntervalKey("lithology"));
1589
1781
  rects.exit().transition(transition).attr("height", 0).remove();
1590
- 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).attr("y", getYPos).attr("height", getHeight).attr("fill", fillFn).attr("opacity", 0).on("mouseover", tooltips.geology.show).on("mouseout", tooltips.geology.hide);
1591
- withTransition(newLayers, transition).attr("opacity", 1);
1592
- rects.attr("fill", fillFn);
1593
- withTransition(rects, transition).attr("y", getYPos).attr("height", getHeight);
1782
+ 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).attr("y", getYPos).attr("height", 0);
1783
+ const merged = mergeEnter(newLayers, rects);
1784
+ withTransition(merged, transition).attr("y", getYPos).attr("height", getHeight).attr("fill", getLithologyFill(data, svg, ctx.theme.lithologyTexture));
1594
1785
  }
1595
1786
 
1596
1787
  // src/renderers/unit-labels.renderer.ts
@@ -1677,20 +1868,26 @@ function buildSvgStyleBlock(theme) {
1677
1868
  `.wp-annotation-label{font-size:${s(labels.fontSize)}px}`,
1678
1869
  `.wp-annotation-header{font-weight:${s(labels.headerFontWeight ?? 600)};font-family:${s(labels.headerFont)};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`,
1679
1870
  `.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)}}`,
1680
- `.wp-depth-tip-text{font-family:${s(labels.bodyFont ?? "sans-serif")};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`
1871
+ `.wp-depth-tip-text{font-family:${s(labels.bodyFont ?? "sans-serif")};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`,
1872
+ `.wp-tooltip{position:absolute;z-index:1000;display:flex;flex-direction:column;gap:2px;max-width:240px;padding:8px 10px;border-radius:6px;background:${s(labels.annotationBg)};border:1px solid ${s(labels.annotationBorderColor ?? "rgba(0,0,0,0.08)")};box-shadow:0 4px 12px rgba(0,0,0,0.15);font-family:${s(labels.bodyFont ?? "sans-serif")};line-height:1.45}`,
1873
+ `.wp-tooltip-title{font-family:${s(labels.headerFont)};font-weight:${s(labels.headerFontWeight ?? 600)};font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:${s(labels.color)}}`,
1874
+ `.wp-tooltip-primary{font-weight:${s(labels.headerFontWeight ?? 600)};font-size:12px;color:${s(labels.color)}}`,
1875
+ `.wp-tooltip-secondary{font-size:11px;color:${s(labels.bodyColor)}}`
1681
1876
  ].join("");
1682
1877
  }
1683
1878
 
1684
1879
  // src/Renderer.ts
1685
- var d36 = Object.assign(d3module3, { tip: d3tip2 });
1880
+ var d37 = { ...d3module2, tip: d3tip2 };
1686
1881
  var WellRenderer = class {
1687
1882
  /**
1688
1883
  * @param svgs - One entry per SVG panel; multi-entry for split-panel layouts.
1689
1884
  * @param options.classNames - Override default BEM CSS class names.
1690
1885
  * @param options.units - Active measurement units (`length` and `diameter`).
1886
+ * @param options.locale - Locale for renderer-drawn text (diameter symbol, and any `RenderConfig` fields resolved via `applyRenderLocale`). Defaults to `'pt'`, matching the package's historical output.
1691
1887
  * @param options.renderConfig - Rendering behaviour (zoom, pan, animation, layout). Defaults to `INTERACTIVE_RENDER_CONFIG`.
1692
1888
  * @param options.theme - Visual theme overrides. Merged on top of `DEFAULT_WELL_THEME`.
1693
1889
  * @param options.onError - Called when a draw error occurs (e.g., a renderer throws). Falls back to `console.error` if omitted.
1890
+ * @param options.onZoom - Called with the current zoom scale (`1` = initial/fit) whenever the user zooms or pans, and whenever `zoomBy`/`resetZoom` are called. No-op if `renderConfig.zoom`/`pan` are both disabled.
1694
1891
  */
1695
1892
  constructor(svgs, options = {}) {
1696
1893
  this.svgInstances = [];
@@ -1703,6 +1900,7 @@ var WellRenderer = class {
1703
1900
  length: "m",
1704
1901
  diameter: "mm"
1705
1902
  };
1903
+ this.locale = "pt";
1706
1904
  if (svgs.length === 0) {
1707
1905
  console.warn(
1708
1906
  "[WellRenderer] No SVG instances provided. The renderer will be a no-op."
@@ -1711,6 +1909,7 @@ var WellRenderer = class {
1711
1909
  }
1712
1910
  this.svgInstances = svgs;
1713
1911
  if (options.onError) this.onError = options.onError;
1912
+ if (options.onZoom) this.onZoom = options.onZoom;
1714
1913
  if (options.classNames) {
1715
1914
  this.classes = defu2(
1716
1915
  options.classNames,
@@ -1720,6 +1919,9 @@ var WellRenderer = class {
1720
1919
  if (options.units) {
1721
1920
  this.units = { ...this.units, ...options.units };
1722
1921
  }
1922
+ if (options.locale) {
1923
+ this.locale = options.locale;
1924
+ }
1723
1925
  if (options.renderConfig) {
1724
1926
  this.renderConfig = defu2(
1725
1927
  options.renderConfig,
@@ -1735,7 +1937,7 @@ var WellRenderer = class {
1735
1937
  const { selector, height, width, margins } = inst;
1736
1938
  const svgWidth = width + margins.left + margins.right;
1737
1939
  const svgHeight = height + margins.top + margins.bottom;
1738
- const svg = d36.select(selector);
1940
+ const svg = d37.select(selector);
1739
1941
  svg.selectAll("*").remove();
1740
1942
  svg.attr("height", svgHeight).attr("width", svgWidth);
1741
1943
  const defs = svg.append("defs");
@@ -1755,14 +1957,15 @@ var WellRenderer = class {
1755
1957
  );
1756
1958
  const fracturesGroup = poco.append("g").attr("class", this.classes.fractures.group).attr("transform", `translate(${margins.left}, ${margins.top})`).attr("clip-path", `url(#${clipId})`);
1757
1959
  fracturesGroup.append("g").attr("class", this.classes.highlights.fracturesGroup);
1758
- construction.append("g").attr("class", this.classes.constructionLabels.group);
1759
1960
  construction.append("g").attr("class", this.classes.cementPad.group);
1760
1961
  construction.append("g").attr("class", this.classes.boreHole.group);
1761
1962
  construction.append("g").attr("class", this.classes.surfaceCase.group);
1762
1963
  construction.append("g").attr("class", this.classes.holeFill.group);
1763
1964
  construction.append("g").attr("class", this.classes.wellCase.group);
1764
1965
  construction.append("g").attr("class", this.classes.wellScreen.group);
1966
+ construction.append("g").attr("class", this.classes.reduction.group);
1765
1967
  construction.append("g").attr("class", this.classes.conflict.group);
1968
+ construction.append("g").attr("class", this.classes.constructionLabels.group);
1766
1969
  construction.append("g").attr("class", this.classes.highlights.constructionGroup);
1767
1970
  Object.values(this.textures).forEach((texture) => svg.call(texture));
1768
1971
  return { svg, height, width, margins, clipId, clipRectId };
@@ -1776,6 +1979,36 @@ var WellRenderer = class {
1776
1979
  textures: this.renderConfig.textures
1777
1980
  });
1778
1981
  }
1982
+ /**
1983
+ * Multiplies the current zoom scale by `factor` (e.g. `1.25` to zoom in,
1984
+ * `1 / 1.25` to zoom out) on every panel. No-op if both `renderConfig.zoom`
1985
+ * and `renderConfig.pan` are disabled (no zoom behavior was attached).
1986
+ */
1987
+ zoomBy(factor) {
1988
+ for (const state of this.instanceStates) {
1989
+ if (!state.zoomNode) continue;
1990
+ asSvgElement(state.svg).transition().call(state.zoomNode.scaleBy, factor);
1991
+ }
1992
+ }
1993
+ /**
1994
+ * Resets zoom/pan on every panel back to `renderConfig.zoomLevel`
1995
+ * (defaults to `1`, i.e. the initial fit-to-container view).
1996
+ */
1997
+ resetZoom() {
1998
+ const target = d37.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
1999
+ for (const state of this.instanceStates) {
2000
+ if (!state.zoomNode) continue;
2001
+ asSvgElement(state.svg).transition().call(state.zoomNode.transform, target);
2002
+ }
2003
+ }
2004
+ /**
2005
+ * Current zoom scale factor of the first panel (`1` = initial/fit).
2006
+ * Returns `1` if zoom/pan is disabled or `prepareSvg()` hasn't run yet.
2007
+ */
2008
+ getZoomScale() {
2009
+ const node = this.instanceStates[0]?.svg.node();
2010
+ return node ? d37.zoomTransform(node).k : 1;
2011
+ }
1779
2012
  /**
1780
2013
  * Initialises the SVG DOM structure and preloads FGDC textures.
1781
2014
  * Must be called once before the first `draw()`.
@@ -1790,14 +2023,23 @@ var WellRenderer = class {
1790
2023
  * Renders the well profile into all configured SVG panels.
1791
2024
  * @param profile - The well data to render.
1792
2025
  * @param options.units - Override the instance-level measurement units for this render.
2026
+ * @param options.locale - Override the instance-level locale for this render.
1793
2027
  * @param options.highlights - Highlight overlays to display on top of the profile.
1794
2028
  */
1795
2029
  draw(profile, options = {}) {
1796
2030
  if (isWellEmpty(profile)) return;
2031
+ if (profile.version !== 2) {
2032
+ console.warn(
2033
+ `[WellRenderer] Received a .well file with version ${profile.version ?? "unknown"}. Only v2 is supported. Normalize with deserializeWell() from @welldot/core before rendering.`
2034
+ );
2035
+ }
1797
2036
  this.units = { ...this.units, ...options.units };
2037
+ if (options.locale) {
2038
+ this.locale = options.locale;
2039
+ }
1798
2040
  const highlights = options.highlights ?? {};
1799
2041
  const maxValues = getProfileLastItemsDepths(profile);
1800
- const maxYValues = d36.max(maxValues) || 0;
2042
+ const maxYValues = d37.max(maxValues) || 0;
1801
2043
  if (this.instanceStates.length === 1) {
1802
2044
  this.drawLogToInstance(
1803
2045
  this.instanceStates[0],
@@ -1809,7 +2051,7 @@ var WellRenderer = class {
1809
2051
  return;
1810
2052
  }
1811
2053
  const totalHeight = this.instanceStates.reduce((s, st) => s + st.height, 0) || 1;
1812
- const yScaleGlobal = d36.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
2054
+ const yScaleGlobal = d37.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
1813
2055
  let currentDepth = 0;
1814
2056
  for (const state of this.instanceStates) {
1815
2057
  const maxSvgDepth = yScaleGlobal.invert(
@@ -1841,6 +2083,7 @@ var WellRenderer = class {
1841
2083
  const holeFillGroup = svg.select(`.${this.classes.holeFill.group}`);
1842
2084
  const wellCaseGroup = svg.select(`.${this.classes.wellCase.group}`);
1843
2085
  const wellScreenGroup = svg.select(`.${this.classes.wellScreen.group}`);
2086
+ const reductionGroup = svg.select(`.${this.classes.reduction.group}`);
1844
2087
  const conflictGroup = svg.select(`.${this.classes.conflict.group}`);
1845
2088
  const constructionLabelsGroup = svg.select(
1846
2089
  `.${this.classes.constructionLabels.group}`
@@ -1861,12 +2104,14 @@ var WellRenderer = class {
1861
2104
  const POCO_WIDTH = svgWidth * pocoWidthRatio;
1862
2105
  const POCO_CENTER = svgWidth * pocoCenterRatio;
1863
2106
  const { duration, ease } = this.renderConfig.animation;
1864
- const transition = d36.transition().duration(duration).ease(ease);
2107
+ const transition = d37.transition().duration(duration).ease(ease);
1865
2108
  const tooltips = populateTooltips(
1866
2109
  svg,
1867
2110
  this.classes,
1868
2111
  this.units,
1869
- this.renderConfig.tooltips
2112
+ this.renderConfig.tooltips,
2113
+ this.renderConfig.tooltipLabels,
2114
+ this.locale
1870
2115
  );
1871
2116
  const { xLeft: geoXLeft, xRightInset: geoXRightInset } = this.renderConfig.geologic;
1872
2117
  const geoXRight = svgWidth - geoXRightInset;
@@ -1887,11 +2132,14 @@ var WellRenderer = class {
1887
2132
  fractures: profile.fractures
1888
2133
  };
1889
2134
  const contentHeight = svgHeight - state.margins.top - state.margins.bottom;
1890
- const yScaleLocal = d36.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
2135
+ const yScaleLocal = d37.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
1891
2136
  const depthFactor = this.units.length === "ft" ? 3.28084 : 1;
1892
- const yScaleAxis = d36.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
1893
- const yAxis = d36.axisLeft(yScaleAxis).tickFormat(
1894
- (d) => `${d}${getLengthUnit(this.units.length)}`
2137
+ const yScaleAxis = d37.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
2138
+ const yAxis = d37.axisLeft(yScaleAxis).tickFormat(
2139
+ (d) => `${formatNumber3(Number(d), {
2140
+ maximumFractionDigits: this.units.length === "ft" ? 1 : 2,
2141
+ locale: this.locale
2142
+ })}${getLengthUnit(this.units.length)}`
1895
2143
  );
1896
2144
  const gY = svg.select(`.${this.classes.yAxis}`).call(yAxis);
1897
2145
  gY.select(".domain").attr("stroke", this.theme.labels.color).attr("fill", "none");
@@ -1928,6 +2176,7 @@ var WellRenderer = class {
1928
2176
  holeFillGroup,
1929
2177
  wellCaseGroup,
1930
2178
  wellScreenGroup,
2179
+ reductionGroup,
1931
2180
  conflictGroup,
1932
2181
  highlightsGeologicGroup,
1933
2182
  highlightsConstructionGroup,
@@ -1953,6 +2202,7 @@ var WellRenderer = class {
1953
2202
  classes: this.classes,
1954
2203
  textures: this.textures,
1955
2204
  units: this.units,
2205
+ locale: this.locale,
1956
2206
  constructionData,
1957
2207
  groups
1958
2208
  };
@@ -1983,12 +2233,24 @@ var WellRenderer = class {
1983
2233
  surfaceCaseGroup.selectAll(`g.${this.classes.surfaceCase.rect}`).each((d, i, nodes) => {
1984
2234
  if (!d) return;
1985
2235
  const sc = d;
1986
- const g = d36.select(nodes[i]);
2236
+ const g = d37.select(nodes[i]);
1987
2237
  const y = transform.applyY(spanY(sc));
1988
2238
  const h = transform.k * spanH(sc);
1989
2239
  g.select(".surface-case-fill").attr("y", y).attr("height", h);
1990
2240
  g.selectAll(".surface-case-side").attr("y1", y).attr("y2", y + h);
1991
2241
  });
2242
+ const maxXValsReduction = getProfileDiamValues4(constructionData);
2243
+ const xScaleReduction = d37.scaleLinear().domain([0, d37.max(maxXValsReduction) || 0]).range([0, POCO_WIDTH]);
2244
+ reductionGroup.selectAll(`polygon.${this.classes.reduction.item}`).attr("points", (d) => {
2245
+ const r = d;
2246
+ const yTop = transform.applyY(spanY(r));
2247
+ const yBot = yTop + transform.k * spanH(r);
2248
+ const xTopLeft = (POCO_CENTER - xScaleReduction(r.diam_from)) / 2;
2249
+ const xTopRight = (POCO_CENTER + xScaleReduction(r.diam_from)) / 2;
2250
+ const xBotLeft = (POCO_CENTER - xScaleReduction(r.diam_to)) / 2;
2251
+ const xBotRight = (POCO_CENTER + xScaleReduction(r.diam_to)) / 2;
2252
+ return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
2253
+ });
1992
2254
  fracturesGroup.selectAll(`g.${this.classes.fractures.item}`).attr("transform", (d) => {
1993
2255
  if (!d) return null;
1994
2256
  const f = d;
@@ -2010,6 +2272,7 @@ var WellRenderer = class {
2010
2272
  well_screen: constructionData.well_screen.filter(inDepth)
2011
2273
  });
2012
2274
  drawHighlights(zoomedCtx, highlights);
2275
+ this.onZoom?.(transform.k);
2013
2276
  };
2014
2277
  const drawProfile = () => {
2015
2278
  drawLithology(ctx, profile.lithology?.filter(inDepth) ?? []);
@@ -2049,17 +2312,23 @@ var WellRenderer = class {
2049
2312
  }
2050
2313
  const { zoom: zoomEnabled, pan: panEnabled } = this.renderConfig;
2051
2314
  if (zoomEnabled || panEnabled) {
2052
- const zoomNode = d36.zoom();
2315
+ const zoomNode = d37.zoom();
2053
2316
  if (zoomEnabled || panEnabled) zoomNode.on("zoom", zooming);
2054
- if (!zoomEnabled) zoomNode.scaleExtent([1, 1]);
2317
+ zoomNode.scaleExtent(
2318
+ zoomEnabled ? [
2319
+ this.renderConfig.minZoomScale ?? 0,
2320
+ this.renderConfig.maxZoomScale ?? Infinity
2321
+ ] : [1, 1]
2322
+ );
2055
2323
  if (!panEnabled)
2056
2324
  zoomNode.filter((e) => e.type === "wheel" || e.type === "dblclick");
2057
2325
  asSvgElement(svg).call(zoomNode);
2326
+ state.zoomNode = zoomNode;
2058
2327
  const initialZoom = this.renderConfig.zoomLevel ?? 1;
2059
2328
  if (initialZoom !== 1) {
2060
2329
  asSvgElement(svg).call(
2061
2330
  zoomNode.transform,
2062
- d36.zoomIdentity.scale(initialZoom)
2331
+ d37.zoomIdentity.scale(initialZoom)
2063
2332
  );
2064
2333
  }
2065
2334
  }
@@ -2068,13 +2337,16 @@ var WellRenderer = class {
2068
2337
  export {
2069
2338
  DEFAULT_WELL_THEME,
2070
2339
  INTERACTIVE_RENDER_CONFIG,
2340
+ RENDER_LABELS,
2071
2341
  STATIC_RENDER_CONFIG,
2072
2342
  WellRenderer,
2343
+ applyRenderLocale,
2073
2344
  drawWellLegend,
2074
2345
  formatDiameter,
2075
2346
  formatLength,
2076
2347
  getDiameterUnit,
2077
2348
  getLengthUnit,
2078
- importFgdcTextures
2349
+ importFgdcTextures,
2350
+ resolveRenderLabel
2079
2351
  };
2080
2352
  //# sourceMappingURL=index.js.map