@welldot/render 0.1.0-next.20260509.039db95 → 0.2.2

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"
@@ -563,92 +705,102 @@ var makeSeededPrng = (seed, scramble = 6271) => {
563
705
  };
564
706
 
565
707
  // src/utils/tooltips.utils.ts
566
- import * as d3module from "d3";
567
708
  import d3tip from "d3-tip";
568
709
  import sanitizeHtml from "sanitize-html";
569
- var d3 = Object.assign(d3module, { tip: d3tip });
710
+ import { formatNumber as formatNumber2 } from "@welldot/utils";
570
711
  var esc = (v) => sanitizeHtml(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
571
- var populateTooltips = (svg, customClasses, units, tooltipConfig) => {
712
+ var _tooltipCache = /* @__PURE__ */ new WeakMap();
713
+ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale) => {
572
714
  const svgEl = typeof svg.node === "function" ? svg.node() : null;
573
- if (svgEl?.getAttribute("data-tooltips-init") === "true") {
574
- return {};
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;
575
719
  }
576
720
  const tipsText = {
577
721
  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>` : ""}
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>` : ""}
583
727
  `,
584
728
  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>
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>
588
732
  `,
589
733
  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>
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>
593
737
  `,
594
738
  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>
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>
598
742
  <span class="${customClasses.tooltip.secondaryInfo}">
599
- <strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}
743
+ <strong>${labels.common.description}</strong> ${esc(d.description)}
600
744
  </span>
601
745
  `,
602
746
  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>
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>
605
749
  <span class="${customClasses.tooltip.secondaryInfo}">
606
- <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))}
607
751
  </span>
608
- <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>
609
753
  `,
610
754
  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>
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>
613
757
  <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>
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>
760
+ <span class="${customClasses.tooltip.secondaryInfo}">
761
+ <strong>${labels.wellScreen.slot}</strong> ${esc(formatNumber2(d.screen_slot, { maximumFractionDigits: 2, locale }))} mm
762
+ </span>
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>
616
767
  <span class="${customClasses.tooltip.secondaryInfo}">
617
- <strong>Ranhura:</strong> ${esc(d.screen_slot_mm)} mm
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))}
618
769
  </span>
770
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
619
771
  `,
620
772
  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>
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>
623
775
  `,
624
776
  fracture: (_, d) => {
625
- const title = d.swarm ? "ENXAME DE FRATURAS" : "FRATURA";
777
+ const title = d.swarm ? labels.fracture.titleSwarm : labels.fracture.title;
626
778
  return `
627
779
  <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>` : ""}
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>` : ""}
633
785
  `;
634
786
  },
635
787
  cementPad: (_, d) => `
636
- <span class="${customClasses.tooltip.title}">LAJE DE PROTE\xC7\xC3O</span>
788
+ <span class="${customClasses.tooltip.title}">${labels.cementPad.title}</span>
637
789
  <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>
790
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cementPad.thickness}</strong>
791
+ ${esc(formatLength(d.thickness, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
640
792
  <span class="${customClasses.tooltip.secondaryInfo}">
641
- <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))}
642
794
  </span>
643
795
  <span class="${customClasses.tooltip.secondaryInfo}">
644
- <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))}
645
797
  </span>
646
798
  `,
647
799
  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>` : ""}
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>` : ""}
652
804
  `
653
805
  };
654
806
  const noop = { show: () => {
@@ -661,12 +813,13 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig) => {
661
813
  tooltips[tipTextKey] = noop;
662
814
  return;
663
815
  }
664
- 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]);
665
817
  svg.call(
666
818
  tooltips[tipTextKey]
667
819
  );
668
820
  });
669
821
  svgEl?.setAttribute("data-tooltips-init", "true");
822
+ if (svgEl) _tooltipCache.set(svgEl, { signature, tooltips });
670
823
  return tooltips;
671
824
  };
672
825
 
@@ -682,21 +835,31 @@ var preloadFgdcTextures = () => {
682
835
  return _fgdcTexturesPromise;
683
836
  };
684
837
  var getLithologicalFillList = (data, opts) => {
685
- 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))];
686
841
  const texturesLoaded = Object.fromEntries(
687
- uniqueTextureCodes.filter((code) => _fgdcTextures[code]).map((code) => [code, _fgdcTextures[code]])
842
+ uniqueTextureCodes.filter((code) => {
843
+ const path = _fgdcTextures[code];
844
+ return path && path !== "-" && /^[Mm]/.test(path);
845
+ }).map((code) => [code, _fgdcTextures[code]])
688
846
  );
689
847
  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
- ])
848
+ data.map((d) => {
849
+ const key = textureKey(d);
850
+ const pathData = isFgdc(d) ? texturesLoaded[key] : void 0;
851
+ if (!pathData) return [`${key}.${d.from}`, d.color];
852
+ return [
853
+ `${key}.${d.from}`,
854
+ textures2.paths().d(() => pathData).size(opts.size).strokeWidth(opts.strokeWidth).stroke(opts.stroke).background(d.color)
855
+ ];
856
+ })
694
857
  );
695
858
  };
696
859
  var getLithologyFill = (geologyData, svg, opts) => {
697
860
  const lithologicalFill = getLithologicalFillList(geologyData, opts);
698
861
  return (d) => {
699
- const fill = lithologicalFill[`${d.fgdc_texture}.${d.from}`];
862
+ const fill = lithologicalFill[`${String(d.texture.code)}.${d.from}`];
700
863
  if (!fill.url) return fill;
701
864
  svg.call(fill);
702
865
  return fill.url();
@@ -759,7 +922,7 @@ function drawAnnotationLabels(ctx, data) {
759
922
  ctx.groups.lithologyLabelsGroup.selectAll("*").remove();
760
923
  if (active.has("lithology") && show.has("depth")) {
761
924
  data.lithology.filter((d) => d.to <= ctx.depthTo).forEach((d) => {
762
- const text = `${d.to}`;
925
+ const text = `${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`;
763
926
  const approxW = text.length * (st.fontSize * 0.52) + s.depthTipPadX * 2;
764
927
  const y = ctx.yScale(d.to) - s.depthTipHeight / 2;
765
928
  const tipG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.depth);
@@ -775,20 +938,20 @@ function drawAnnotationLabels(ctx, data) {
775
938
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
776
939
  originX: ctx.geoXRight,
777
940
  originY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
778
- 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)}`,
779
942
  description: d.description || ""
780
943
  });
781
944
  });
782
945
  }
783
946
  if (active.has("fractures")) {
784
947
  data.fractures.filter((d) => d.depth >= ctx.depthFrom && d.depth < ctx.depthTo).forEach((d) => {
785
- 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";
786
949
  items.push({
787
950
  sortDepth: d.depth,
788
951
  baseY: ctx.yScale(d.depth),
789
952
  originX: ctx.pocoCenterX,
790
953
  originY: ctx.yScale(d.depth),
791
- header: `${d.depth} m \xB7 ${typeLabel}`,
954
+ header: `${formatLength(d.depth, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
792
955
  description: d.description || ""
793
956
  });
794
957
  });
@@ -796,13 +959,13 @@ function drawAnnotationLabels(ctx, data) {
796
959
  if (active.has("caves")) {
797
960
  data.caves.filter((d) => d.from < ctx.depthTo).forEach((d) => {
798
961
  const midDepth = (d.from + d.to) / 2;
799
- 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";
800
963
  items.push({
801
964
  sortDepth: d.from,
802
965
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
803
966
  originX: ctx.geoXRight,
804
967
  originY: ctx.yScale(Math.max(midDepth, ctx.depthFrom)),
805
- 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}`,
806
969
  description: d.description || ""
807
970
  });
808
971
  });
@@ -848,6 +1011,19 @@ function drawAnnotationLabels(ctx, data) {
848
1011
  }
849
1012
  }
850
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
+
851
1027
  // src/renderers/caves.renderer.ts
852
1028
  function drawCaves(ctx, data) {
853
1029
  if (!data.length) return;
@@ -864,9 +1040,14 @@ function drawCaves(ctx, data) {
864
1040
  classes,
865
1041
  tooltips
866
1042
  } = ctx;
867
- cavesGroup.selectAll(`g.${classes.caves.item}`).remove();
868
1043
  const rc = renderConfig.caves;
869
- 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();
870
1051
  const seedTop = cave.from * 100 + cave.to;
871
1052
  const seedBot = cave.to * 100 + cave.from + 999;
872
1053
  const rngTop = makeSeededPrng(seedTop);
@@ -893,12 +1074,12 @@ function drawCaves(ctx, data) {
893
1074
  const botReversed = botCut ? `L ${geoXLeft.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath([...botPts].reverse(), { omitMoveTo: true });
894
1075
  const closedPath = `${topPath} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}${botReversed} L ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} Z`;
895
1076
  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
1077
  const caveStroke = cave.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
898
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");
899
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");
900
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");
901
1081
  });
1082
+ merged.transition(ctx.transition).style("opacity", 1);
902
1083
  }
903
1084
 
904
1085
  // src/renderers/construction-labels.renderer.ts
@@ -909,8 +1090,8 @@ function drawConstructionLabels(ctx, data) {
909
1090
  ctx.groups.constructionLabelsGroup.selectAll("*").remove();
910
1091
  const clc = ctx.renderConfig.constructionLabels;
911
1092
  const clct = ctx.theme.constructionLabels;
912
- const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter);
913
- 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);
914
1095
  const maxXValues = getProfileDiamValues(ctx.constructionData);
915
1096
  const xScale = d32.scaleLinear().domain([0, d32.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
916
1097
  const occupied = [];
@@ -972,7 +1153,7 @@ function drawConstructionLabels(ctx, data) {
972
1153
  }).forEach(
973
1154
  (d) => placeLabel(
974
1155
  d,
975
- `${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}`
976
1157
  )
977
1158
  );
978
1159
  }
@@ -989,9 +1170,10 @@ function drawConstructive(ctx, data) {
989
1170
  const maxXValueConstruction = d33.max(maxXValues) || 0;
990
1171
  const xScale = d33.scaleLinear().domain([0, maxXValueConstruction]).range([0, ctx.POCO_WIDTH]);
991
1172
  const rcc = ctx.renderConfig.construction;
1173
+ const yPos = getYPos;
992
1174
  ctx.groups.constructionGroup.selectAll(`.${ctx.classes.cementPad.item}`).remove();
993
1175
  if (data.cement_pad && data.cement_pad.thickness && ctx.depthFrom === 0) {
994
- 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");
995
1177
  cementPad.exit().remove();
996
1178
  const newCementPad = cementPad.enter().append("rect").attr("class", ctx.classes.cementPad.item).attr(
997
1179
  "x",
@@ -1006,15 +1188,15 @@ function drawConstructive(ctx, data) {
1006
1188
  }).attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth);
1007
1189
  newCementPad.on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1008
1190
  }
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);
1191
+ const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, makeIntervalKey("bore_hole"));
1192
+ hole.exit().transition(ctx.transition).attr("height", 0).remove();
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);
1012
1194
  const mergedHole = mergeEnter(newHole, hole).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1013
1195
  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);
1196
+ const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case, makeIntervalKey("surface_case"));
1015
1197
  surfaceCaseGs.exit().transition(ctx.transition).style("opacity", 0).remove();
1016
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);
1017
- newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none");
1199
+ newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none").attr("y", yPos).attr("height", 0);
1018
1200
  newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1019
1201
  newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1020
1202
  const mergedSC = mergeEnter(newSC, surfaceCaseGs);
@@ -1039,33 +1221,54 @@ function drawConstructive(ctx, data) {
1039
1221
  "y2",
1040
1222
  (d) => getYPos(d) + getHeight(d)
1041
1223
  );
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);
1224
+ const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, makeIntervalKey("hole_fill"));
1225
+ holeFill.exit().transition(ctx.transition).attr("height", 0).remove();
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);
1045
1227
  const mergedHoleFill = mergeEnter(newHoleFill, holeFill).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1046
1228
  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);
1229
+ const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case, makeIntervalKey("well_case"));
1230
+ wellCase.exit().transition(ctx.transition).attr("height", 0).remove();
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);
1050
1232
  const mergedWellCase = mergeEnter(newWellCase, wellCase).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1051
1233
  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);
1234
+ const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen, makeIntervalKey("well_screen"));
1235
+ wellScreen.exit().transition(ctx.transition).attr("height", 0).remove();
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);
1055
1237
  const mergedWellScreen = mergeEnter(newWellScreen, wellScreen).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1056
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
+ );
1057
1259
  const conflictAreas = [];
1058
1260
  conflictAreas.push(...getConflictAreas(data.well_case, data.well_screen));
1059
1261
  conflictAreas.push(...getConflictAreas(data.well_screen, data.well_case));
1060
1262
  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);
1263
+ const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts, makeIntervalKey("conflict"));
1264
+ conflict.exit().transition(ctx.transition).attr("height", 0).remove();
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);
1064
1266
  const mergedConflict = mergeEnter(newConflict, conflict).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1065
1267
  withTransition(mergedConflict, ctx.transition).attr("y", ctx.yScale(0)).attr("height", getHeight);
1066
1268
  }
1067
1269
 
1068
1270
  // src/renderers/fractures.renderer.ts
1271
+ import * as d34 from "d3";
1069
1272
  function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1070
1273
  const pts = [];
1071
1274
  for (let i = 0; i < steps; i++) {
@@ -1088,19 +1291,23 @@ function drawFractures(ctx, data) {
1088
1291
  classes,
1089
1292
  tooltips
1090
1293
  } = ctx;
1091
- fracturesGroup.selectAll(`g.${classes.fractures.item}`).remove();
1092
1294
  const rf = renderConfig.fractures;
1093
1295
  const halfWidth = POCO_WIDTH * rf.widthMultiplier / 2;
1094
1296
  const xa = pocoCenterX - halfWidth;
1095
1297
  const w = halfWidth * 2;
1096
1298
  const normToAbsX = (normX) => xa + normX * w;
1097
- 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();
1098
1310
  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
1311
  const hitBuffer = fracture.swarm ? rf.hitBuffer.swarm : rf.hitBuffer.single;
1105
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");
1106
1313
  const fractureStroke = fracture.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
@@ -1173,10 +1380,11 @@ function drawFractures(ctx, data) {
1173
1380
  }
1174
1381
  }
1175
1382
  });
1383
+ merged.transition(ctx.transition).style("opacity", 1);
1176
1384
  }
1177
1385
 
1178
1386
  // src/renderers/highlights.renderer.ts
1179
- import * as d34 from "d3";
1387
+ import * as d35 from "d3";
1180
1388
  import { getProfileDiamValues as getProfileDiamValues3 } from "@welldot/utils";
1181
1389
  function drawHighlightItem(ctx, group, x, y, w, h, label) {
1182
1390
  if (h <= 0 || w <= 0) return;
@@ -1237,7 +1445,7 @@ function drawHighlights(ctx, hl) {
1237
1445
  );
1238
1446
  });
1239
1447
  const maxXVals = getProfileDiamValues3(ctx.constructionData);
1240
- 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]);
1241
1449
  const constructionConfig = ctx.renderConfig.construction;
1242
1450
  if (hl.cement_pad && ctx.depthFrom === 0 && ctx.constructionData.cement_pad?.thickness) {
1243
1451
  const cp = ctx.constructionData.cement_pad;
@@ -1317,18 +1525,29 @@ function drawHighlights(ctx, hl) {
1317
1525
  stdX,
1318
1526
  stdW
1319
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
+ );
1320
1538
  }
1321
1539
 
1322
1540
  // src/renderers/legend.renderer.ts
1323
- import * as d3module2 from "d3";
1541
+ import * as d3module from "d3";
1324
1542
  import { defu } from "defu";
1325
- var d35 = { ...d3module2 };
1543
+ var d36 = { ...d3module };
1326
1544
  function drawWellLegend(selector, profile, options = {}) {
1327
1545
  const fractures = profile.fractures ?? [];
1328
1546
  const caves = profile.caves ?? [];
1329
1547
  const boreHoles = profile.bore_hole ?? [];
1330
1548
  const wellCases = profile.well_case ?? [];
1331
1549
  const wellScreens = profile.well_screen ?? [];
1550
+ const reductions = profile.reduction ?? [];
1332
1551
  const holeFills = profile.hole_fill ?? [];
1333
1552
  const hasSimple = fractures.some((f) => !f.swarm && !f.water_intake);
1334
1553
  const hasSwarm = fractures.some((f) => f.swarm);
@@ -1341,6 +1560,7 @@ function drawWellLegend(selector, profile, options = {}) {
1341
1560
  const hasHoleFillSeal = holeFills.some((h) => h.type === "seal");
1342
1561
  const hasWellCase = wellCases.length > 0;
1343
1562
  const hasWellScreen = wellScreens.length > 0;
1563
+ const hasReduction = reductions.length > 0;
1344
1564
  const hasCementPad = !!profile.cement_pad?.thickness;
1345
1565
  const hasConflict = mergeConflicts(
1346
1566
  [
@@ -1427,13 +1647,19 @@ function drawWellLegend(selector, profile, options = {}) {
1427
1647
  label: cfg.labels.wellScreen,
1428
1648
  subKind: "wellScreen"
1429
1649
  });
1650
+ if (hasReduction)
1651
+ items.push({
1652
+ kind: "construction",
1653
+ label: cfg.labels.reduction,
1654
+ subKind: "reduction"
1655
+ });
1430
1656
  if (hasConflict)
1431
1657
  items.push({
1432
1658
  kind: "construction",
1433
1659
  label: cfg.labels.conflict,
1434
1660
  subKind: "conflict"
1435
1661
  });
1436
- const svgEl = d35.select(selector);
1662
+ const svgEl = d36.select(selector);
1437
1663
  svgEl.selectAll("*").remove();
1438
1664
  if (items.length === 0) {
1439
1665
  svgEl.attr("width", 0).attr("height", 0);
@@ -1485,6 +1711,15 @@ function drawWellLegend(selector, profile, options = {}) {
1485
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");
1486
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);
1487
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);
1488
1723
  } else {
1489
1724
  const symMap = {
1490
1725
  boreHole: {
@@ -1542,11 +1777,11 @@ function drawLithology(ctx, data) {
1542
1777
  from: depthFrom,
1543
1778
  to: depthTo
1544
1779
  });
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));
1780
+ const rects = lithologyGroup.selectAll(`.${classes.lithology.rect}`).data(data, makeIntervalKey("lithology"));
1781
+ rects.exit().transition(transition).attr("height", 0).remove();
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));
1550
1785
  }
1551
1786
 
1552
1787
  // src/renderers/unit-labels.renderer.ts
@@ -1633,20 +1868,26 @@ function buildSvgStyleBlock(theme) {
1633
1868
  `.wp-annotation-label{font-size:${s(labels.fontSize)}px}`,
1634
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)}}`,
1635
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)}}`,
1636
- `.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)}}`
1637
1876
  ].join("");
1638
1877
  }
1639
1878
 
1640
1879
  // src/Renderer.ts
1641
- var d36 = Object.assign(d3module3, { tip: d3tip2 });
1880
+ var d37 = { ...d3module2, tip: d3tip2 };
1642
1881
  var WellRenderer = class {
1643
1882
  /**
1644
1883
  * @param svgs - One entry per SVG panel; multi-entry for split-panel layouts.
1645
1884
  * @param options.classNames - Override default BEM CSS class names.
1646
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.
1647
1887
  * @param options.renderConfig - Rendering behaviour (zoom, pan, animation, layout). Defaults to `INTERACTIVE_RENDER_CONFIG`.
1648
1888
  * @param options.theme - Visual theme overrides. Merged on top of `DEFAULT_WELL_THEME`.
1649
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.
1650
1891
  */
1651
1892
  constructor(svgs, options = {}) {
1652
1893
  this.svgInstances = [];
@@ -1659,6 +1900,7 @@ var WellRenderer = class {
1659
1900
  length: "m",
1660
1901
  diameter: "mm"
1661
1902
  };
1903
+ this.locale = "pt";
1662
1904
  if (svgs.length === 0) {
1663
1905
  console.warn(
1664
1906
  "[WellRenderer] No SVG instances provided. The renderer will be a no-op."
@@ -1667,6 +1909,7 @@ var WellRenderer = class {
1667
1909
  }
1668
1910
  this.svgInstances = svgs;
1669
1911
  if (options.onError) this.onError = options.onError;
1912
+ if (options.onZoom) this.onZoom = options.onZoom;
1670
1913
  if (options.classNames) {
1671
1914
  this.classes = defu2(
1672
1915
  options.classNames,
@@ -1676,6 +1919,9 @@ var WellRenderer = class {
1676
1919
  if (options.units) {
1677
1920
  this.units = { ...this.units, ...options.units };
1678
1921
  }
1922
+ if (options.locale) {
1923
+ this.locale = options.locale;
1924
+ }
1679
1925
  if (options.renderConfig) {
1680
1926
  this.renderConfig = defu2(
1681
1927
  options.renderConfig,
@@ -1691,7 +1937,7 @@ var WellRenderer = class {
1691
1937
  const { selector, height, width, margins } = inst;
1692
1938
  const svgWidth = width + margins.left + margins.right;
1693
1939
  const svgHeight = height + margins.top + margins.bottom;
1694
- const svg = d36.select(selector);
1940
+ const svg = d37.select(selector);
1695
1941
  svg.selectAll("*").remove();
1696
1942
  svg.attr("height", svgHeight).attr("width", svgWidth);
1697
1943
  const defs = svg.append("defs");
@@ -1711,14 +1957,15 @@ var WellRenderer = class {
1711
1957
  );
1712
1958
  const fracturesGroup = poco.append("g").attr("class", this.classes.fractures.group).attr("transform", `translate(${margins.left}, ${margins.top})`).attr("clip-path", `url(#${clipId})`);
1713
1959
  fracturesGroup.append("g").attr("class", this.classes.highlights.fracturesGroup);
1714
- construction.append("g").attr("class", this.classes.constructionLabels.group);
1715
1960
  construction.append("g").attr("class", this.classes.cementPad.group);
1716
1961
  construction.append("g").attr("class", this.classes.boreHole.group);
1717
1962
  construction.append("g").attr("class", this.classes.surfaceCase.group);
1718
1963
  construction.append("g").attr("class", this.classes.holeFill.group);
1719
1964
  construction.append("g").attr("class", this.classes.wellCase.group);
1720
1965
  construction.append("g").attr("class", this.classes.wellScreen.group);
1966
+ construction.append("g").attr("class", this.classes.reduction.group);
1721
1967
  construction.append("g").attr("class", this.classes.conflict.group);
1968
+ construction.append("g").attr("class", this.classes.constructionLabels.group);
1722
1969
  construction.append("g").attr("class", this.classes.highlights.constructionGroup);
1723
1970
  Object.values(this.textures).forEach((texture) => svg.call(texture));
1724
1971
  return { svg, height, width, margins, clipId, clipRectId };
@@ -1732,6 +1979,36 @@ var WellRenderer = class {
1732
1979
  textures: this.renderConfig.textures
1733
1980
  });
1734
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
+ }
1735
2012
  /**
1736
2013
  * Initialises the SVG DOM structure and preloads FGDC textures.
1737
2014
  * Must be called once before the first `draw()`.
@@ -1746,14 +2023,23 @@ var WellRenderer = class {
1746
2023
  * Renders the well profile into all configured SVG panels.
1747
2024
  * @param profile - The well data to render.
1748
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.
1749
2027
  * @param options.highlights - Highlight overlays to display on top of the profile.
1750
2028
  */
1751
2029
  draw(profile, options = {}) {
1752
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
+ }
1753
2036
  this.units = { ...this.units, ...options.units };
2037
+ if (options.locale) {
2038
+ this.locale = options.locale;
2039
+ }
1754
2040
  const highlights = options.highlights ?? {};
1755
2041
  const maxValues = getProfileLastItemsDepths(profile);
1756
- const maxYValues = d36.max(maxValues) || 0;
2042
+ const maxYValues = d37.max(maxValues) || 0;
1757
2043
  if (this.instanceStates.length === 1) {
1758
2044
  this.drawLogToInstance(
1759
2045
  this.instanceStates[0],
@@ -1765,7 +2051,7 @@ var WellRenderer = class {
1765
2051
  return;
1766
2052
  }
1767
2053
  const totalHeight = this.instanceStates.reduce((s, st) => s + st.height, 0) || 1;
1768
- const yScaleGlobal = d36.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
2054
+ const yScaleGlobal = d37.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
1769
2055
  let currentDepth = 0;
1770
2056
  for (const state of this.instanceStates) {
1771
2057
  const maxSvgDepth = yScaleGlobal.invert(
@@ -1797,6 +2083,7 @@ var WellRenderer = class {
1797
2083
  const holeFillGroup = svg.select(`.${this.classes.holeFill.group}`);
1798
2084
  const wellCaseGroup = svg.select(`.${this.classes.wellCase.group}`);
1799
2085
  const wellScreenGroup = svg.select(`.${this.classes.wellScreen.group}`);
2086
+ const reductionGroup = svg.select(`.${this.classes.reduction.group}`);
1800
2087
  const conflictGroup = svg.select(`.${this.classes.conflict.group}`);
1801
2088
  const constructionLabelsGroup = svg.select(
1802
2089
  `.${this.classes.constructionLabels.group}`
@@ -1817,12 +2104,14 @@ var WellRenderer = class {
1817
2104
  const POCO_WIDTH = svgWidth * pocoWidthRatio;
1818
2105
  const POCO_CENTER = svgWidth * pocoCenterRatio;
1819
2106
  const { duration, ease } = this.renderConfig.animation;
1820
- const transition = d36.transition().duration(duration).ease(ease);
2107
+ const transition = d37.transition().duration(duration).ease(ease);
1821
2108
  const tooltips = populateTooltips(
1822
2109
  svg,
1823
2110
  this.classes,
1824
2111
  this.units,
1825
- this.renderConfig.tooltips
2112
+ this.renderConfig.tooltips,
2113
+ this.renderConfig.tooltipLabels,
2114
+ this.locale
1826
2115
  );
1827
2116
  const { xLeft: geoXLeft, xRightInset: geoXRightInset } = this.renderConfig.geologic;
1828
2117
  const geoXRight = svgWidth - geoXRightInset;
@@ -1843,11 +2132,14 @@ var WellRenderer = class {
1843
2132
  fractures: profile.fractures
1844
2133
  };
1845
2134
  const contentHeight = svgHeight - state.margins.top - state.margins.bottom;
1846
- const yScaleLocal = d36.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
2135
+ const yScaleLocal = d37.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
1847
2136
  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)}`
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)}`
1851
2143
  );
1852
2144
  const gY = svg.select(`.${this.classes.yAxis}`).call(yAxis);
1853
2145
  gY.select(".domain").attr("stroke", this.theme.labels.color).attr("fill", "none");
@@ -1884,6 +2176,7 @@ var WellRenderer = class {
1884
2176
  holeFillGroup,
1885
2177
  wellCaseGroup,
1886
2178
  wellScreenGroup,
2179
+ reductionGroup,
1887
2180
  conflictGroup,
1888
2181
  highlightsGeologicGroup,
1889
2182
  highlightsConstructionGroup,
@@ -1909,6 +2202,7 @@ var WellRenderer = class {
1909
2202
  classes: this.classes,
1910
2203
  textures: this.textures,
1911
2204
  units: this.units,
2205
+ locale: this.locale,
1912
2206
  constructionData,
1913
2207
  groups
1914
2208
  };
@@ -1939,12 +2233,24 @@ var WellRenderer = class {
1939
2233
  surfaceCaseGroup.selectAll(`g.${this.classes.surfaceCase.rect}`).each((d, i, nodes) => {
1940
2234
  if (!d) return;
1941
2235
  const sc = d;
1942
- const g = d36.select(nodes[i]);
2236
+ const g = d37.select(nodes[i]);
1943
2237
  const y = transform.applyY(spanY(sc));
1944
2238
  const h = transform.k * spanH(sc);
1945
2239
  g.select(".surface-case-fill").attr("y", y).attr("height", h);
1946
2240
  g.selectAll(".surface-case-side").attr("y1", y).attr("y2", y + h);
1947
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
+ });
1948
2254
  fracturesGroup.selectAll(`g.${this.classes.fractures.item}`).attr("transform", (d) => {
1949
2255
  if (!d) return null;
1950
2256
  const f = d;
@@ -1966,6 +2272,7 @@ var WellRenderer = class {
1966
2272
  well_screen: constructionData.well_screen.filter(inDepth)
1967
2273
  });
1968
2274
  drawHighlights(zoomedCtx, highlights);
2275
+ this.onZoom?.(transform.k);
1969
2276
  };
1970
2277
  const drawProfile = () => {
1971
2278
  drawLithology(ctx, profile.lithology?.filter(inDepth) ?? []);
@@ -2005,17 +2312,23 @@ var WellRenderer = class {
2005
2312
  }
2006
2313
  const { zoom: zoomEnabled, pan: panEnabled } = this.renderConfig;
2007
2314
  if (zoomEnabled || panEnabled) {
2008
- const zoomNode = d36.zoom();
2315
+ const zoomNode = d37.zoom();
2009
2316
  if (zoomEnabled || panEnabled) zoomNode.on("zoom", zooming);
2010
- 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
+ );
2011
2323
  if (!panEnabled)
2012
2324
  zoomNode.filter((e) => e.type === "wheel" || e.type === "dblclick");
2013
2325
  asSvgElement(svg).call(zoomNode);
2326
+ state.zoomNode = zoomNode;
2014
2327
  const initialZoom = this.renderConfig.zoomLevel ?? 1;
2015
2328
  if (initialZoom !== 1) {
2016
2329
  asSvgElement(svg).call(
2017
2330
  zoomNode.transform,
2018
- d36.zoomIdentity.scale(initialZoom)
2331
+ d37.zoomIdentity.scale(initialZoom)
2019
2332
  );
2020
2333
  }
2021
2334
  }
@@ -2024,13 +2337,16 @@ var WellRenderer = class {
2024
2337
  export {
2025
2338
  DEFAULT_WELL_THEME,
2026
2339
  INTERACTIVE_RENDER_CONFIG,
2340
+ RENDER_LABELS,
2027
2341
  STATIC_RENDER_CONFIG,
2028
2342
  WellRenderer,
2343
+ applyRenderLocale,
2029
2344
  drawWellLegend,
2030
2345
  formatDiameter,
2031
2346
  formatLength,
2032
2347
  getDiameterUnit,
2033
2348
  getLengthUnit,
2034
- importFgdcTextures
2349
+ importFgdcTextures,
2350
+ resolveRenderLabel
2035
2351
  };
2036
2352
  //# sourceMappingURL=index.js.map