@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.cjs CHANGED
@@ -325,19 +325,163 @@ var index_exports = {};
325
325
  __export(index_exports, {
326
326
  DEFAULT_WELL_THEME: () => DEFAULT_WELL_THEME,
327
327
  INTERACTIVE_RENDER_CONFIG: () => INTERACTIVE_RENDER_CONFIG,
328
+ RENDER_LABELS: () => RENDER_LABELS,
328
329
  STATIC_RENDER_CONFIG: () => STATIC_RENDER_CONFIG,
329
330
  WellRenderer: () => WellRenderer,
331
+ applyRenderLocale: () => applyRenderLocale,
330
332
  drawWellLegend: () => drawWellLegend,
331
333
  formatDiameter: () => formatDiameter,
332
334
  formatLength: () => formatLength,
333
335
  getDiameterUnit: () => getDiameterUnit,
334
336
  getLengthUnit: () => getLengthUnit,
335
- importFgdcTextures: () => importFgdcTextures
337
+ importFgdcTextures: () => importFgdcTextures,
338
+ resolveRenderLabel: () => resolveRenderLabel
336
339
  });
337
340
  module.exports = __toCommonJS(index_exports);
338
341
 
339
342
  // src/configs/render.configs.ts
340
343
  var import_d3 = require("d3");
344
+
345
+ // src/utils/format.utils.ts
346
+ var import_utils = require("@welldot/utils");
347
+ var MM_TO_INCHES = 0.0393701;
348
+ var M_TO_FT = 3.28084;
349
+ function formatLength(m, units, locale) {
350
+ return units === "ft" ? (0, import_utils.formatNumber)(m * M_TO_FT, { maximumFractionDigits: 1, locale }) : (0, import_utils.formatNumber)(m, { maximumFractionDigits: 2, locale });
351
+ }
352
+ function formatDiameter(mm, units, locale) {
353
+ return units === "inches" ? (0, import_utils.formatNumber)(mm * MM_TO_INCHES, { maximumFractionDigits: 2, locale }) : (0, import_utils.formatNumber)(mm, { maximumFractionDigits: 2, locale });
354
+ }
355
+ function getLengthUnit(units) {
356
+ return units === "ft" ? "ft" : "m";
357
+ }
358
+ function getDiameterUnit(units, locale) {
359
+ if (units !== "inches") return "mm";
360
+ return locale === "en" ? "in." : '"';
361
+ }
362
+ function resolveRenderLabel(value, locale) {
363
+ if (typeof value === "string") return value;
364
+ return locale === "en" && value.en ? value.en : value.pt;
365
+ }
366
+
367
+ // src/configs/render.configs.ts
368
+ var RENDER_LABELS = {
369
+ constructionLabels: {
370
+ wellCasePrefix: { pt: "Revest.", en: "Casing" },
371
+ wellScreenPrefix: { pt: "Filtro", en: "Screen" },
372
+ wellScreenSlotPrefix: { pt: "Ranhura:", en: "Slot:" }
373
+ },
374
+ legend: {
375
+ title: { pt: "LEGENDA", en: "LEGEND" },
376
+ labels: {
377
+ fractureSingle: { pt: "Fratura simples", en: "Single fracture" },
378
+ fractureSwarm: { pt: "Enxame de fraturas", en: "Fracture swarm" },
379
+ fractureWater: { pt: "Entrada d'\xE1gua", en: "Water intake" },
380
+ caveDry: { pt: "Caverna seca", en: "Dry cave" },
381
+ caveWet: { pt: "Caverna c/ \xE1gua", en: "Wet cave" },
382
+ boreHole: { pt: "Perfura\xE7\xE3o", en: "Borehole" },
383
+ surfaceCase: { pt: "Tubo guia", en: "Surface casing" },
384
+ holeFillGravel: { pt: "Pr\xE9-filtro", en: "Gravel pack" },
385
+ holeFillSeal: { pt: "Veda\xE7\xE3o", en: "Seal" },
386
+ wellCase: { pt: "Revestimento", en: "Casing" },
387
+ wellScreen: { pt: "Filtro", en: "Screen" },
388
+ reduction: { pt: "Redu\xE7\xE3o", en: "Reduction" },
389
+ cementPad: { pt: "Laje de cimento", en: "Cement pad" },
390
+ conflict: { pt: "Conflito", en: "Conflict" }
391
+ }
392
+ },
393
+ tooltipLabels: {
394
+ common: {
395
+ from: { pt: "De", en: "From" },
396
+ to: { pt: "at\xE9", en: "to" },
397
+ description: { pt: "Descri\xE7\xE3o:", en: "Description:" },
398
+ diameter: { pt: "Di\xE2metro:", en: "Diameter:" },
399
+ type: { pt: "Tipo:", en: "Type:" }
400
+ },
401
+ geology: {
402
+ title: { pt: "Litologia", en: "Lithology" },
403
+ geologicUnit: { pt: "Unidade geol\xF3gica:", en: "Geologic unit:" },
404
+ aquiferUnit: { pt: "Unidade aqu\xEDfera:", en: "Aquifer unit:" }
405
+ },
406
+ hole: { title: { pt: "FURO", en: "BOREHOLE" } },
407
+ surfaceCase: { title: { pt: "TUBO DE BOCA", en: "SURFACE CASING" } },
408
+ holeFill: { title: { pt: "ESP. ANULAR", en: "ANNULAR SPACE" } },
409
+ wellCase: { title: { pt: "REVESTIMENTO", en: "CASING" } },
410
+ wellScreen: {
411
+ title: { pt: "FILTROS", en: "SCREENS" },
412
+ slot: { pt: "Ranhura:", en: "Slot:" }
413
+ },
414
+ reduction: { title: { pt: "REDU\xC7\xC3O", en: "REDUCTION" } },
415
+ conflict: { title: { pt: "CONFLITO", en: "CONFLICT" } },
416
+ fracture: {
417
+ title: { pt: "FRATURA", en: "FRACTURE" },
418
+ titleSwarm: { pt: "ENXAME DE FRATURAS", en: "FRACTURE SWARM" },
419
+ depth: { pt: "Profundidade:", en: "Depth:" },
420
+ waterIntake: { pt: "Entrada d'\xE1gua:", en: "Water intake:" },
421
+ dip: { pt: "Mergulho:", en: "Dip:" },
422
+ azimuth: { pt: "Azimute:", en: "Azimuth:" }
423
+ },
424
+ cementPad: {
425
+ title: { pt: "LAJE DE PROTE\xC7\xC3O", en: "PROTECTION SLAB" },
426
+ thickness: { pt: "Espessura:", en: "Thickness:" },
427
+ width: { pt: "Largura:", en: "Width:" },
428
+ length: { pt: "Comprimento:", en: "Length:" }
429
+ },
430
+ cave: {
431
+ title: { pt: "CAVERNA", en: "CAVE" },
432
+ waterIntake: { pt: "Entrada d'\xE1gua", en: "Water intake" }
433
+ }
434
+ },
435
+ typeLabels: {
436
+ fracture: { pt: "fratura", en: "fracture" },
437
+ fractureWater: { pt: "fratura aberta", en: "open fracture" },
438
+ cave: { pt: "caverna", en: "cave" },
439
+ caveWater: { pt: "caverna \xFAmida", en: "wet cave" }
440
+ }
441
+ };
442
+ function resolveGroup(group, locale) {
443
+ const result = {};
444
+ for (const key in group) {
445
+ result[key] = resolveRenderLabel(group[key], locale);
446
+ }
447
+ return result;
448
+ }
449
+ function resolveTooltipLabels(locale) {
450
+ const t = RENDER_LABELS.tooltipLabels;
451
+ return {
452
+ common: resolveGroup(t.common, locale),
453
+ geology: resolveGroup(t.geology, locale),
454
+ hole: resolveGroup(t.hole, locale),
455
+ surfaceCase: resolveGroup(t.surfaceCase, locale),
456
+ holeFill: resolveGroup(t.holeFill, locale),
457
+ wellCase: resolveGroup(t.wellCase, locale),
458
+ wellScreen: resolveGroup(t.wellScreen, locale),
459
+ reduction: resolveGroup(t.reduction, locale),
460
+ conflict: resolveGroup(t.conflict, locale),
461
+ fracture: resolveGroup(t.fracture, locale),
462
+ cementPad: resolveGroup(t.cementPad, locale),
463
+ cave: resolveGroup(t.cave, locale)
464
+ };
465
+ }
466
+ function applyRenderLocale(config, locale) {
467
+ return {
468
+ ...config,
469
+ constructionLabels: {
470
+ ...config.constructionLabels,
471
+ labels: resolveGroup(RENDER_LABELS.constructionLabels, locale)
472
+ },
473
+ legend: {
474
+ ...config.legend,
475
+ title: resolveRenderLabel(RENDER_LABELS.legend.title, locale),
476
+ labels: resolveGroup(RENDER_LABELS.legend.labels, locale)
477
+ },
478
+ tooltipLabels: resolveTooltipLabels(locale),
479
+ labels: {
480
+ ...config.labels,
481
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, locale)
482
+ }
483
+ };
484
+ }
341
485
  var DEFAULT_WELL_THEME = {
342
486
  lithology: { stroke: "#101010", strokeWidth: 1 },
343
487
  lithologyTexture: { size: 150, strokeWidth: 0.8, stroke: "#303030" },
@@ -372,6 +516,7 @@ var DEFAULT_WELL_THEME = {
372
516
  holeFill: { stroke: "#303030", strokeWidth: 2 },
373
517
  wellCase: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
374
518
  wellScreen: { stroke: "#303030", strokeWidth: 2 },
519
+ reduction: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
375
520
  conflict: { stroke: "#e52117", strokeWidth: 4 },
376
521
  unitLabels: {
377
522
  geologicFill: "#f0f0f0",
@@ -435,6 +580,8 @@ var STATIC_RENDER_CONFIG = {
435
580
  zoom: true,
436
581
  pan: true,
437
582
  zoomLevel: 1,
583
+ maxZoomScale: 150,
584
+ minZoomScale: 0,
438
585
  animation: { duration: 600, ease: import_d3.easeCubic },
439
586
  geologic: { xLeft: 20, xRightInset: 300 },
440
587
  layout: {
@@ -465,9 +612,10 @@ var STATIC_RENDER_CONFIG = {
465
612
  cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
466
613
  surfaceCase: { diameterPaddingRatio: 0.1 }
467
614
  },
615
+ tooltipLabels: resolveTooltipLabels("pt"),
468
616
  labels: {
469
617
  active: true,
470
- typeLabels: { fracture: "fratura", cave: "caverna" },
618
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, "pt"),
471
619
  depthTipHeight: 11,
472
620
  depthTipPadX: 2,
473
621
  descriptionXOffset: 20,
@@ -532,6 +680,7 @@ var STATIC_RENDER_CONFIG = {
532
680
  holeFillSeal: "Veda\xE7\xE3o",
533
681
  wellCase: "Revestimento",
534
682
  wellScreen: "Filtro",
683
+ reduction: "Redu\xE7\xE3o",
535
684
  cementPad: "Laje de cimento",
536
685
  conflict: "Conflito"
537
686
  }
@@ -542,6 +691,8 @@ var INTERACTIVE_RENDER_CONFIG = {
542
691
  zoom: true,
543
692
  pan: true,
544
693
  zoomLevel: 1,
694
+ maxZoomScale: 150,
695
+ minZoomScale: 0,
545
696
  animation: { duration: 600, ease: import_d3.easeCubic },
546
697
  geologic: { xLeft: 6, xRightInset: 56 },
547
698
  layout: {
@@ -572,9 +723,10 @@ var INTERACTIVE_RENDER_CONFIG = {
572
723
  cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
573
724
  surfaceCase: { diameterPaddingRatio: 0.1 }
574
725
  },
726
+ tooltipLabels: resolveTooltipLabels("pt"),
575
727
  labels: {
576
728
  active: false,
577
- typeLabels: { fracture: "fratura", cave: "caverna" },
729
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, "pt"),
578
730
  depthTipHeight: 11,
579
731
  depthTipPadX: 2,
580
732
  descriptionXOffset: 78,
@@ -636,6 +788,7 @@ var INTERACTIVE_RENDER_CONFIG = {
636
788
  holeFillSeal: "Veda\xE7\xE3o",
637
789
  wellCase: "Revestimento",
638
790
  wellScreen: "Filtro",
791
+ reduction: "Redu\xE7\xE3o",
639
792
  cementPad: "Laje de cimento",
640
793
  conflict: "Conflito"
641
794
  }
@@ -650,7 +803,7 @@ async function importFgdcTextures() {
650
803
  }
651
804
 
652
805
  // src/Renderer.ts
653
- var d3module3 = __toESM(require("d3"), 1);
806
+ var d3module2 = __toESM(require("d3"), 1);
654
807
  var import_d3_tip2 = __toESM(require("d3-tip"), 1);
655
808
  var import_defu2 = require("defu");
656
809
 
@@ -716,23 +869,7 @@ function createWellTextures(cfg) {
716
869
 
717
870
  // src/Renderer.ts
718
871
  var import_core = require("@welldot/core");
719
- var import_utils4 = require("@welldot/utils");
720
-
721
- // src/utils/format.utils.ts
722
- var MM_TO_INCHES = 0.0393701;
723
- var M_TO_FT = 3.28084;
724
- function formatLength(m, units) {
725
- return units === "ft" ? (m * M_TO_FT).toFixed(1) : String(m);
726
- }
727
- function formatDiameter(mm, units) {
728
- return units === "inches" ? (mm * MM_TO_INCHES).toFixed(2) : String(mm);
729
- }
730
- function getLengthUnit(units) {
731
- return units === "ft" ? "ft" : "m";
732
- }
733
- function getDiameterUnit(units) {
734
- return units === "inches" ? '"' : "mm";
735
- }
872
+ var import_utils6 = require("@welldot/utils");
736
873
 
737
874
  // src/configs/render.classnames.ts
738
875
  var DEFAULT_COMPONENTS_CLASS_NAMES = {
@@ -790,6 +927,10 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
790
927
  group: "well-screen",
791
928
  rect: "well-screen-rect"
792
929
  },
930
+ reduction: {
931
+ group: "reduction",
932
+ item: "reduction-poly"
933
+ },
793
934
  conflict: {
794
935
  group: "conflict",
795
936
  rect: "conflict-rect"
@@ -832,6 +973,9 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
832
973
  var import_textures2 = __toESM(require("textures"), 1);
833
974
 
834
975
  // src/utils/d3.utils.ts
976
+ function mergeEnter(enter, update) {
977
+ return enter.merge(update);
978
+ }
835
979
  function withTransition(selection, transition) {
836
980
  return selection.transition(transition);
837
981
  }
@@ -849,18 +993,6 @@ var getYAxisFunctions = (yScale, clamp) => {
849
993
  getYPos: ({ from }) => yScale(Math.max(from, clampFrom))
850
994
  };
851
995
  };
852
- function hashStr(s) {
853
- let h = 5381;
854
- for (let i = 0; i < s.length; i++) {
855
- h = Math.imul(h, 33) ^ s.charCodeAt(i);
856
- }
857
- return (h >>> 0).toString(36);
858
- }
859
- function stableLayerKey(d) {
860
- const { from: _f, to: _t, ...rest } = d;
861
- const sortedKeys = Object.keys(rest).sort();
862
- return hashStr(JSON.stringify(rest, sortedKeys));
863
- }
864
996
  function filterByDepth(depthFrom, depthTo) {
865
997
  return (l) => !(l.to <= depthFrom || l.from >= depthTo);
866
998
  }
@@ -908,93 +1040,102 @@ var makeSeededPrng = (seed, scramble = 6271) => {
908
1040
  };
909
1041
 
910
1042
  // src/utils/tooltips.utils.ts
911
- var d3module = __toESM(require("d3"), 1);
912
1043
  var import_d3_tip = __toESM(require("d3-tip"), 1);
913
1044
  var import_sanitize_html = __toESM(require("sanitize-html"), 1);
914
- var d3 = Object.assign(d3module, { tip: import_d3_tip.default });
1045
+ var import_utils2 = require("@welldot/utils");
915
1046
  var esc = (v) => (0, import_sanitize_html.default)(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
916
1047
  var _tooltipCache = /* @__PURE__ */ new WeakMap();
917
- var populateTooltips = (svg, customClasses, units, tooltipConfig) => {
1048
+ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale) => {
918
1049
  const svgEl = typeof svg.node === "function" ? svg.node() : null;
919
- if (svgEl && _tooltipCache.has(svgEl)) {
920
- return _tooltipCache.get(svgEl);
1050
+ const signature = JSON.stringify({ units, tooltipConfig, labels, locale });
1051
+ const cached = svgEl ? _tooltipCache.get(svgEl) : void 0;
1052
+ if (cached && cached.signature === signature) {
1053
+ return cached.tooltips;
921
1054
  }
922
1055
  const tipsText = {
923
1056
  geology: (_, d) => `
924
- <span class="${customClasses.tooltip.title}">Litologia</span>
925
- <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>
926
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>
927
- ${d.geologic_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Unidade geol\xF3gica:</strong> ${esc(d.geologic_unit)}</span>` : ""}
928
- ${d.aquifer_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Unidade aqu\xEDfera:</strong> ${esc(d.aquifer_unit)}</span>` : ""}
1057
+ <span class="${customClasses.tooltip.title}">${labels.geology.title}</span>
1058
+ <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>
1059
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>
1060
+ ${d.geologic_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.geology.geologicUnit}</strong> ${esc(d.geologic_unit)}</span>` : ""}
1061
+ ${d.aquifer_unit ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.geology.aquiferUnit}</strong> ${esc(d.aquifer_unit)}</span>` : ""}
929
1062
  `,
930
1063
  hole: (_, d) => `
931
- <span class="${customClasses.tooltip.title}">FURO</span>
932
- <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>
933
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
1064
+ <span class="${customClasses.tooltip.title}">${labels.hole.title}</span>
1065
+ <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>
1066
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.diameter}</strong>${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
934
1067
  `,
935
1068
  surfaceCase: (_, d) => `
936
- <span class="${customClasses.tooltip.title}">TUBO DE BOCA</span>
937
- <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>
938
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
1069
+ <span class="${customClasses.tooltip.title}">${labels.surfaceCase.title}</span>
1070
+ <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>
1071
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.diameter}</strong>${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
939
1072
  `,
940
1073
  holeFill: (_, d) => `
941
- <span class="${customClasses.tooltip.title}">ESP. ANULAR</span>
942
- <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>
943
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Di\xE2metro:</strong>${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
1074
+ <span class="${customClasses.tooltip.title}">${labels.holeFill.title}</span>
1075
+ <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>
1076
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.diameter}</strong>${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
944
1077
  <span class="${customClasses.tooltip.secondaryInfo}">
945
- <strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}
1078
+ <strong>${labels.common.description}</strong> ${esc(d.description)}
946
1079
  </span>
947
1080
  `,
948
1081
  wellCase: (_, d) => `
949
- <span class="${customClasses.tooltip.title}">REVESTIMENTO</span>
950
- <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>
1082
+ <span class="${customClasses.tooltip.title}">${labels.wellCase.title}</span>
1083
+ <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>
951
1084
  <span class="${customClasses.tooltip.secondaryInfo}">
952
- <strong>Di\xE2metro:</strong> ${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}
1085
+ <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}
953
1086
  </span>
954
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Tipo:</strong> ${esc(d.type)}</span>
1087
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
955
1088
  `,
956
1089
  wellScreen: (_, d) => `
957
- <span class="${customClasses.tooltip.title}">FILTROS</span>
958
- <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>
1090
+ <span class="${customClasses.tooltip.title}">${labels.wellScreen.title}</span>
1091
+ <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>
959
1092
  <span class="${customClasses.tooltip.secondaryInfo}">
960
- <strong>Di\xE2metro:</strong> ${esc(formatDiameter(d.diameter, units.diameter))} ${esc(units.diameter)}</span>
961
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Tipo:</strong> ${esc(d.type)}</span>
1093
+ <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
1094
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
962
1095
  <span class="${customClasses.tooltip.secondaryInfo}">
963
- <strong>Ranhura:</strong> ${esc(d.screen_slot_mm)} mm
1096
+ <strong>${labels.wellScreen.slot}</strong> ${esc((0, import_utils2.formatNumber)(d.screen_slot, { maximumFractionDigits: 2, locale }))} mm
964
1097
  </span>
965
1098
  `,
1099
+ reduction: (_, d) => `
1100
+ <span class="${customClasses.tooltip.title}">${labels.reduction.title}</span>
1101
+ <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>
1102
+ <span class="${customClasses.tooltip.secondaryInfo}">
1103
+ <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))}
1104
+ </span>
1105
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
1106
+ `,
966
1107
  conflict: (_, d) => `
967
- <span class="${customClasses.tooltip.title}">CONFLITO</span>
968
- <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>
1108
+ <span class="${customClasses.tooltip.title}">${labels.conflict.title}</span>
1109
+ <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>
969
1110
  `,
970
1111
  fracture: (_, d) => {
971
- const title = d.swarm ? "ENXAME DE FRATURAS" : "FRATURA";
1112
+ const title = d.swarm ? labels.fracture.titleSwarm : labels.fracture.title;
972
1113
  return `
973
1114
  <span class="${customClasses.tooltip.title}">${title}</span>
974
- <span class="${customClasses.tooltip.primaryInfo}"><strong>Profundidade:</strong> ${esc(formatLength(d.depth, units.length))} ${esc(units.length)}</span>
975
- ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Entrada d'\xE1gua:</strong> ${esc(formatLength(d.depth, units.length))} ${esc(units.length)}</span>` : ""}
976
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Mergulho:</strong> ${esc(d.dip)}\xB0</span>
977
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Azimute:</strong> ${esc(d.azimuth)}\xB0</span>
978
- ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>` : ""}
1115
+ <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.fracture.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
1116
+ ${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>` : ""}
1117
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.dip}</strong> ${esc(d.dip)}\xB0</span>
1118
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.azimuth}</strong> ${esc(d.azimuth)}\xB0</span>
1119
+ ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
979
1120
  `;
980
1121
  },
981
1122
  cementPad: (_, d) => `
982
- <span class="${customClasses.tooltip.title}">LAJE DE PROTE\xC7\xC3O</span>
1123
+ <span class="${customClasses.tooltip.title}">${labels.cementPad.title}</span>
983
1124
  <span class="${customClasses.tooltip.primaryInfo}">${esc(d.type)}</span>
984
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>Espessura:</strong>
985
- ${esc(formatLength(d.thickness, units.length))} ${esc(units.length)}</span>
1125
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cementPad.thickness}</strong>
1126
+ ${esc(formatLength(d.thickness, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
986
1127
  <span class="${customClasses.tooltip.secondaryInfo}">
987
- <strong>Largura:</strong> ${esc(formatLength(d.width, units.length))} ${esc(units.length)}
1128
+ <strong>${labels.cementPad.width}</strong> ${esc(formatLength(d.width, units.length, locale))} ${esc(getLengthUnit(units.length))}
988
1129
  </span>
989
1130
  <span class="${customClasses.tooltip.secondaryInfo}">
990
- <strong>Comprimento:</strong> ${esc(formatLength(d.length, units.length))} ${esc(units.length)}
1131
+ <strong>${labels.cementPad.length}</strong> ${esc(formatLength(d.length, units.length, locale))} ${esc(getLengthUnit(units.length))}
991
1132
  </span>
992
1133
  `,
993
1134
  cave: (_, d) => `
994
- <span class="${customClasses.tooltip.title}">CAVERNA</span>
995
- <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>
996
- ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Entrada d'\xE1gua</strong></span>` : ""}
997
- ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>Descri\xE7\xE3o:</strong> ${esc(d.description)}</span>` : ""}
1135
+ <span class="${customClasses.tooltip.title}">${labels.cave.title}</span>
1136
+ <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>
1137
+ ${d.water_intake ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cave.waterIntake}</strong></span>` : ""}
1138
+ ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
998
1139
  `
999
1140
  };
1000
1141
  const noop = { show: () => {
@@ -1007,13 +1148,13 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig) => {
1007
1148
  tooltips[tipTextKey] = noop;
1008
1149
  return;
1009
1150
  }
1010
- tooltips[tipTextKey] = d3.tip().attr("class", customClasses.tooltip.root).direction("e").html(tipsText[tipTextKey]);
1151
+ tooltips[tipTextKey] = (0, import_d3_tip.default)().attr("class", customClasses.tooltip.root).direction("e").html(tipsText[tipTextKey]);
1011
1152
  svg.call(
1012
1153
  tooltips[tipTextKey]
1013
1154
  );
1014
1155
  });
1015
1156
  svgEl?.setAttribute("data-tooltips-init", "true");
1016
- if (svgEl) _tooltipCache.set(svgEl, tooltips);
1157
+ if (svgEl) _tooltipCache.set(svgEl, { signature, tooltips });
1017
1158
  return tooltips;
1018
1159
  };
1019
1160
 
@@ -1029,7 +1170,9 @@ var preloadFgdcTextures = () => {
1029
1170
  return _fgdcTexturesPromise;
1030
1171
  };
1031
1172
  var getLithologicalFillList = (data, opts) => {
1032
- const uniqueTextureCodes = [...new Set(data.map((d) => d.fgdc_texture))];
1173
+ const textureKey = (d) => String(d.texture.code);
1174
+ const isFgdc = (d) => !d.texture.vocabulary || d.texture.vocabulary === "fgdc";
1175
+ const uniqueTextureCodes = [...new Set(data.filter(isFgdc).map(textureKey))];
1033
1176
  const texturesLoaded = Object.fromEntries(
1034
1177
  uniqueTextureCodes.filter((code) => {
1035
1178
  const path = _fgdcTextures[code];
@@ -1038,10 +1181,11 @@ var getLithologicalFillList = (data, opts) => {
1038
1181
  );
1039
1182
  return Object.fromEntries(
1040
1183
  data.map((d) => {
1041
- const pathData = texturesLoaded[d.fgdc_texture];
1042
- if (!pathData) return [`${d.fgdc_texture}.${d.from}`, d.color];
1184
+ const key = textureKey(d);
1185
+ const pathData = isFgdc(d) ? texturesLoaded[key] : void 0;
1186
+ if (!pathData) return [`${key}.${d.from}`, d.color];
1043
1187
  return [
1044
- `${d.fgdc_texture}.${d.from}`,
1188
+ `${key}.${d.from}`,
1045
1189
  import_textures2.default.paths().d(() => pathData).size(opts.size).strokeWidth(opts.strokeWidth).stroke(opts.stroke).background(d.color)
1046
1190
  ];
1047
1191
  })
@@ -1050,7 +1194,7 @@ var getLithologicalFillList = (data, opts) => {
1050
1194
  var getLithologyFill = (geologyData, svg, opts) => {
1051
1195
  const lithologicalFill = getLithologicalFillList(geologyData, opts);
1052
1196
  return (d) => {
1053
- const fill = lithologicalFill[`${d.fgdc_texture}.${d.from}`];
1197
+ const fill = lithologicalFill[`${String(d.texture.code)}.${d.from}`];
1054
1198
  if (!fill.url) return fill;
1055
1199
  svg.call(fill);
1056
1200
  return fill.url();
@@ -1113,7 +1257,7 @@ function drawAnnotationLabels(ctx, data) {
1113
1257
  ctx.groups.lithologyLabelsGroup.selectAll("*").remove();
1114
1258
  if (active.has("lithology") && show.has("depth")) {
1115
1259
  data.lithology.filter((d) => d.to <= ctx.depthTo).forEach((d) => {
1116
- const text = `${d.to}`;
1260
+ const text = `${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`;
1117
1261
  const approxW = text.length * (st.fontSize * 0.52) + s.depthTipPadX * 2;
1118
1262
  const y = ctx.yScale(d.to) - s.depthTipHeight / 2;
1119
1263
  const tipG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.depth);
@@ -1129,20 +1273,20 @@ function drawAnnotationLabels(ctx, data) {
1129
1273
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
1130
1274
  originX: ctx.geoXRight,
1131
1275
  originY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
1132
- header: `${d.from}\u2013${d.to} m`,
1276
+ header: `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`,
1133
1277
  description: d.description || ""
1134
1278
  });
1135
1279
  });
1136
1280
  }
1137
1281
  if (active.has("fractures")) {
1138
1282
  data.fractures.filter((d) => d.depth >= ctx.depthFrom && d.depth < ctx.depthTo).forEach((d) => {
1139
- const typeLabel = d.water_intake ? "fratura aberta" : "fratura";
1283
+ const typeLabel = d.water_intake ? ctx.renderConfig.labels.typeLabels?.fractureWater ?? "fratura aberta" : ctx.renderConfig.labels.typeLabels?.fracture ?? "fratura";
1140
1284
  items.push({
1141
1285
  sortDepth: d.depth,
1142
1286
  baseY: ctx.yScale(d.depth),
1143
1287
  originX: ctx.pocoCenterX,
1144
1288
  originY: ctx.yScale(d.depth),
1145
- header: `${d.depth} m \xB7 ${typeLabel}`,
1289
+ header: `${formatLength(d.depth, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
1146
1290
  description: d.description || ""
1147
1291
  });
1148
1292
  });
@@ -1150,13 +1294,13 @@ function drawAnnotationLabels(ctx, data) {
1150
1294
  if (active.has("caves")) {
1151
1295
  data.caves.filter((d) => d.from < ctx.depthTo).forEach((d) => {
1152
1296
  const midDepth = (d.from + d.to) / 2;
1153
- const typeLabel = d.water_intake ? "caverna \xFAmida" : "caverna";
1297
+ const typeLabel = d.water_intake ? ctx.renderConfig.labels.typeLabels?.caveWater ?? "caverna \xFAmida" : ctx.renderConfig.labels.typeLabels?.cave ?? "caverna";
1154
1298
  items.push({
1155
1299
  sortDepth: d.from,
1156
1300
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
1157
1301
  originX: ctx.geoXRight,
1158
1302
  originY: ctx.yScale(Math.max(midDepth, ctx.depthFrom)),
1159
- header: `${d.from}\u2013${d.to} m \xB7 ${typeLabel}`,
1303
+ header: `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
1160
1304
  description: d.description || ""
1161
1305
  });
1162
1306
  });
@@ -1202,6 +1346,19 @@ function drawAnnotationLabels(ctx, data) {
1202
1346
  }
1203
1347
  }
1204
1348
 
1349
+ // src/renderers/caves.renderer.ts
1350
+ var d3 = __toESM(require("d3"), 1);
1351
+
1352
+ // src/utils/key.utils.ts
1353
+ var makeIntervalKey = (type) => (d, _i) => {
1354
+ const { key, from, to } = d;
1355
+ return key ? `${key}` : `${type}:${from}:${to}`;
1356
+ };
1357
+ var makePointKey = (type) => (d, i) => {
1358
+ const { key, depth } = d;
1359
+ return key ? `${key}` : `${type}:${depth}:${i}`;
1360
+ };
1361
+
1205
1362
  // src/renderers/caves.renderer.ts
1206
1363
  function drawCaves(ctx, data) {
1207
1364
  if (!data.length) return;
@@ -1218,9 +1375,14 @@ function drawCaves(ctx, data) {
1218
1375
  classes,
1219
1376
  tooltips
1220
1377
  } = ctx;
1221
- cavesGroup.selectAll(`g.${classes.caves.item}`).remove();
1222
1378
  const rc = renderConfig.caves;
1223
- data.forEach((cave) => {
1379
+ const groups = cavesGroup.selectAll(`g.${classes.caves.item}`).data(data, makeIntervalKey("cave"));
1380
+ groups.exit().transition(ctx.transition).style("opacity", 0).remove();
1381
+ 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);
1382
+ const merged = mergeEnter(entered, groups);
1383
+ merged.each(function(cave) {
1384
+ const g = d3.select(this);
1385
+ g.selectAll("*").remove();
1224
1386
  const seedTop = cave.from * 100 + cave.to;
1225
1387
  const seedBot = cave.to * 100 + cave.from + 999;
1226
1388
  const rngTop = makeSeededPrng(seedTop);
@@ -1247,25 +1409,25 @@ function drawCaves(ctx, data) {
1247
1409
  const botReversed = botCut ? `L ${geoXLeft.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath([...botPts].reverse(), { omitMoveTo: true });
1248
1410
  const closedPath = `${topPath} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}${botReversed} L ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} Z`;
1249
1411
  const caveTexture = cave.water_intake ? textures3.cave_wet : textures3.cave_dry;
1250
- 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);
1251
1412
  const caveStroke = cave.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
1252
1413
  g.append("path").attr("class", classes.caves.fill).attr("d", closedPath).attr("fill", caveTexture.url()).attr("fill-opacity", theme.cave.fillOpacity).attr("stroke", "none");
1253
1414
  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");
1254
1415
  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");
1255
1416
  });
1417
+ merged.transition(ctx.transition).style("opacity", 1);
1256
1418
  }
1257
1419
 
1258
1420
  // src/renderers/construction-labels.renderer.ts
1259
1421
  var d32 = __toESM(require("d3"), 1);
1260
- var import_utils = require("@welldot/utils");
1422
+ var import_utils3 = require("@welldot/utils");
1261
1423
  function drawConstructionLabels(ctx, data) {
1262
1424
  if (!ctx.renderConfig.constructionLabels.active) return;
1263
1425
  ctx.groups.constructionLabelsGroup.selectAll("*").remove();
1264
1426
  const clc = ctx.renderConfig.constructionLabels;
1265
1427
  const clct = ctx.theme.constructionLabels;
1266
- const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter);
1267
- const dUnit = getDiameterUnit(ctx.units.diameter);
1268
- const maxXValues = (0, import_utils.getProfileDiamValues)(ctx.constructionData);
1428
+ const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter, ctx.locale);
1429
+ const dUnit = getDiameterUnit(ctx.units.diameter, ctx.locale);
1430
+ const maxXValues = (0, import_utils3.getProfileDiamValues)(ctx.constructionData);
1269
1431
  const xScale = d32.scaleLinear().domain([0, d32.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
1270
1432
  const occupied = [];
1271
1433
  const placeLabel = (d, text) => {
@@ -1326,26 +1488,27 @@ function drawConstructionLabels(ctx, data) {
1326
1488
  }).forEach(
1327
1489
  (d) => placeLabel(
1328
1490
  d,
1329
- `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${d.type} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot_mm)}${dUnit}`
1491
+ `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${d.type} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot)}${dUnit}`
1330
1492
  )
1331
1493
  );
1332
1494
  }
1333
1495
 
1334
1496
  // src/renderers/construction.renderer.ts
1335
1497
  var d33 = __toESM(require("d3"), 1);
1336
- var import_utils2 = require("@welldot/utils");
1498
+ var import_utils4 = require("@welldot/utils");
1337
1499
  function drawConstructive(ctx, data) {
1338
1500
  const { getHeight, getYPos } = getYAxisFunctions(ctx.yScale, {
1339
1501
  from: ctx.depthFrom,
1340
1502
  to: ctx.depthTo
1341
1503
  });
1342
- const maxXValues = (0, import_utils2.getProfileDiamValues)(ctx.constructionData);
1504
+ const maxXValues = (0, import_utils4.getProfileDiamValues)(ctx.constructionData);
1343
1505
  const maxXValueConstruction = d33.max(maxXValues) || 0;
1344
1506
  const xScale = d33.scaleLinear().domain([0, maxXValueConstruction]).range([0, ctx.POCO_WIDTH]);
1345
1507
  const rcc = ctx.renderConfig.construction;
1508
+ const yPos = getYPos;
1346
1509
  ctx.groups.constructionGroup.selectAll(`.${ctx.classes.cementPad.item}`).remove();
1347
1510
  if (data.cement_pad && data.cement_pad.thickness && ctx.depthFrom === 0) {
1348
- const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad]);
1511
+ const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad], () => "cement_pad");
1349
1512
  cementPad.exit().remove();
1350
1513
  const newCementPad = cementPad.enter().append("rect").attr("class", ctx.classes.cementPad.item).attr(
1351
1514
  "x",
@@ -1360,36 +1523,19 @@ function drawConstructive(ctx, data) {
1360
1523
  }).attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth);
1361
1524
  newCementPad.on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1362
1525
  }
1363
- const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, stableLayerKey);
1526
+ const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, makeIntervalKey("bore_hole"));
1364
1527
  hole.exit().transition(ctx.transition).attr("height", 0).remove();
1365
- 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);
1366
- withTransition(newHole, ctx.transition).attr(
1367
- "opacity",
1368
- ctx.theme.boreHole.opacity
1369
- );
1370
- hole.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1371
- withTransition(hole, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1372
- const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case, stableLayerKey);
1528
+ 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);
1529
+ const mergedHole = mergeEnter(newHole, hole).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1530
+ withTransition(mergedHole, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1531
+ const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case, makeIntervalKey("surface_case"));
1373
1532
  surfaceCaseGs.exit().transition(ctx.transition).style("opacity", 0).remove();
1374
- 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);
1375
- newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none");
1533
+ const newSC = surfaceCaseGs.enter().append("g").attr("class", ctx.classes.surfaceCase.rect).on("mouseover", ctx.tooltips.surfaceCase.show).on("mouseout", ctx.tooltips.surfaceCase.hide);
1534
+ newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none").attr("y", yPos).attr("height", 0);
1376
1535
  newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1377
1536
  newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1378
- newSC.each((d, i, nodes) => {
1379
- const x = (ctx.POCO_CENTER - xScale(
1380
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1381
- )) / 2;
1382
- const w = xScale(
1383
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1384
- );
1385
- const g = d33.select(nodes[i]);
1386
- 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));
1387
- const sideNodes = g.selectAll(".surface-case-side").nodes();
1388
- d33.select(sideNodes[0]).attr("x1", x).attr("x2", x).attr("y1", getYPos(d)).attr("y2", getYPos(d) + getHeight(d));
1389
- d33.select(sideNodes[1]).attr("x1", x + w).attr("x2", x + w).attr("y1", getYPos(d)).attr("y2", getYPos(d) + getHeight(d));
1390
- });
1391
- withTransition(newSC, ctx.transition).style("opacity", 1);
1392
- surfaceCaseGs.each((d, i, nodes) => {
1537
+ const mergedSC = mergeEnter(newSC, surfaceCaseGs);
1538
+ mergedSC.each((d, i, nodes) => {
1393
1539
  const x = (ctx.POCO_CENTER - xScale(
1394
1540
  d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1395
1541
  )) / 2;
@@ -1403,47 +1549,61 @@ function drawConstructive(ctx, data) {
1403
1549
  d33.select(sideNodes[1]).attr("x1", x + w).attr("x2", x + w);
1404
1550
  });
1405
1551
  withTransition(
1406
- surfaceCaseGs.select(".surface-case-fill"),
1552
+ mergedSC.select(".surface-case-fill"),
1407
1553
  ctx.transition
1408
1554
  ).attr("y", (d) => getYPos(d)).attr("height", (d) => getHeight(d));
1409
- withTransition(
1410
- surfaceCaseGs.selectAll(".surface-case-side"),
1411
- ctx.transition
1412
- ).attr("y1", (d) => getYPos(d)).attr(
1555
+ mergedSC.selectAll(".surface-case-side").attr("y1", (d) => getYPos(d)).attr(
1413
1556
  "y2",
1414
1557
  (d) => getYPos(d) + getHeight(d)
1415
1558
  );
1416
- const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, stableLayerKey);
1559
+ const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, makeIntervalKey("hole_fill"));
1417
1560
  holeFill.exit().transition(ctx.transition).attr("height", 0).remove();
1418
- 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);
1419
- withTransition(newHoleFill, ctx.transition).attr("opacity", 1);
1420
- 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());
1421
- withTransition(holeFill, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1422
- const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case, stableLayerKey);
1561
+ 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);
1562
+ const mergedHoleFill = mergeEnter(newHoleFill, holeFill).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1563
+ withTransition(mergedHoleFill, ctx.transition).attr("y", getYPos).attr("height", getHeight).attr("fill", (d) => ctx.textures[d.type].url());
1564
+ const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case, makeIntervalKey("well_case"));
1423
1565
  wellCase.exit().transition(ctx.transition).attr("height", 0).remove();
1424
- 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);
1425
- withTransition(newWellCase, ctx.transition).attr("opacity", 1);
1426
- wellCase.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1427
- withTransition(wellCase, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1428
- const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen, stableLayerKey);
1566
+ 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);
1567
+ const mergedWellCase = mergeEnter(newWellCase, wellCase).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1568
+ withTransition(mergedWellCase, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1569
+ const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen, makeIntervalKey("well_screen"));
1429
1570
  wellScreen.exit().transition(ctx.transition).attr("height", 0).remove();
1430
- 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);
1431
- withTransition(newWellScreen, ctx.transition).attr("opacity", 1);
1432
- wellScreen.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1433
- withTransition(wellScreen, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1571
+ 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);
1572
+ const mergedWellScreen = mergeEnter(newWellScreen, wellScreen).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1573
+ withTransition(mergedWellScreen, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1574
+ const reductionPoints = (d, yTop, yBot) => {
1575
+ const xTopLeft = (ctx.POCO_CENTER - xScale(d.diam_from)) / 2;
1576
+ const xTopRight = (ctx.POCO_CENTER + xScale(d.diam_from)) / 2;
1577
+ const xBotLeft = (ctx.POCO_CENTER - xScale(d.diam_to)) / 2;
1578
+ const xBotRight = (ctx.POCO_CENTER + xScale(d.diam_to)) / 2;
1579
+ return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
1580
+ };
1581
+ const reduction = ctx.groups.reductionGroup.selectAll(`.${ctx.classes.reduction.item}`).data(data.reduction, makeIntervalKey("reduction"));
1582
+ reduction.exit().transition(ctx.transition).style("opacity", 0).remove();
1583
+ const collapsedPoints = (d) => {
1584
+ const r = d;
1585
+ const y = getYPos(r);
1586
+ return reductionPoints(r, y, y);
1587
+ };
1588
+ 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);
1589
+ const mergedReduction = mergeEnter(newReduction, reduction);
1590
+ withTransition(mergedReduction, ctx.transition).attr(
1591
+ "points",
1592
+ (d) => reductionPoints(d, getYPos(d), getYPos(d) + getHeight(d))
1593
+ );
1434
1594
  const conflictAreas = [];
1435
1595
  conflictAreas.push(...getConflictAreas(data.well_case, data.well_screen));
1436
1596
  conflictAreas.push(...getConflictAreas(data.well_screen, data.well_case));
1437
1597
  const mergedConflicts = mergeConflicts(conflictAreas, 1);
1438
- const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts, (d) => `${d.from}_${d.to}_${d.diameter}`);
1598
+ const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts, makeIntervalKey("conflict"));
1439
1599
  conflict.exit().transition(ctx.transition).attr("height", 0).remove();
1440
- 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);
1441
- withTransition(newConflict, ctx.transition).attr("opacity", 1);
1442
- conflict.attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1443
- withTransition(conflict, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1600
+ 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);
1601
+ const mergedConflict = mergeEnter(newConflict, conflict).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1602
+ withTransition(mergedConflict, ctx.transition).attr("y", ctx.yScale(0)).attr("height", getHeight);
1444
1603
  }
1445
1604
 
1446
1605
  // src/renderers/fractures.renderer.ts
1606
+ var d34 = __toESM(require("d3"), 1);
1447
1607
  function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1448
1608
  const pts = [];
1449
1609
  for (let i = 0; i < steps; i++) {
@@ -1466,19 +1626,23 @@ function drawFractures(ctx, data) {
1466
1626
  classes,
1467
1627
  tooltips
1468
1628
  } = ctx;
1469
- fracturesGroup.selectAll(`g.${classes.fractures.item}`).remove();
1470
1629
  const rf = renderConfig.fractures;
1471
1630
  const halfWidth = POCO_WIDTH * rf.widthMultiplier / 2;
1472
1631
  const xa = pocoCenterX - halfWidth;
1473
1632
  const w = halfWidth * 2;
1474
1633
  const normToAbsX = (normX) => xa + normX * w;
1475
- data.forEach((fracture) => {
1634
+ const groups = fracturesGroup.selectAll(`g.${classes.fractures.item}`).data(data, makePointKey("fracture"));
1635
+ groups.exit().transition(ctx.transition).style("opacity", 0).remove();
1636
+ 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);
1637
+ const merged = mergeEnter(entered, groups);
1638
+ merged.attr(
1639
+ "transform",
1640
+ (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fracture.dip},${pocoCenterX},0)`
1641
+ );
1642
+ merged.each(function(fracture) {
1643
+ const g = d34.select(this);
1644
+ g.selectAll("*").remove();
1476
1645
  const rng = makeSeededPrng(fracture.depth, 7919);
1477
- const cy = yScale(fracture.depth);
1478
- const g = fracturesGroup.append("g").attr("class", classes.fractures.item).datum(fracture).attr(
1479
- "transform",
1480
- `translate(0,${cy}) rotate(${fracture.dip},${pocoCenterX},0)`
1481
- ).on("mouseover", tooltips.fracture.show).on("mouseout", tooltips.fracture.hide).style("cursor", "pointer");
1482
1646
  const hitBuffer = fracture.swarm ? rf.hitBuffer.swarm : rf.hitBuffer.single;
1483
1647
  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");
1484
1648
  const fractureStroke = fracture.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
@@ -1551,11 +1715,12 @@ function drawFractures(ctx, data) {
1551
1715
  }
1552
1716
  }
1553
1717
  });
1718
+ merged.transition(ctx.transition).style("opacity", 1);
1554
1719
  }
1555
1720
 
1556
1721
  // src/renderers/highlights.renderer.ts
1557
- var d34 = __toESM(require("d3"), 1);
1558
- var import_utils3 = require("@welldot/utils");
1722
+ var d35 = __toESM(require("d3"), 1);
1723
+ var import_utils5 = require("@welldot/utils");
1559
1724
  function drawHighlightItem(ctx, group, x, y, w, h, label) {
1560
1725
  if (h <= 0 || w <= 0) return;
1561
1726
  const hlConfig = ctx.renderConfig.highlights;
@@ -1614,8 +1779,8 @@ function drawHighlights(ctx, hl) {
1614
1779
  h.label
1615
1780
  );
1616
1781
  });
1617
- const maxXVals = (0, import_utils3.getProfileDiamValues)(ctx.constructionData);
1618
- const xScaleConstr = d34.scaleLinear().domain([0, d34.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1782
+ const maxXVals = (0, import_utils5.getProfileDiamValues)(ctx.constructionData);
1783
+ const xScaleConstr = d35.scaleLinear().domain([0, d35.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1619
1784
  const constructionConfig = ctx.renderConfig.construction;
1620
1785
  if (hl.cement_pad && ctx.depthFrom === 0 && ctx.constructionData.cement_pad?.thickness) {
1621
1786
  const cp = ctx.constructionData.cement_pad;
@@ -1695,18 +1860,29 @@ function drawHighlights(ctx, hl) {
1695
1860
  stdX,
1696
1861
  stdW
1697
1862
  );
1863
+ drawConstrHighlights(
1864
+ hl.reduction,
1865
+ ctx.constructionData.reduction.map((d) => ({
1866
+ from: d.from,
1867
+ to: d.to,
1868
+ diameter: Math.max(d.diam_from, d.diam_to)
1869
+ })),
1870
+ stdX,
1871
+ stdW
1872
+ );
1698
1873
  }
1699
1874
 
1700
1875
  // src/renderers/legend.renderer.ts
1701
- var d3module2 = __toESM(require("d3"), 1);
1876
+ var d3module = __toESM(require("d3"), 1);
1702
1877
  var import_defu = require("defu");
1703
- var d35 = { ...d3module2 };
1878
+ var d36 = { ...d3module };
1704
1879
  function drawWellLegend(selector, profile, options = {}) {
1705
1880
  const fractures = profile.fractures ?? [];
1706
1881
  const caves = profile.caves ?? [];
1707
1882
  const boreHoles = profile.bore_hole ?? [];
1708
1883
  const wellCases = profile.well_case ?? [];
1709
1884
  const wellScreens = profile.well_screen ?? [];
1885
+ const reductions = profile.reduction ?? [];
1710
1886
  const holeFills = profile.hole_fill ?? [];
1711
1887
  const hasSimple = fractures.some((f) => !f.swarm && !f.water_intake);
1712
1888
  const hasSwarm = fractures.some((f) => f.swarm);
@@ -1719,6 +1895,7 @@ function drawWellLegend(selector, profile, options = {}) {
1719
1895
  const hasHoleFillSeal = holeFills.some((h) => h.type === "seal");
1720
1896
  const hasWellCase = wellCases.length > 0;
1721
1897
  const hasWellScreen = wellScreens.length > 0;
1898
+ const hasReduction = reductions.length > 0;
1722
1899
  const hasCementPad = !!profile.cement_pad?.thickness;
1723
1900
  const hasConflict = mergeConflicts(
1724
1901
  [
@@ -1805,13 +1982,19 @@ function drawWellLegend(selector, profile, options = {}) {
1805
1982
  label: cfg.labels.wellScreen,
1806
1983
  subKind: "wellScreen"
1807
1984
  });
1985
+ if (hasReduction)
1986
+ items.push({
1987
+ kind: "construction",
1988
+ label: cfg.labels.reduction,
1989
+ subKind: "reduction"
1990
+ });
1808
1991
  if (hasConflict)
1809
1992
  items.push({
1810
1993
  kind: "construction",
1811
1994
  label: cfg.labels.conflict,
1812
1995
  subKind: "conflict"
1813
1996
  });
1814
- const svgEl = d35.select(selector);
1997
+ const svgEl = d36.select(selector);
1815
1998
  svgEl.selectAll("*").remove();
1816
1999
  if (items.length === 0) {
1817
2000
  svgEl.attr("width", 0).attr("height", 0);
@@ -1863,6 +2046,15 @@ function drawWellLegend(selector, profile, options = {}) {
1863
2046
  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");
1864
2047
  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);
1865
2048
  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);
2049
+ } else if (item.subKind === "reduction") {
2050
+ const topW = rw * 0.5;
2051
+ const y1 = rowSymY - rh / 2;
2052
+ const y2 = rowSymY + rh / 2;
2053
+ const midX = cx + rw / 2;
2054
+ symG.append("polygon").attr("class", cls.constructionRect).attr(
2055
+ "points",
2056
+ `${midX - topW / 2},${y1} ${midX + topW / 2},${y1} ${cx + rw},${y2} ${cx},${y2}`
2057
+ ).attr("fill", theme.reduction.fill).attr("stroke", theme.reduction.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
1866
2058
  } else {
1867
2059
  const symMap = {
1868
2060
  boreHole: {
@@ -1920,13 +2112,11 @@ function drawLithology(ctx, data) {
1920
2112
  from: depthFrom,
1921
2113
  to: depthTo
1922
2114
  });
1923
- const fillFn = getLithologyFill(data, svg, ctx.theme.lithologyTexture);
1924
- const rects = lithologyGroup.selectAll(`.${classes.lithology.rect}`).data(data, stableLayerKey);
2115
+ const rects = lithologyGroup.selectAll(`.${classes.lithology.rect}`).data(data, makeIntervalKey("lithology"));
1925
2116
  rects.exit().transition(transition).attr("height", 0).remove();
1926
- 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);
1927
- withTransition(newLayers, transition).attr("opacity", 1);
1928
- rects.attr("fill", fillFn);
1929
- withTransition(rects, transition).attr("y", getYPos).attr("height", getHeight);
2117
+ 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);
2118
+ const merged = mergeEnter(newLayers, rects);
2119
+ withTransition(merged, transition).attr("y", getYPos).attr("height", getHeight).attr("fill", getLithologyFill(data, svg, ctx.theme.lithologyTexture));
1930
2120
  }
1931
2121
 
1932
2122
  // src/renderers/unit-labels.renderer.ts
@@ -2013,20 +2203,26 @@ function buildSvgStyleBlock(theme) {
2013
2203
  `.wp-annotation-label{font-size:${s(labels.fontSize)}px}`,
2014
2204
  `.wp-annotation-header{font-weight:${s(labels.headerFontWeight ?? 600)};font-family:${s(labels.headerFont)};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`,
2015
2205
  `.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)}}`,
2016
- `.wp-depth-tip-text{font-family:${s(labels.bodyFont ?? "sans-serif")};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`
2206
+ `.wp-depth-tip-text{font-family:${s(labels.bodyFont ?? "sans-serif")};font-size:${s(labels.fontSize)}px;color:${s(labels.color)}}`,
2207
+ `.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}`,
2208
+ `.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)}}`,
2209
+ `.wp-tooltip-primary{font-weight:${s(labels.headerFontWeight ?? 600)};font-size:12px;color:${s(labels.color)}}`,
2210
+ `.wp-tooltip-secondary{font-size:11px;color:${s(labels.bodyColor)}}`
2017
2211
  ].join("");
2018
2212
  }
2019
2213
 
2020
2214
  // src/Renderer.ts
2021
- var d36 = Object.assign(d3module3, { tip: import_d3_tip2.default });
2215
+ var d37 = { ...d3module2, tip: import_d3_tip2.default };
2022
2216
  var WellRenderer = class {
2023
2217
  /**
2024
2218
  * @param svgs - One entry per SVG panel; multi-entry for split-panel layouts.
2025
2219
  * @param options.classNames - Override default BEM CSS class names.
2026
2220
  * @param options.units - Active measurement units (`length` and `diameter`).
2221
+ * @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.
2027
2222
  * @param options.renderConfig - Rendering behaviour (zoom, pan, animation, layout). Defaults to `INTERACTIVE_RENDER_CONFIG`.
2028
2223
  * @param options.theme - Visual theme overrides. Merged on top of `DEFAULT_WELL_THEME`.
2029
2224
  * @param options.onError - Called when a draw error occurs (e.g., a renderer throws). Falls back to `console.error` if omitted.
2225
+ * @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.
2030
2226
  */
2031
2227
  constructor(svgs, options = {}) {
2032
2228
  this.svgInstances = [];
@@ -2039,6 +2235,7 @@ var WellRenderer = class {
2039
2235
  length: "m",
2040
2236
  diameter: "mm"
2041
2237
  };
2238
+ this.locale = "pt";
2042
2239
  if (svgs.length === 0) {
2043
2240
  console.warn(
2044
2241
  "[WellRenderer] No SVG instances provided. The renderer will be a no-op."
@@ -2047,6 +2244,7 @@ var WellRenderer = class {
2047
2244
  }
2048
2245
  this.svgInstances = svgs;
2049
2246
  if (options.onError) this.onError = options.onError;
2247
+ if (options.onZoom) this.onZoom = options.onZoom;
2050
2248
  if (options.classNames) {
2051
2249
  this.classes = (0, import_defu2.defu)(
2052
2250
  options.classNames,
@@ -2056,6 +2254,9 @@ var WellRenderer = class {
2056
2254
  if (options.units) {
2057
2255
  this.units = { ...this.units, ...options.units };
2058
2256
  }
2257
+ if (options.locale) {
2258
+ this.locale = options.locale;
2259
+ }
2059
2260
  if (options.renderConfig) {
2060
2261
  this.renderConfig = (0, import_defu2.defu)(
2061
2262
  options.renderConfig,
@@ -2071,7 +2272,7 @@ var WellRenderer = class {
2071
2272
  const { selector, height, width, margins } = inst;
2072
2273
  const svgWidth = width + margins.left + margins.right;
2073
2274
  const svgHeight = height + margins.top + margins.bottom;
2074
- const svg = d36.select(selector);
2275
+ const svg = d37.select(selector);
2075
2276
  svg.selectAll("*").remove();
2076
2277
  svg.attr("height", svgHeight).attr("width", svgWidth);
2077
2278
  const defs = svg.append("defs");
@@ -2091,14 +2292,15 @@ var WellRenderer = class {
2091
2292
  );
2092
2293
  const fracturesGroup = poco.append("g").attr("class", this.classes.fractures.group).attr("transform", `translate(${margins.left}, ${margins.top})`).attr("clip-path", `url(#${clipId})`);
2093
2294
  fracturesGroup.append("g").attr("class", this.classes.highlights.fracturesGroup);
2094
- construction.append("g").attr("class", this.classes.constructionLabels.group);
2095
2295
  construction.append("g").attr("class", this.classes.cementPad.group);
2096
2296
  construction.append("g").attr("class", this.classes.boreHole.group);
2097
2297
  construction.append("g").attr("class", this.classes.surfaceCase.group);
2098
2298
  construction.append("g").attr("class", this.classes.holeFill.group);
2099
2299
  construction.append("g").attr("class", this.classes.wellCase.group);
2100
2300
  construction.append("g").attr("class", this.classes.wellScreen.group);
2301
+ construction.append("g").attr("class", this.classes.reduction.group);
2101
2302
  construction.append("g").attr("class", this.classes.conflict.group);
2303
+ construction.append("g").attr("class", this.classes.constructionLabels.group);
2102
2304
  construction.append("g").attr("class", this.classes.highlights.constructionGroup);
2103
2305
  Object.values(this.textures).forEach((texture) => svg.call(texture));
2104
2306
  return { svg, height, width, margins, clipId, clipRectId };
@@ -2112,6 +2314,36 @@ var WellRenderer = class {
2112
2314
  textures: this.renderConfig.textures
2113
2315
  });
2114
2316
  }
2317
+ /**
2318
+ * Multiplies the current zoom scale by `factor` (e.g. `1.25` to zoom in,
2319
+ * `1 / 1.25` to zoom out) on every panel. No-op if both `renderConfig.zoom`
2320
+ * and `renderConfig.pan` are disabled (no zoom behavior was attached).
2321
+ */
2322
+ zoomBy(factor) {
2323
+ for (const state of this.instanceStates) {
2324
+ if (!state.zoomNode) continue;
2325
+ asSvgElement(state.svg).transition().call(state.zoomNode.scaleBy, factor);
2326
+ }
2327
+ }
2328
+ /**
2329
+ * Resets zoom/pan on every panel back to `renderConfig.zoomLevel`
2330
+ * (defaults to `1`, i.e. the initial fit-to-container view).
2331
+ */
2332
+ resetZoom() {
2333
+ const target = d37.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
2334
+ for (const state of this.instanceStates) {
2335
+ if (!state.zoomNode) continue;
2336
+ asSvgElement(state.svg).transition().call(state.zoomNode.transform, target);
2337
+ }
2338
+ }
2339
+ /**
2340
+ * Current zoom scale factor of the first panel (`1` = initial/fit).
2341
+ * Returns `1` if zoom/pan is disabled or `prepareSvg()` hasn't run yet.
2342
+ */
2343
+ getZoomScale() {
2344
+ const node = this.instanceStates[0]?.svg.node();
2345
+ return node ? d37.zoomTransform(node).k : 1;
2346
+ }
2115
2347
  /**
2116
2348
  * Initialises the SVG DOM structure and preloads FGDC textures.
2117
2349
  * Must be called once before the first `draw()`.
@@ -2126,14 +2358,23 @@ var WellRenderer = class {
2126
2358
  * Renders the well profile into all configured SVG panels.
2127
2359
  * @param profile - The well data to render.
2128
2360
  * @param options.units - Override the instance-level measurement units for this render.
2361
+ * @param options.locale - Override the instance-level locale for this render.
2129
2362
  * @param options.highlights - Highlight overlays to display on top of the profile.
2130
2363
  */
2131
2364
  draw(profile, options = {}) {
2132
2365
  if ((0, import_core.isWellEmpty)(profile)) return;
2366
+ if (profile.version !== 2) {
2367
+ console.warn(
2368
+ `[WellRenderer] Received a .well file with version ${profile.version ?? "unknown"}. Only v2 is supported. Normalize with deserializeWell() from @welldot/core before rendering.`
2369
+ );
2370
+ }
2133
2371
  this.units = { ...this.units, ...options.units };
2372
+ if (options.locale) {
2373
+ this.locale = options.locale;
2374
+ }
2134
2375
  const highlights = options.highlights ?? {};
2135
- const maxValues = (0, import_utils4.getProfileLastItemsDepths)(profile);
2136
- const maxYValues = d36.max(maxValues) || 0;
2376
+ const maxValues = (0, import_utils6.getProfileLastItemsDepths)(profile);
2377
+ const maxYValues = d37.max(maxValues) || 0;
2137
2378
  if (this.instanceStates.length === 1) {
2138
2379
  this.drawLogToInstance(
2139
2380
  this.instanceStates[0],
@@ -2145,7 +2386,7 @@ var WellRenderer = class {
2145
2386
  return;
2146
2387
  }
2147
2388
  const totalHeight = this.instanceStates.reduce((s, st) => s + st.height, 0) || 1;
2148
- const yScaleGlobal = d36.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
2389
+ const yScaleGlobal = d37.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
2149
2390
  let currentDepth = 0;
2150
2391
  for (const state of this.instanceStates) {
2151
2392
  const maxSvgDepth = yScaleGlobal.invert(
@@ -2177,6 +2418,7 @@ var WellRenderer = class {
2177
2418
  const holeFillGroup = svg.select(`.${this.classes.holeFill.group}`);
2178
2419
  const wellCaseGroup = svg.select(`.${this.classes.wellCase.group}`);
2179
2420
  const wellScreenGroup = svg.select(`.${this.classes.wellScreen.group}`);
2421
+ const reductionGroup = svg.select(`.${this.classes.reduction.group}`);
2180
2422
  const conflictGroup = svg.select(`.${this.classes.conflict.group}`);
2181
2423
  const constructionLabelsGroup = svg.select(
2182
2424
  `.${this.classes.constructionLabels.group}`
@@ -2197,12 +2439,14 @@ var WellRenderer = class {
2197
2439
  const POCO_WIDTH = svgWidth * pocoWidthRatio;
2198
2440
  const POCO_CENTER = svgWidth * pocoCenterRatio;
2199
2441
  const { duration, ease } = this.renderConfig.animation;
2200
- const transition = d36.transition().duration(duration).ease(ease);
2442
+ const transition = d37.transition().duration(duration).ease(ease);
2201
2443
  const tooltips = populateTooltips(
2202
2444
  svg,
2203
2445
  this.classes,
2204
2446
  this.units,
2205
- this.renderConfig.tooltips
2447
+ this.renderConfig.tooltips,
2448
+ this.renderConfig.tooltipLabels,
2449
+ this.locale
2206
2450
  );
2207
2451
  const { xLeft: geoXLeft, xRightInset: geoXRightInset } = this.renderConfig.geologic;
2208
2452
  const geoXRight = svgWidth - geoXRightInset;
@@ -2223,11 +2467,14 @@ var WellRenderer = class {
2223
2467
  fractures: profile.fractures
2224
2468
  };
2225
2469
  const contentHeight = svgHeight - state.margins.top - state.margins.bottom;
2226
- const yScaleLocal = d36.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
2470
+ const yScaleLocal = d37.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
2227
2471
  const depthFactor = this.units.length === "ft" ? 3.28084 : 1;
2228
- const yScaleAxis = d36.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
2229
- const yAxis = d36.axisLeft(yScaleAxis).tickFormat(
2230
- (d) => `${d}${getLengthUnit(this.units.length)}`
2472
+ const yScaleAxis = d37.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
2473
+ const yAxis = d37.axisLeft(yScaleAxis).tickFormat(
2474
+ (d) => `${(0, import_utils6.formatNumber)(Number(d), {
2475
+ maximumFractionDigits: this.units.length === "ft" ? 1 : 2,
2476
+ locale: this.locale
2477
+ })}${getLengthUnit(this.units.length)}`
2231
2478
  );
2232
2479
  const gY = svg.select(`.${this.classes.yAxis}`).call(yAxis);
2233
2480
  gY.select(".domain").attr("stroke", this.theme.labels.color).attr("fill", "none");
@@ -2264,6 +2511,7 @@ var WellRenderer = class {
2264
2511
  holeFillGroup,
2265
2512
  wellCaseGroup,
2266
2513
  wellScreenGroup,
2514
+ reductionGroup,
2267
2515
  conflictGroup,
2268
2516
  highlightsGeologicGroup,
2269
2517
  highlightsConstructionGroup,
@@ -2289,6 +2537,7 @@ var WellRenderer = class {
2289
2537
  classes: this.classes,
2290
2538
  textures: this.textures,
2291
2539
  units: this.units,
2540
+ locale: this.locale,
2292
2541
  constructionData,
2293
2542
  groups
2294
2543
  };
@@ -2319,12 +2568,24 @@ var WellRenderer = class {
2319
2568
  surfaceCaseGroup.selectAll(`g.${this.classes.surfaceCase.rect}`).each((d, i, nodes) => {
2320
2569
  if (!d) return;
2321
2570
  const sc = d;
2322
- const g = d36.select(nodes[i]);
2571
+ const g = d37.select(nodes[i]);
2323
2572
  const y = transform.applyY(spanY(sc));
2324
2573
  const h = transform.k * spanH(sc);
2325
2574
  g.select(".surface-case-fill").attr("y", y).attr("height", h);
2326
2575
  g.selectAll(".surface-case-side").attr("y1", y).attr("y2", y + h);
2327
2576
  });
2577
+ const maxXValsReduction = (0, import_utils6.getProfileDiamValues)(constructionData);
2578
+ const xScaleReduction = d37.scaleLinear().domain([0, d37.max(maxXValsReduction) || 0]).range([0, POCO_WIDTH]);
2579
+ reductionGroup.selectAll(`polygon.${this.classes.reduction.item}`).attr("points", (d) => {
2580
+ const r = d;
2581
+ const yTop = transform.applyY(spanY(r));
2582
+ const yBot = yTop + transform.k * spanH(r);
2583
+ const xTopLeft = (POCO_CENTER - xScaleReduction(r.diam_from)) / 2;
2584
+ const xTopRight = (POCO_CENTER + xScaleReduction(r.diam_from)) / 2;
2585
+ const xBotLeft = (POCO_CENTER - xScaleReduction(r.diam_to)) / 2;
2586
+ const xBotRight = (POCO_CENTER + xScaleReduction(r.diam_to)) / 2;
2587
+ return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
2588
+ });
2328
2589
  fracturesGroup.selectAll(`g.${this.classes.fractures.item}`).attr("transform", (d) => {
2329
2590
  if (!d) return null;
2330
2591
  const f = d;
@@ -2346,6 +2607,7 @@ var WellRenderer = class {
2346
2607
  well_screen: constructionData.well_screen.filter(inDepth)
2347
2608
  });
2348
2609
  drawHighlights(zoomedCtx, highlights);
2610
+ this.onZoom?.(transform.k);
2349
2611
  };
2350
2612
  const drawProfile = () => {
2351
2613
  drawLithology(ctx, profile.lithology?.filter(inDepth) ?? []);
@@ -2385,17 +2647,23 @@ var WellRenderer = class {
2385
2647
  }
2386
2648
  const { zoom: zoomEnabled, pan: panEnabled } = this.renderConfig;
2387
2649
  if (zoomEnabled || panEnabled) {
2388
- const zoomNode = d36.zoom();
2650
+ const zoomNode = d37.zoom();
2389
2651
  if (zoomEnabled || panEnabled) zoomNode.on("zoom", zooming);
2390
- if (!zoomEnabled) zoomNode.scaleExtent([1, 1]);
2652
+ zoomNode.scaleExtent(
2653
+ zoomEnabled ? [
2654
+ this.renderConfig.minZoomScale ?? 0,
2655
+ this.renderConfig.maxZoomScale ?? Infinity
2656
+ ] : [1, 1]
2657
+ );
2391
2658
  if (!panEnabled)
2392
2659
  zoomNode.filter((e) => e.type === "wheel" || e.type === "dblclick");
2393
2660
  asSvgElement(svg).call(zoomNode);
2661
+ state.zoomNode = zoomNode;
2394
2662
  const initialZoom = this.renderConfig.zoomLevel ?? 1;
2395
2663
  if (initialZoom !== 1) {
2396
2664
  asSvgElement(svg).call(
2397
2665
  zoomNode.transform,
2398
- d36.zoomIdentity.scale(initialZoom)
2666
+ d37.zoomIdentity.scale(initialZoom)
2399
2667
  );
2400
2668
  }
2401
2669
  }
@@ -2405,13 +2673,16 @@ var WellRenderer = class {
2405
2673
  0 && (module.exports = {
2406
2674
  DEFAULT_WELL_THEME,
2407
2675
  INTERACTIVE_RENDER_CONFIG,
2676
+ RENDER_LABELS,
2408
2677
  STATIC_RENDER_CONFIG,
2409
2678
  WellRenderer,
2679
+ applyRenderLocale,
2410
2680
  drawWellLegend,
2411
2681
  formatDiameter,
2412
2682
  formatLength,
2413
2683
  getDiameterUnit,
2414
2684
  getLengthUnit,
2415
- importFgdcTextures
2685
+ importFgdcTextures,
2686
+ resolveRenderLabel
2416
2687
  });
2417
2688
  //# sourceMappingURL=index.cjs.map