@welldot/render 0.2.3 → 0.3.0-next.20261014.26249fd

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
@@ -330,10 +330,13 @@ __export(index_exports, {
330
330
  WellRenderer: () => WellRenderer,
331
331
  applyRenderLocale: () => applyRenderLocale,
332
332
  drawWellLegend: () => drawWellLegend,
333
+ drawWellLegendSymbol: () => drawWellLegendSymbol,
333
334
  formatDiameter: () => formatDiameter,
334
335
  formatLength: () => formatLength,
335
336
  getDiameterUnit: () => getDiameterUnit,
336
337
  getLengthUnit: () => getLengthUnit,
338
+ getProfileTopDepth: () => getProfileTopDepth,
339
+ getWellLegendItems: () => getWellLegendItems,
337
340
  importFgdcTextures: () => importFgdcTextures,
338
341
  resolveRenderLabel: () => resolveRenderLabel
339
342
  });
@@ -343,6 +346,7 @@ module.exports = __toCommonJS(index_exports);
343
346
  var import_d3 = require("d3");
344
347
 
345
348
  // src/utils/format.utils.ts
349
+ var import_core = require("@welldot/core");
346
350
  var import_utils = require("@welldot/utils");
347
351
  var MM_TO_INCHES = 0.0393701;
348
352
  var M_TO_FT = 3.28084;
@@ -360,8 +364,7 @@ function getDiameterUnit(units, locale) {
360
364
  return locale === "en" ? "in." : '"';
361
365
  }
362
366
  function resolveRenderLabel(value, locale) {
363
- if (typeof value === "string") return value;
364
- return locale === "en" && value.en ? value.en : value.pt;
367
+ return (0, import_core.resolveLanguageText)(value, locale, "pt");
365
368
  }
366
369
 
367
370
  // src/configs/render.configs.ts
@@ -386,6 +389,8 @@ var RENDER_LABELS = {
386
389
  wellCase: { pt: "Revestimento", en: "Casing" },
387
390
  wellScreen: { pt: "Filtro", en: "Screen" },
388
391
  reduction: { pt: "Redu\xE7\xE3o", en: "Reduction" },
392
+ centralizer: { pt: "Centralizador", en: "Centralizer" },
393
+ pump: { pt: "Bomba e edutor", en: "Pump and riser" },
389
394
  cementPad: { pt: "Laje de cimento", en: "Cement pad" },
390
395
  conflict: { pt: "Conflito", en: "Conflict" }
391
396
  }
@@ -409,9 +414,38 @@ var RENDER_LABELS = {
409
414
  wellCase: { title: { pt: "REVESTIMENTO", en: "CASING" } },
410
415
  wellScreen: {
411
416
  title: { pt: "FILTROS", en: "SCREENS" },
412
- slot: { pt: "Ranhura:", en: "Slot:" }
417
+ slot: { pt: "Ranhura:", en: "Slot:" },
418
+ material: { pt: "Material:", en: "Material:" },
419
+ slotType: { pt: "Tipo:", en: "Type:" }
413
420
  },
414
421
  reduction: { title: { pt: "REDU\xC7\xC3O", en: "REDUCTION" } },
422
+ centralizer: {
423
+ title: { pt: "CENTRALIZADOR", en: "CENTRALIZER" },
424
+ depth: { pt: "Profundidade:", en: "Depth:" },
425
+ spacing: { pt: "Espa\xE7amento:", en: "Spacing:" }
426
+ },
427
+ pump: {
428
+ title: { pt: "BOMBA", en: "PUMP" },
429
+ intakeDepth: { pt: "Profundidade do crivo:", en: "Intake depth:" },
430
+ model: { pt: "Modelo:", en: "Model:" },
431
+ power: { pt: "Pot\xEAncia:", en: "Power:" },
432
+ riser: { pt: "Edutor:", en: "Riser:" },
433
+ type_submersible: { pt: "Bomba submersa", en: "Submersible pump" },
434
+ type_vertical_turbine: {
435
+ pt: "Bomba de eixo vertical",
436
+ en: "Vertical turbine pump"
437
+ },
438
+ type_jet: { pt: "Bomba injetora", en: "Jet pump" },
439
+ type_progressive_cavity: {
440
+ pt: "Bomba helicoidal",
441
+ en: "Progressive cavity pump"
442
+ },
443
+ type_hand_pump: { pt: "Bomba manual", en: "Hand pump" },
444
+ type_compressor_airlift: {
445
+ pt: "Compressor (air-lift)",
446
+ en: "Compressor (air-lift)"
447
+ }
448
+ },
415
449
  conflict: { title: { pt: "CONFLITO", en: "CONFLICT" } },
416
450
  fracture: {
417
451
  title: { pt: "FRATURA", en: "FRACTURE" },
@@ -437,6 +471,19 @@ var RENDER_LABELS = {
437
471
  fractureWater: { pt: "fratura aberta", en: "open fracture" },
438
472
  cave: { pt: "caverna", en: "cave" },
439
473
  caveWater: { pt: "caverna \xFAmida", en: "wet cave" }
474
+ },
475
+ columnHeaders: {
476
+ depth: { pt: "PROF.", en: "DEPTH" },
477
+ lithology: { pt: "LIT.", en: "LITH." },
478
+ construction: { pt: "CONSTRU\xC7\xC3O", en: "CONSTRUCTION" },
479
+ description: { pt: "DESCRI\xC7\xC3O GEOL\xD3GICA", en: "GEOLOGICAL DESCRIPTION" }
480
+ },
481
+ continuation: { prefix: { pt: "continua\xE7\xE3o", en: "continued" } },
482
+ overlap: {
483
+ label: {
484
+ pt: "{range} repetido da p\xE1g. anterior",
485
+ en: "{range} repeated from previous page"
486
+ }
440
487
  }
441
488
  };
442
489
  function resolveGroup(group, locale) {
@@ -457,6 +504,8 @@ function resolveTooltipLabels(locale) {
457
504
  wellCase: resolveGroup(t.wellCase, locale),
458
505
  wellScreen: resolveGroup(t.wellScreen, locale),
459
506
  reduction: resolveGroup(t.reduction, locale),
507
+ centralizer: resolveGroup(t.centralizer, locale),
508
+ pump: resolveGroup(t.pump, locale),
460
509
  conflict: resolveGroup(t.conflict, locale),
461
510
  fracture: resolveGroup(t.fracture, locale),
462
511
  cementPad: resolveGroup(t.cementPad, locale),
@@ -478,7 +527,19 @@ function applyRenderLocale(config, locale) {
478
527
  tooltipLabels: resolveTooltipLabels(locale),
479
528
  labels: {
480
529
  ...config.labels,
481
- typeLabels: resolveGroup(RENDER_LABELS.typeLabels, locale)
530
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, locale),
531
+ continuation: {
532
+ ...config.labels.continuation,
533
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, locale)
534
+ }
535
+ },
536
+ columnHeaders: {
537
+ ...config.columnHeaders,
538
+ labels: resolveGroup(RENDER_LABELS.columnHeaders, locale)
539
+ },
540
+ overlap: {
541
+ ...config.overlap,
542
+ label: resolveRenderLabel(RENDER_LABELS.overlap.label, locale)
482
543
  }
483
544
  };
484
545
  }
@@ -504,7 +565,7 @@ var DEFAULT_WELL_THEME = {
504
565
  crackStrokeWidth: 0.7
505
566
  }
506
567
  },
507
- cementPad: { stroke: "#303030", strokeWidth: 2 },
568
+ cementPad: { stroke: "#303030", strokeWidth: 1 },
508
569
  boreHole: {
509
570
  fill: "#ffffff",
510
571
  stroke: "#191919",
@@ -513,10 +574,24 @@ var DEFAULT_WELL_THEME = {
513
574
  strokeWidth: 1.2
514
575
  },
515
576
  surfaceCase: { stroke: "#000000", strokeWidth: 4 },
516
- holeFill: { stroke: "#303030", strokeWidth: 2 },
577
+ holeFill: { stroke: "#303030", strokeWidth: 1.2 },
517
578
  wellCase: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
518
- wellScreen: { stroke: "#303030", strokeWidth: 2 },
579
+ wellScreen: {
580
+ stroke: "#303030",
581
+ strokeWidth: 2,
582
+ endStrokeWidth: 0.75,
583
+ endDasharray: "3, 2"
584
+ },
519
585
  reduction: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
586
+ centralizer: { fill: "none", stroke: "#303030", strokeWidth: 1.4 },
587
+ pump: {
588
+ fill: "#5b6b7a",
589
+ stroke: "#202020",
590
+ strokeWidth: 1.2,
591
+ riserFill: "#a3adb7",
592
+ riserStroke: "#2f3a44",
593
+ riserStrokeWidth: 1
594
+ },
520
595
  conflict: { stroke: "#e52117", strokeWidth: 4 },
521
596
  unitLabels: {
522
597
  geologicFill: "#f0f0f0",
@@ -552,6 +627,39 @@ var DEFAULT_WELL_THEME = {
552
627
  headerFontWeight: 600,
553
628
  bodyFontWeight: 400
554
629
  },
630
+ overview: {
631
+ stroke: "#303030",
632
+ strokeWidth: 0.5,
633
+ emptyFill: "#d9d9d9",
634
+ windowStroke: "#c0392b",
635
+ windowStrokeWidth: 1.2
636
+ },
637
+ columnHeaders: {
638
+ fontSize: 6.5,
639
+ color: "#303030",
640
+ fontFamily: "sans-serif",
641
+ fontWeight: 600,
642
+ ruleStroke: "#101010",
643
+ ruleStrokeWidth: 1
644
+ },
645
+ markers: {
646
+ fill: "#ffffff",
647
+ stroke: "#101010",
648
+ strokeWidth: 0.6,
649
+ color: "#101010",
650
+ fontSize: 6,
651
+ fontFamily: "sans-serif",
652
+ fontWeight: 600
653
+ },
654
+ overlap: {
655
+ stroke: "#9a9a9a",
656
+ strokeWidth: 0.6,
657
+ size: 6,
658
+ ruleStroke: "#9a9a9a",
659
+ labelColor: "#555555",
660
+ fontSize: 6.5,
661
+ fontFamily: "monospace"
662
+ },
555
663
  legend: {
556
664
  borderStrokeWidth: 0.8,
557
665
  fractureStrokeWidth: 1.5,
@@ -609,8 +717,17 @@ var STATIC_RENDER_CONFIG = {
609
717
  }
610
718
  },
611
719
  construction: {
612
- cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
613
- surfaceCase: { diameterPaddingRatio: 0.1 }
720
+ cementPad: { widthRatio: 1.4, height: 6 },
721
+ casingShoe: { active: true, size: 4 },
722
+ screenEnds: { active: true },
723
+ surfaceCase: { diameterPaddingRatio: 0.1 },
724
+ centralizer: { markerHalfHeight: 4 },
725
+ pump: {
726
+ active: true,
727
+ bodyHeight: 22,
728
+ bodyWidthRatio: 0.6,
729
+ riserWidthRatio: 0.3
730
+ }
614
731
  },
615
732
  tooltipLabels: resolveTooltipLabels("pt"),
616
733
  labels: {
@@ -623,7 +740,11 @@ var STATIC_RENDER_CONFIG = {
623
740
  stackingLineHeight: 10,
624
741
  stackingGap: 0,
625
742
  fractureLabelLeaderGap: 5,
626
- lithology: true
743
+ lithology: true,
744
+ continuation: {
745
+ active: false,
746
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, "pt")
747
+ }
627
748
  },
628
749
  unitLabels: {
629
750
  active: true,
@@ -681,10 +802,22 @@ var STATIC_RENDER_CONFIG = {
681
802
  wellCase: "Revestimento",
682
803
  wellScreen: "Filtro",
683
804
  reduction: "Redu\xE7\xE3o",
805
+ centralizer: "Centralizador",
806
+ pump: "Bomba e edutor",
684
807
  cementPad: "Laje de cimento",
685
808
  conflict: "Conflito"
686
809
  }
687
810
  },
811
+ overview: { active: false, x: 4, width: 8, height: 200 },
812
+ columnHeaders: {
813
+ active: false,
814
+ height: 14,
815
+ labels: resolveGroup(RENDER_LABELS.columnHeaders, "pt")
816
+ },
817
+ markers: { lithology: false, fractures: false, radius: 4.5 },
818
+ overlap: {
819
+ label: resolveRenderLabel(RENDER_LABELS.overlap.label, "pt")
820
+ },
688
821
  highlights: DEFAULT_HIGHLIGHT_CONFIG
689
822
  };
690
823
  var INTERACTIVE_RENDER_CONFIG = {
@@ -720,8 +853,17 @@ var INTERACTIVE_RENDER_CONFIG = {
720
853
  }
721
854
  },
722
855
  construction: {
723
- cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
724
- surfaceCase: { diameterPaddingRatio: 0.1 }
856
+ cementPad: { widthRatio: 1.4, height: 6 },
857
+ casingShoe: { active: true, size: 4 },
858
+ screenEnds: { active: true },
859
+ surfaceCase: { diameterPaddingRatio: 0.1 },
860
+ centralizer: { markerHalfHeight: 4 },
861
+ pump: {
862
+ active: true,
863
+ bodyHeight: 22,
864
+ bodyWidthRatio: 0.6,
865
+ riserWidthRatio: 0.3
866
+ }
725
867
  },
726
868
  tooltipLabels: resolveTooltipLabels("pt"),
727
869
  labels: {
@@ -732,7 +874,11 @@ var INTERACTIVE_RENDER_CONFIG = {
732
874
  descriptionXOffset: 78,
733
875
  descriptionMaxWidth: 226,
734
876
  stackingLineHeight: 11,
735
- stackingGap: 0
877
+ stackingGap: 0,
878
+ continuation: {
879
+ active: false,
880
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, "pt")
881
+ }
736
882
  },
737
883
  unitLabels: {
738
884
  active: false,
@@ -789,10 +935,22 @@ var INTERACTIVE_RENDER_CONFIG = {
789
935
  wellCase: "Revestimento",
790
936
  wellScreen: "Filtro",
791
937
  reduction: "Redu\xE7\xE3o",
938
+ centralizer: "Centralizador",
939
+ pump: "Bomba e edutor",
792
940
  cementPad: "Laje de cimento",
793
941
  conflict: "Conflito"
794
942
  }
795
943
  },
944
+ overview: { active: false, x: 4, width: 8, height: 200 },
945
+ columnHeaders: {
946
+ active: false,
947
+ height: 14,
948
+ labels: resolveGroup(RENDER_LABELS.columnHeaders, "pt")
949
+ },
950
+ markers: { lithology: false, fractures: false, radius: 4.5 },
951
+ overlap: {
952
+ label: resolveRenderLabel(RENDER_LABELS.overlap.label, "pt")
953
+ },
796
954
  highlights: DEFAULT_HIGHLIGHT_CONFIG
797
955
  };
798
956
 
@@ -809,6 +967,8 @@ var import_defu2 = require("defu");
809
967
 
810
968
  // src/configs/render.textures.ts
811
969
  var import_textures = __toESM(require("textures"), 1);
970
+ var dot = (x, y, r) => `M${x - r},${y}a${r},${r} 0 1,0 ${2 * r},0a${r},${r} 0 1,0 ${-2 * r},0Z`;
971
+ var concreteAggregate = (s) => `M${s * 0.08},${s * 0.42}l${s * 0.3},0l${-s * 0.12},${-s * 0.26}ZM${s * 0.55},${s * 0.92}l${s * 0.26},${-s * 0.06}l${-s * 0.16},${-s * 0.2}Z${dot(s * 0.72, s * 0.24, s * 0.06)}${dot(s * 0.22, s * 0.78, s * 0.05)}`;
812
972
  function createWellTextures(cfg) {
813
973
  const pad = {
814
974
  background: "#ffffff",
@@ -856,7 +1016,9 @@ function createWellTextures(cfg) {
856
1016
  ...cfg?.surface_case
857
1017
  };
858
1018
  return {
859
- pad: import_textures.default.lines().heavier(10).thinner(1.5).stroke(pad.stroke).background(pad.background),
1019
+ // Concrete: aggregate (small triangles and dots) on a light ground, so it
1020
+ // reads apart from the seal's diagonals and the gravel pack's circles.
1021
+ pad: import_textures.default.paths().d(concreteAggregate).size(6).strokeWidth(0.4).stroke(pad.stroke).fill(pad.stroke).background(pad.background),
860
1022
  conflict: import_textures.default.lines().heavier().stroke(conflict.stroke),
861
1023
  cave_dry: import_textures.default.lines().size(cave_dry.size).orientation("6/8").heavier(0.3).thinner(0.8).background(cave_dry.background).stroke(cave_dry.stroke),
862
1024
  cave_wet: import_textures.default.lines().size(cave_wet.size).orientation("6/8").heavier(0.3).thinner(0.8).background(cave_wet.background).stroke(cave_wet.stroke),
@@ -868,8 +1030,8 @@ function createWellTextures(cfg) {
868
1030
  }
869
1031
 
870
1032
  // src/Renderer.ts
871
- var import_core = require("@welldot/core");
872
- var import_utils6 = require("@welldot/utils");
1033
+ var import_core4 = require("@welldot/core");
1034
+ var import_utils10 = require("@welldot/utils");
873
1035
 
874
1036
  // src/configs/render.classnames.ts
875
1037
  var DEFAULT_COMPONENTS_CLASS_NAMES = {
@@ -927,6 +1089,14 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
927
1089
  group: "well-screen",
928
1090
  rect: "well-screen-rect"
929
1091
  },
1092
+ centralizer: {
1093
+ group: "centralizer",
1094
+ item: "centralizer-marker"
1095
+ },
1096
+ pump: {
1097
+ group: "pump",
1098
+ item: "pump-item"
1099
+ },
930
1100
  reduction: {
931
1101
  group: "reduction",
932
1102
  item: "reduction-poly"
@@ -958,6 +1128,26 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
958
1128
  caveFill: "cave-fill",
959
1129
  constructionRect: "construction-rect"
960
1130
  },
1131
+ overview: {
1132
+ group: "wp-overview",
1133
+ item: "wp-overview-item",
1134
+ window: "wp-overview-window"
1135
+ },
1136
+ columnHeaders: {
1137
+ group: "wp-column-headers",
1138
+ text: "wp-column-header",
1139
+ rule: "wp-column-headers-rule"
1140
+ },
1141
+ markers: {
1142
+ group: "wp-markers",
1143
+ lithology: "wp-marker-lithology",
1144
+ fracture: "wp-marker-fracture"
1145
+ },
1146
+ overlap: {
1147
+ group: "wp-overlap",
1148
+ band: "wp-overlap-band",
1149
+ label: "wp-overlap-label"
1150
+ },
961
1151
  highlights: {
962
1152
  geologicGroup: "wp-highlights-geologic",
963
1153
  constructionGroup: "wp-highlights-construction",
@@ -972,6 +1162,156 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
972
1162
  // src/utils/render.utils.ts
973
1163
  var import_textures2 = __toESM(require("textures"), 1);
974
1164
 
1165
+ // src/utils/construction.geometry.ts
1166
+ var JOINT_EPS = 1e-3;
1167
+ function diameterAt(items, depth) {
1168
+ return items.find((i) => depth >= i.from && depth <= i.to)?.diameter;
1169
+ }
1170
+ function getIntervalEnds(item, string, depthFrom, depthTo) {
1171
+ return {
1172
+ cutTop: item.from < depthFrom - JOINT_EPS,
1173
+ cutBottom: item.to > depthTo + JOINT_EPS,
1174
+ joinedTop: string.some(
1175
+ (o) => o !== item && Math.abs(o.to - item.from) < JOINT_EPS
1176
+ ),
1177
+ joinedBottom: string.some(
1178
+ (o) => o !== item && Math.abs(o.from - item.to) < JOINT_EPS
1179
+ )
1180
+ };
1181
+ }
1182
+ function pipeWallsPath(xLeft, xRight, yTop, yBottom) {
1183
+ return `M${xLeft},${yTop}V${yBottom}M${xRight},${yTop}V${yBottom}`;
1184
+ }
1185
+ function shoePath(xLeft, xRight, y, size) {
1186
+ return `M${xLeft},${y - size}L${xLeft},${y}L${xLeft - size},${y}ZM${xRight},${y - size}L${xRight},${y}L${xRight + size},${y}Z`;
1187
+ }
1188
+ function screenEndPath(xLeft, xRight, y) {
1189
+ return `M${xLeft},${y}H${xRight}`;
1190
+ }
1191
+ var clampDepth = (s, depth) => Math.min(Math.max(depth, s.depthFrom), s.depthTo);
1192
+ var inPanel = (s, depth) => depth >= s.depthFrom - JOINT_EPS && depth <= s.depthTo + JOINT_EPS;
1193
+ var shoulders = (s, a, b, y) => `M${s.left(a)},${y}H${s.left(b)}M${s.right(a)},${y}H${s.right(b)}`;
1194
+ function boreHoleOutlinePath(item, holes, s) {
1195
+ const yTop = s.y(clampDepth(s, item.from));
1196
+ const yBottom = s.y(clampDepth(s, item.to));
1197
+ let d = pipeWallsPath(
1198
+ s.left(item.diameter),
1199
+ s.right(item.diameter),
1200
+ yTop,
1201
+ yBottom
1202
+ );
1203
+ if (item.from > JOINT_EPS && inPanel(s, item.from)) {
1204
+ const above = holes.find(
1205
+ (o) => o !== item && o.from < item.from && o.to >= item.from - JOINT_EPS
1206
+ );
1207
+ if (above && above.diameter < item.diameter)
1208
+ d += shoulders(s, above.diameter, item.diameter, yTop);
1209
+ }
1210
+ if (inPanel(s, item.to)) {
1211
+ const below = holes.find(
1212
+ (o) => o !== item && o.from <= item.to + JOINT_EPS && o.to > item.to
1213
+ );
1214
+ if (!below) {
1215
+ d += `M${s.left(item.diameter)},${yBottom}H${s.right(item.diameter)}`;
1216
+ } else if (below.diameter < item.diameter) {
1217
+ d += shoulders(s, item.diameter, below.diameter, yBottom);
1218
+ }
1219
+ }
1220
+ return d;
1221
+ }
1222
+ function holeFillOutlinePath(item, pipes, s) {
1223
+ const yTop = s.y(clampDepth(s, item.from));
1224
+ const yBottom = s.y(clampDepth(s, item.to));
1225
+ const xL = s.left(item.diameter);
1226
+ const xR = s.right(item.diameter);
1227
+ let d = pipeWallsPath(xL, xR, yTop, yBottom);
1228
+ const interfaces = [item.from, item.to].filter(
1229
+ (depth) => depth > JOINT_EPS && inPanel(s, depth)
1230
+ );
1231
+ for (const depth of interfaces) {
1232
+ const y = s.y(depth);
1233
+ const pipe = diameterAt(pipes, depth);
1234
+ if (pipe !== void 0 && pipe < item.diameter) {
1235
+ d += `M${xL},${y}H${s.left(pipe)}M${s.right(pipe)},${y}H${xR}`;
1236
+ } else {
1237
+ d += `M${xL},${y}H${xR}`;
1238
+ }
1239
+ }
1240
+ return d;
1241
+ }
1242
+ function pipeSlices(pipes, from, to) {
1243
+ const cuts = [.../* @__PURE__ */ new Set([from, to, ...pipes.flatMap((p) => [p.from, p.to])])].filter((d) => d >= from && d <= to).sort((a, b) => a - b);
1244
+ const slices = [];
1245
+ for (let i = 1; i < cuts.length; i++) {
1246
+ const a = cuts[i - 1];
1247
+ const b = cuts[i];
1248
+ const mid = (a + b) / 2;
1249
+ const widest = Math.max(
1250
+ 0,
1251
+ ...pipes.filter((p) => p.from <= mid && p.to >= mid).map((p) => p.diameter)
1252
+ );
1253
+ if (widest === 0) continue;
1254
+ const last = slices[slices.length - 1];
1255
+ if (last && last.to === a && last.diameter === widest) last.to = b;
1256
+ else slices.push({ from: a, to: b, diameter: widest });
1257
+ }
1258
+ return slices;
1259
+ }
1260
+ function annulusFillPath(item, pipes, s) {
1261
+ const from = clampDepth(s, item.from);
1262
+ const to = clampDepth(s, item.to);
1263
+ const box = (dia, a, b) => `M${s.left(dia)},${s.y(a)}H${s.right(dia)}V${s.y(b)}H${s.left(dia)}Z`;
1264
+ const inner = pipes.filter((p) => p.diameter < item.diameter);
1265
+ return box(item.diameter, from, to) + pipeSlices(inner, from, to).map((p) => box(p.diameter, p.from, p.to)).join("");
1266
+ }
1267
+ function getPadDiameters(data, surfaceCasePaddingRatio) {
1268
+ const surfaceCase = diameterAt(data.surface_case, 0);
1269
+ const paddedSurfaceCase = surfaceCase === void 0 ? void 0 : surfaceCase * (1 + surfaceCasePaddingRatio);
1270
+ const casing = diameterAt([...data.well_case, ...data.well_screen], 0);
1271
+ const hole = diameterAt(data.bore_hole, 0);
1272
+ const opening = casing ?? paddedSurfaceCase ?? hole ?? 0;
1273
+ const outer = Math.max(
1274
+ opening,
1275
+ paddedSurfaceCase ?? 0,
1276
+ hole ?? 0,
1277
+ diameterAt(data.hole_fill, 0) ?? 0
1278
+ );
1279
+ return { opening, outer };
1280
+ }
1281
+ function getCementPadGeometry(pocoWidth, pocoCenter, surfaceY, config, openingWidth = 0, outerWidth = openingWidth) {
1282
+ const h = config.height;
1283
+ const opening = Math.max(0, openingWidth);
1284
+ const width = Math.max(
1285
+ pocoWidth * config.widthRatio,
1286
+ opening + 3 * h,
1287
+ outerWidth + 2 * h
1288
+ );
1289
+ const xLeft = (pocoCenter - width) / 2;
1290
+ const xRight = xLeft + width;
1291
+ const oLeft = (pocoCenter - opening) / 2;
1292
+ const oRight = oLeft + opening;
1293
+ const yTop = surfaceY - h;
1294
+ const a = h * 0.3;
1295
+ const breakLine = (x, y0, y1, dir) => `L${x + dir * a},${y0 + (y1 - y0) * 0.4}L${x - dir * a},${y0 + (y1 - y0) * 0.6}L${x},${y1}`;
1296
+ const bbox = { x: xLeft, y: yTop, width, height: h };
1297
+ if (opening === 0) {
1298
+ return {
1299
+ bbox,
1300
+ slabs: [
1301
+ `M${xLeft},${yTop}H${xRight}${breakLine(xRight, yTop, surfaceY, 1)}H${xLeft}${breakLine(xLeft, surfaceY, yTop, -1)}Z`,
1302
+ ""
1303
+ ]
1304
+ };
1305
+ }
1306
+ return {
1307
+ bbox,
1308
+ slabs: [
1309
+ `M${oLeft},${yTop}H${xLeft}${breakLine(xLeft, yTop, surfaceY, -1)}H${oLeft}Z`,
1310
+ `M${oRight},${yTop}H${xRight}${breakLine(xRight, yTop, surfaceY, 1)}H${oRight}Z`
1311
+ ]
1312
+ };
1313
+ }
1314
+
975
1315
  // src/utils/d3.utils.ts
976
1316
  function mergeEnter(enter, update) {
977
1317
  return enter.merge(update);
@@ -1042,6 +1382,7 @@ var makeSeededPrng = (seed, scramble = 6271) => {
1042
1382
  // src/utils/tooltips.utils.ts
1043
1383
  var import_d3_tip = __toESM(require("d3-tip"), 1);
1044
1384
  var import_sanitize_html = __toESM(require("sanitize-html"), 1);
1385
+ var import_core2 = require("@welldot/core");
1045
1386
  var import_utils2 = require("@welldot/utils");
1046
1387
  var esc = (v) => (0, import_sanitize_html.default)(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
1047
1388
  var _tooltipCache = /* @__PURE__ */ new WeakMap();
@@ -1052,6 +1393,7 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1052
1393
  if (cached && cached.signature === signature) {
1053
1394
  return cached.tooltips;
1054
1395
  }
1396
+ const material = (value) => (0, import_core2.getVocabLabel)(import_core2.CONSTRUCTION_MATERIALS, value, locale);
1055
1397
  const tipsText = {
1056
1398
  geology: (_, d) => `
1057
1399
  <span class="${customClasses.tooltip.title}">${labels.geology.title}</span>
@@ -1084,14 +1426,15 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1084
1426
  <span class="${customClasses.tooltip.secondaryInfo}">
1085
1427
  <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}
1086
1428
  </span>
1087
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
1429
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(material(d.type))}</span>
1088
1430
  `,
1089
1431
  wellScreen: (_, d) => `
1090
1432
  <span class="${customClasses.tooltip.title}">${labels.wellScreen.title}</span>
1091
1433
  <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>
1092
1434
  <span class="${customClasses.tooltip.secondaryInfo}">
1093
1435
  <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>
1436
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.wellScreen.material}</strong> ${esc(material(d.type))}</span>
1437
+ ${d.slot_type ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.wellScreen.slotType}</strong> ${esc((0, import_core2.getVocabLabel)(import_core2.SCREEN_SLOT_TYPES, d.slot_type, locale))}</span>` : ""}
1095
1438
  <span class="${customClasses.tooltip.secondaryInfo}">
1096
1439
  <strong>${labels.wellScreen.slot}</strong> ${esc((0, import_utils2.formatNumber)(d.screen_slot, { maximumFractionDigits: 2, locale }))} mm
1097
1440
  </span>
@@ -1102,8 +1445,29 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1102
1445
  <span class="${customClasses.tooltip.secondaryInfo}">
1103
1446
  <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
1447
  </span>
1105
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
1448
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(material(d.type))}</span>
1106
1449
  `,
1450
+ centralizer: (_, d) => `
1451
+ <span class="${customClasses.tooltip.title}">${labels.centralizer.title}</span>
1452
+ <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.centralizer.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
1453
+ ${d.from !== d.to ? `<span class="${customClasses.tooltip.secondaryInfo}">${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>` : ""}
1454
+ ${d.spacing ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.centralizer.spacing}</strong> ${esc(formatLength(d.spacing, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>` : ""}
1455
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc((0, import_core2.getVocabLabel)(import_core2.CENTRALIZER_TYPES, d.type, locale))}</span>
1456
+ ${d.diameter ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>` : ""}
1457
+ ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
1458
+ `,
1459
+ pump: (_, d) => {
1460
+ const typeLabel = labels.pump[`type_${d.type}`] ?? d.type;
1461
+ const model = [d.manufacturer, d.model].filter(Boolean).join(" ");
1462
+ return `
1463
+ <span class="${customClasses.tooltip.title}">${labels.pump.title}</span>
1464
+ <span class="${customClasses.tooltip.primaryInfo}">${esc(typeLabel)}</span>
1465
+ ${d.intake_depth !== void 0 ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.pump.intakeDepth}</strong> ${esc(formatLength(d.intake_depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>` : ""}
1466
+ ${model ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.pump.model}</strong> ${esc(model)}</span>` : ""}
1467
+ ${d.rated_power !== void 0 ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.pump.power}</strong> ${esc((0, import_utils2.formatNumber)((0, import_core2.powerFromCanonical)(d.rated_power, units.power ?? "kW"), { maximumFractionDigits: 2, locale }))} ${esc(units.power ?? "kW")}</span>` : ""}
1468
+ ${d.riser_diameter !== void 0 ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.pump.riser}</strong> ${esc(formatDiameter(d.riser_diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}${d.riser_material ? ` \xB7 ${esc(material(d.riser_material))}` : ""}</span>` : ""}
1469
+ `;
1470
+ },
1107
1471
  conflict: (_, d) => `
1108
1472
  <span class="${customClasses.tooltip.title}">${labels.conflict.title}</span>
1109
1473
  <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>
@@ -1114,14 +1478,14 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1114
1478
  <span class="${customClasses.tooltip.title}">${title}</span>
1115
1479
  <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.fracture.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
1116
1480
  ${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>
1481
+ ${typeof d.dip === "number" ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.dip}</strong> ${esc(d.dip)}\xB0</span>` : ""}
1482
+ ${typeof d.azimuth === "number" ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.azimuth}</strong> ${esc(d.azimuth)}\xB0</span>` : ""}
1119
1483
  ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
1120
1484
  `;
1121
1485
  },
1122
1486
  cementPad: (_, d) => `
1123
1487
  <span class="${customClasses.tooltip.title}">${labels.cementPad.title}</span>
1124
- <span class="${customClasses.tooltip.primaryInfo}">${esc(d.type)}</span>
1488
+ <span class="${customClasses.tooltip.primaryInfo}">${esc((0, import_core2.getVocabLabel)(import_core2.CEMENT_PAD_TYPES, d.type, locale))}</span>
1125
1489
  <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cementPad.thickness}</strong>
1126
1490
  ${esc(formatLength(d.thickness, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
1127
1491
  <span class="${customClasses.tooltip.secondaryInfo}">
@@ -1159,6 +1523,17 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1159
1523
  };
1160
1524
 
1161
1525
  // src/utils/render.utils.ts
1526
+ var fractureDip = (dip) => typeof dip === "number" && Number.isFinite(dip) ? dip : 0;
1527
+ function getCementPadRect(pocoWidth, pocoCenter, surfaceY, config, openingWidth = 0, outerWidth = openingWidth) {
1528
+ return getCementPadGeometry(
1529
+ pocoWidth,
1530
+ pocoCenter,
1531
+ surfaceY,
1532
+ config,
1533
+ openingWidth,
1534
+ outerWidth
1535
+ ).bbox;
1536
+ }
1162
1537
  var _fgdcTextures = {};
1163
1538
  var _fgdcTexturesPromise = null;
1164
1539
  var preloadFgdcTextures = () => {
@@ -1267,14 +1642,23 @@ function drawAnnotationLabels(ctx, data) {
1267
1642
  }
1268
1643
  const items = [];
1269
1644
  if (active.has("lithology")) {
1645
+ const continuation = s.continuation;
1270
1646
  data.lithology.filter((d) => d.from < ctx.depthTo).forEach((d) => {
1647
+ const range = `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`;
1648
+ const continues = continuation?.active && d.from < ctx.depthFrom;
1271
1649
  items.push({
1272
1650
  sortDepth: d.from,
1273
1651
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
1274
1652
  originX: ctx.geoXRight,
1275
1653
  originY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
1276
- header: `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`,
1277
- description: d.description || ""
1654
+ header: continues ? `\u2191 ${continuation.prefix} \xB7 ${range}` : range,
1655
+ description: continues ? "" : d.description || "",
1656
+ ...show.has("units") ? {
1657
+ unit: d.geologic_unit || void 0,
1658
+ aquifer: continues ? void 0 : d.aquifer_unit || void 0
1659
+ } : {},
1660
+ ...show.has("swatch") && !continues ? { swatch: d } : {},
1661
+ ...show.has("rules") ? { rule: continues ? "dashed" : "solid" } : {}
1278
1662
  });
1279
1663
  });
1280
1664
  }
@@ -1287,7 +1671,8 @@ function drawAnnotationLabels(ctx, data) {
1287
1671
  originX: ctx.pocoCenterX,
1288
1672
  originY: ctx.yScale(d.depth),
1289
1673
  header: `${formatLength(d.depth, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
1290
- description: d.description || ""
1674
+ description: d.description || "",
1675
+ ...show.has("rules") ? { rule: "dashed" } : {}
1291
1676
  });
1292
1677
  });
1293
1678
  }
@@ -1307,14 +1692,15 @@ function drawAnnotationLabels(ctx, data) {
1307
1692
  }
1308
1693
  items.sort((a, b) => a.sortDepth - b.sortDepth);
1309
1694
  const labelPositions = [];
1310
- let currY = 0;
1695
+ let currY = ctx.repeatedTo != null ? s.stackingLineHeight + 2 : 0;
1311
1696
  const charsPerLine = Math.max(
1312
1697
  1,
1313
1698
  Math.floor(s.descriptionMaxWidth / (st.fontSize * 0.6))
1314
1699
  );
1315
1700
  items.forEach((item) => {
1316
- const descLines = Math.ceil((item.description.length || 1) / charsPerLine);
1317
- const estH = (1 + descLines) * s.stackingLineHeight + 4;
1701
+ const descLines = show.has("units") ? item.description ? wrapText(item.description, charsPerLine).length : 0 : Math.ceil((item.description.length || 1) / charsPerLine);
1702
+ const extraLines = (item.unit ? 1 : 0) + (item.aquifer ? 1 : 0);
1703
+ const estH = (1 + descLines + extraLines) * s.stackingLineHeight + 4;
1318
1704
  const yLabel = Math.max(item.baseY, currY + s.stackingGap);
1319
1705
  currY = yLabel + estH;
1320
1706
  labelPositions.push({ item, yLabel, estH });
@@ -1331,16 +1717,41 @@ function drawAnnotationLabels(ctx, data) {
1331
1717
  if (show.has("description")) {
1332
1718
  const lineH = s.stackingLineHeight;
1333
1719
  const bgFill = st.annotationBg ?? "white";
1720
+ const swatchFill = labelPositions.some((p) => p.item.swatch) ? getLithologyFill(data.lithology, ctx.svg, ctx.theme.lithologyTexture) : null;
1334
1721
  labelPositions.forEach(({ item, yLabel, estH }) => {
1335
1722
  const labelG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.label).attr("transform", `translate(${labelX},${yLabel})`);
1336
1723
  labelG.append("rect").attr("x", 0).attr("y", 0).attr("width", s.descriptionMaxWidth).attr("height", estH).attr("rx", st.annotationRadius ?? 2).attr("fill", bgFill).attr("fill-opacity", st.annotationBgOpacity ?? 0.85).attr("stroke", st.annotationBorderColor ?? "none");
1337
- labelG.append("text").attr("class", "wp-annotation-header").attr("fill", ctx.theme.labels.color).attr("font-weight", st.headerFontWeight ?? 600).attr("font-family", ctx.theme.labels.headerFont).attr("x", 0).attr("y", lineH * 0.85).attr("font-size", st.fontSize).text(item.header);
1724
+ if (item.rule) {
1725
+ labelG.append("line").attr("x1", 0).attr("x2", s.descriptionMaxWidth).attr("y1", 0).attr("y2", 0).attr("stroke", ctx.theme.labels.color).attr(
1726
+ "stroke-width",
1727
+ item.rule === "solid" ? 0.8 : ctx.theme.labels.dividerStrokeWidth
1728
+ ).attr(
1729
+ "stroke-dasharray",
1730
+ item.rule === "solid" ? null : ctx.theme.labels.dividerStrokeDasharray
1731
+ );
1732
+ }
1733
+ let headerX = 0;
1734
+ if (item.swatch && swatchFill) {
1735
+ const size = lineH * 0.75;
1736
+ labelG.append("rect").attr("x", 0).attr("y", lineH * 0.85 - size + 1).attr("width", size).attr("height", size).attr("fill", swatchFill(item.swatch)).attr("stroke", ctx.theme.lithology.stroke).attr("stroke-width", 0.5);
1737
+ headerX = size + 3;
1738
+ }
1739
+ labelG.append("text").attr("class", "wp-annotation-header").attr("fill", ctx.theme.labels.color).attr("font-weight", st.headerFontWeight ?? 600).attr("font-family", ctx.theme.labels.headerFont).attr("x", headerX).attr("y", lineH * 0.85).attr("font-size", st.fontSize).text(item.header);
1740
+ let lineY = lineH * 0.85 + lineH;
1741
+ if (item.unit) {
1742
+ labelG.append("text").attr("class", "wp-annotation-unit").attr("fill", ctx.theme.labels.color).attr("font-weight", st.headerFontWeight ?? 600).attr("font-family", ctx.theme.labels.bodyFont ?? "sans-serif").attr("x", 0).attr("y", lineY).attr("font-size", st.fontSize).text(item.unit);
1743
+ lineY += lineH;
1744
+ }
1338
1745
  if (item.description) {
1339
1746
  const lines = wrapText(item.description, charsPerLine);
1340
- const bodyEl = labelG.append("text").attr("class", "wp-annotation-body").attr("fill", ctx.theme.labels.bodyColor).attr("font-weight", st.bodyFontWeight ?? 400).attr("font-family", ctx.theme.labels.bodyFont ?? "sans-serif").attr("x", 0).attr("y", lineH * 0.85 + lineH).attr("font-size", st.fontSize);
1747
+ const bodyEl = labelG.append("text").attr("class", "wp-annotation-body").attr("fill", ctx.theme.labels.bodyColor).attr("font-weight", st.bodyFontWeight ?? 400).attr("font-family", ctx.theme.labels.bodyFont ?? "sans-serif").attr("x", 0).attr("y", lineY).attr("font-size", st.fontSize);
1341
1748
  lines.forEach((line, i) => {
1342
1749
  bodyEl.append("tspan").attr("x", 0).attr("dy", i === 0 ? 0 : lineH).text(line);
1343
1750
  });
1751
+ lineY += lines.length * lineH;
1752
+ }
1753
+ if (item.aquifer) {
1754
+ labelG.append("text").attr("class", "wp-annotation-aquifer").attr("fill", ctx.theme.labels.bodyColor).attr("font-style", "italic").attr("font-family", ctx.theme.labels.bodyFont ?? "sans-serif").attr("x", 0).attr("y", lineY).attr("font-size", st.fontSize).text(item.aquifer);
1344
1755
  }
1345
1756
  });
1346
1757
  }
@@ -1361,7 +1772,6 @@ var makePointKey = (type) => (d, i) => {
1361
1772
 
1362
1773
  // src/renderers/caves.renderer.ts
1363
1774
  function drawCaves(ctx, data) {
1364
- if (!data.length) return;
1365
1775
  const {
1366
1776
  groups: { cavesGroup },
1367
1777
  geoXLeft,
@@ -1371,7 +1781,7 @@ function drawCaves(ctx, data) {
1371
1781
  yScale,
1372
1782
  renderConfig,
1373
1783
  theme,
1374
- textures: textures3,
1784
+ textures: textures4,
1375
1785
  classes,
1376
1786
  tooltips
1377
1787
  } = ctx;
@@ -1408,7 +1818,7 @@ function drawCaves(ctx, data) {
1408
1818
  const botPath = botCut ? `M ${geoXLeft.toFixed(1)},${yBot.toFixed(1)} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath(botPts);
1409
1819
  const botReversed = botCut ? `L ${geoXLeft.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath([...botPts].reverse(), { omitMoveTo: true });
1410
1820
  const closedPath = `${topPath} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}${botReversed} L ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} Z`;
1411
- const caveTexture = cave.water_intake ? textures3.cave_wet : textures3.cave_dry;
1821
+ const caveTexture = cave.water_intake ? textures4.cave_wet : textures4.cave_dry;
1412
1822
  const caveStroke = cave.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
1413
1823
  g.append("path").attr("class", classes.caves.fill).attr("d", closedPath).attr("fill", caveTexture.url()).attr("fill-opacity", theme.cave.fillOpacity).attr("stroke", "none");
1414
1824
  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");
@@ -1417,9 +1827,148 @@ function drawCaves(ctx, data) {
1417
1827
  merged.transition(ctx.transition).style("opacity", 1);
1418
1828
  }
1419
1829
 
1420
- // src/renderers/construction-labels.renderer.ts
1830
+ // src/renderers/centralizers.renderer.ts
1421
1831
  var d32 = __toESM(require("d3"), 1);
1422
1832
  var import_utils3 = require("@welldot/utils");
1833
+ function drawCentralizers(ctx, data) {
1834
+ const group = ctx.groups.centralizerGroup;
1835
+ const cls = ctx.classes.centralizer;
1836
+ const maxXValue = d32.max((0, import_utils3.getProfileDiamValues)(ctx.constructionData)) || 0;
1837
+ const xScale = d32.scaleLinear().domain([0, maxXValue]).range([0, ctx.POCO_WIDTH]);
1838
+ const pipes = [
1839
+ ...ctx.constructionData.well_case,
1840
+ ...ctx.constructionData.well_screen
1841
+ ];
1842
+ const holes = ctx.constructionData.bore_hole;
1843
+ const markers = (data.centralizers ?? []).flatMap(
1844
+ (c) => (0, import_utils3.getCentralizerDepths)(c).filter((depth) => depth >= ctx.depthFrom && depth < ctx.depthTo).map((depth) => ({ ...c, depth }))
1845
+ );
1846
+ const halfH = ctx.renderConfig.construction.centralizer.markerHalfHeight;
1847
+ const bowPoints = (m, side) => {
1848
+ const outerD = m.diameter ?? diameterAt(holes, m.depth) ?? maxXValue;
1849
+ const pipeD = diameterAt(pipes, m.depth) ?? outerD * 0.75;
1850
+ const pipeX = (ctx.POCO_CENTER + side * xScale(pipeD)) / 2;
1851
+ const outerX = (ctx.POCO_CENTER + side * xScale(outerD)) / 2;
1852
+ const y = ctx.yScale(m.depth);
1853
+ return `${pipeX},${y - halfH} ${outerX},${y} ${pipeX},${y + halfH}`;
1854
+ };
1855
+ const selection = group.selectAll(`g.${cls.item}`).data(markers, (d) => `${d.from}:${d.to}:${d.depth}`);
1856
+ selection.exit().remove();
1857
+ const entered = selection.enter().append("g").attr("class", cls.item).on("mouseover", ctx.tooltips.centralizer.show).on("mouseout", ctx.tooltips.centralizer.hide);
1858
+ entered.append("polyline").attr("class", `${cls.item}-left`);
1859
+ entered.append("polyline").attr("class", `${cls.item}-right`);
1860
+ const merged = entered.merge(selection);
1861
+ merged.selectAll("polyline").attr("fill", ctx.theme.centralizer.fill).attr("stroke", ctx.theme.centralizer.stroke).attr("stroke-width", ctx.theme.centralizer.strokeWidth).attr("stroke-linejoin", "round");
1862
+ merged.each((d, i, nodes) => {
1863
+ const g = d32.select(nodes[i]);
1864
+ g.select(`.${cls.item}-left`).attr("points", bowPoints(d, -1));
1865
+ g.select(`.${cls.item}-right`).attr("points", bowPoints(d, 1));
1866
+ });
1867
+ }
1868
+
1869
+ // src/utils/layout.utils.ts
1870
+ function getProfileTopDepth(profile) {
1871
+ return Math.min(
1872
+ 0,
1873
+ ...[
1874
+ ...profile.surface_case ?? [],
1875
+ ...profile.well_case ?? [],
1876
+ ...profile.well_screen ?? []
1877
+ ].map((i) => i.from).filter(Number.isFinite)
1878
+ );
1879
+ }
1880
+ function resolvePanelWindows(panels, totalDepth, topDepth = 0) {
1881
+ if (panels.length > 0 && panels.every((p) => p.depthRange)) {
1882
+ return panels.map((p) => [p.depthRange[0], p.depthRange[1]]);
1883
+ }
1884
+ if (panels.length === 1) return [[topDepth, totalDepth]];
1885
+ const totalHeight = panels.reduce((s, p) => s + p.height, 0) || 1;
1886
+ const metersPerPx = (totalDepth - topDepth) / totalHeight;
1887
+ let current = topDepth;
1888
+ return panels.map((p) => {
1889
+ const from = current;
1890
+ current = from + p.height * metersPerPx;
1891
+ return [from, current];
1892
+ });
1893
+ }
1894
+ function getColumnHeaderPositions(geometry) {
1895
+ const { marginLeft, geoXLeft, geoXRight, pocoCenterX, descriptionX } = geometry;
1896
+ return {
1897
+ depth: marginLeft - 2,
1898
+ lithology: marginLeft + (geoXLeft + geoXRight) / 2,
1899
+ construction: marginLeft + pocoCenterX,
1900
+ ...descriptionX != null ? { description: marginLeft + descriptionX } : {}
1901
+ };
1902
+ }
1903
+ function getLithologyMarkers(lithology, depthFrom, depthTo) {
1904
+ const markers = [];
1905
+ lithology.forEach((layer, i) => {
1906
+ const from = Math.max(layer.from, depthFrom);
1907
+ const to = Math.min(layer.to, depthTo);
1908
+ if (to > from) markers.push({ index: i + 1, from, to });
1909
+ });
1910
+ return markers;
1911
+ }
1912
+ function getFractureMarkers(fractures, depthFrom, depthTo) {
1913
+ const markers = [];
1914
+ fractures.forEach((f, i) => {
1915
+ if (f.depth >= depthFrom && f.depth < depthTo) {
1916
+ markers.push({ label: `F${i + 1}`, depth: f.depth });
1917
+ }
1918
+ });
1919
+ return markers;
1920
+ }
1921
+ function getOverviewGeometry(totalDepth, height, depthFrom, depthTo) {
1922
+ const ratio = totalDepth > 0 ? height / totalDepth : 0;
1923
+ const y = (depth) => Math.min(height, Math.max(0, depth * ratio));
1924
+ return {
1925
+ y,
1926
+ window: { y: y(depthFrom), height: Math.max(1, y(depthTo) - y(depthFrom)) }
1927
+ };
1928
+ }
1929
+ function formatOverlapLabel(template, range) {
1930
+ return template.replace("{range}", range);
1931
+ }
1932
+
1933
+ // src/renderers/column-headers.renderer.ts
1934
+ function hasDescriptionColumn(ctx) {
1935
+ const { active, lithology } = ctx.renderConfig.labels;
1936
+ if (active === false) return false;
1937
+ if (Array.isArray(active) && !active.includes("lithology")) return false;
1938
+ if (lithology === false) return false;
1939
+ return !Array.isArray(lithology) || lithology.includes("description");
1940
+ }
1941
+ function drawColumnHeaders(ctx) {
1942
+ const group = ctx.groups.columnHeadersGroup;
1943
+ group.selectAll("*").remove();
1944
+ const cfg = ctx.renderConfig.columnHeaders;
1945
+ if (!cfg.active) return;
1946
+ const theme = ctx.theme.columnHeaders;
1947
+ const { margins } = ctx.state;
1948
+ const positions = getColumnHeaderPositions({
1949
+ marginLeft: margins.left,
1950
+ geoXLeft: ctx.geoXLeft,
1951
+ geoXRight: ctx.geoXRight,
1952
+ pocoCenterX: ctx.pocoCenterX,
1953
+ descriptionX: hasDescriptionColumn(ctx) ? ctx.geoXRight + ctx.renderConfig.labels.descriptionXOffset : void 0
1954
+ });
1955
+ const ruleY = margins.top - 2 - ctx.renderConfig.construction.cementPad.height;
1956
+ const textY = ruleY - 4;
1957
+ const addText = (x, text, anchor) => group.append("text").attr("class", ctx.classes.columnHeaders.text).attr("x", x).attr("y", textY).attr("text-anchor", anchor).attr("font-size", theme.fontSize).attr("font-family", theme.fontFamily ?? "sans-serif").attr("font-weight", theme.fontWeight ?? 600).attr("fill", theme.color).text(text);
1958
+ addText(positions.depth, cfg.labels.depth, "end");
1959
+ addText(positions.lithology, cfg.labels.lithology, "middle");
1960
+ addText(positions.construction, cfg.labels.construction, "middle");
1961
+ if (positions.description != null) {
1962
+ addText(positions.description, cfg.labels.description, "start");
1963
+ }
1964
+ const overview = ctx.renderConfig.overview;
1965
+ group.append("line").attr("class", ctx.classes.columnHeaders.rule).attr("x1", overview.active ? overview.x : 0).attr("x2", ctx.svgWidth).attr("y1", ruleY).attr("y2", ruleY).attr("stroke", theme.ruleStroke).attr("stroke-width", theme.ruleStrokeWidth);
1966
+ }
1967
+
1968
+ // src/renderers/construction-labels.renderer.ts
1969
+ var d33 = __toESM(require("d3"), 1);
1970
+ var import_core3 = require("@welldot/core");
1971
+ var import_utils4 = require("@welldot/utils");
1423
1972
  function drawConstructionLabels(ctx, data) {
1424
1973
  if (!ctx.renderConfig.constructionLabels.active) return;
1425
1974
  ctx.groups.constructionLabelsGroup.selectAll("*").remove();
@@ -1427,16 +1976,18 @@ function drawConstructionLabels(ctx, data) {
1427
1976
  const clct = ctx.theme.constructionLabels;
1428
1977
  const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter, ctx.locale);
1429
1978
  const dUnit = getDiameterUnit(ctx.units.diameter, ctx.locale);
1430
- const maxXValues = (0, import_utils3.getProfileDiamValues)(ctx.constructionData);
1431
- const xScale = d32.scaleLinear().domain([0, d32.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
1979
+ const material = (value) => (0, import_core3.getVocabLabel)(import_core3.CONSTRUCTION_MATERIALS, value, ctx.locale);
1980
+ const maxXValues = (0, import_utils4.getProfileDiamValues)(ctx.constructionData);
1981
+ const xScale = d33.scaleLinear().domain([0, d33.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
1432
1982
  const occupied = [];
1433
- const placeLabel = (d, text) => {
1983
+ const onRight = clc.side === "right";
1984
+ const continuation = ctx.renderConfig.labels.continuation;
1985
+ const placeLabel = (d, label) => {
1434
1986
  const clampedFrom = Math.max(d.from, ctx.depthFrom);
1435
1987
  const clampedTo = Math.min(d.to, ctx.depthTo);
1436
1988
  if (clampedFrom >= clampedTo) return;
1989
+ const text = continuation?.active && d.from < ctx.depthFrom ? `\u2191 ${label}` : label;
1437
1990
  const midY = ctx.yScale((clampedFrom + clampedTo) / 2);
1438
- const leftEdgeX = (ctx.POCO_CENTER - xScale(d.diameter)) / 2;
1439
- const labelRightX = leftEdgeX - clc.xOffset;
1440
1991
  const charW = clct.fontSize * 0.52;
1441
1992
  const lineH = clct.fontSize * 1.35;
1442
1993
  const vertPad = 6;
@@ -1453,16 +2004,17 @@ function drawConstructionLabels(ctx, data) {
1453
2004
  lines = [text];
1454
2005
  boxW = text.length * charW + 8;
1455
2006
  }
2007
+ const labelRightX = onRight ? (ctx.POCO_CENTER + ctx.POCO_WIDTH) / 2 + clc.xOffset + boxW : (ctx.POCO_CENTER - xScale(d.diameter)) / 2 - clc.xOffset;
1456
2008
  const labelH = lines.length * lineH + vertPad;
1457
- let labelY = midY - labelH / 2;
2009
+ let labelY = onRight ? ctx.yScale(clampedFrom) + 1 : midY - labelH / 2;
1458
2010
  for (const pos of occupied) {
1459
2011
  if (labelY < pos.bot && labelY + labelH > pos.top) labelY = pos.bot + 2;
1460
2012
  }
1461
2013
  occupied.push({ top: labelY, bot: labelY + labelH });
1462
2014
  ctx.groups.constructionLabelsGroup.append("rect").attr("x", labelRightX - boxW).attr("y", labelY).attr("width", boxW).attr("height", labelH).attr("rx", clc.labelRadius).attr("fill", clct.labelFill ?? "white").attr("stroke", clct.labelColor ?? "#303030").attr("stroke-width", 0.5);
1463
- const textEl = ctx.groups.constructionLabelsGroup.append("text").attr("x", labelRightX - 4).attr("y", labelY + lineH * 0.85).attr("font-size", clct.fontSize).attr("font-family", clct.fontFamily ?? "sans-serif").attr("font-weight", clct.fontWeight ?? 400).attr("text-anchor", "end").attr("fill", clct.labelColor ?? "#303030");
2015
+ const textEl = ctx.groups.constructionLabelsGroup.append("text").attr("x", onRight ? labelRightX - boxW + 4 : labelRightX - 4).attr("y", labelY + lineH * 0.85).attr("font-size", clct.fontSize).attr("font-family", clct.fontFamily ?? "sans-serif").attr("font-weight", clct.fontWeight ?? 400).attr("text-anchor", onRight ? "start" : "end").attr("fill", clct.labelColor ?? "#303030");
1464
2016
  lines.forEach((line, i) => {
1465
- textEl.append("tspan").attr("x", labelRightX - 4).attr("dy", i === 0 ? 0 : lineH).text(line);
2017
+ textEl.append("tspan").attr("x", onRight ? labelRightX - boxW + 4 : labelRightX - 4).attr("dy", i === 0 ? 0 : lineH).text(line);
1466
2018
  });
1467
2019
  };
1468
2020
  let lastDiam = 0;
@@ -1475,7 +2027,7 @@ function drawConstructionLabels(ctx, data) {
1475
2027
  }).forEach(
1476
2028
  (d) => placeLabel(
1477
2029
  d,
1478
- `${clc.labels.wellCasePrefix} ${fmtD(d.diameter)}${dUnit} ${d.type}`
2030
+ `${clc.labels.wellCasePrefix} ${fmtD(d.diameter)}${dUnit} ${material(d.type)}`
1479
2031
  )
1480
2032
  );
1481
2033
  lastDiam = 0;
@@ -1488,122 +2040,319 @@ function drawConstructionLabels(ctx, data) {
1488
2040
  }).forEach(
1489
2041
  (d) => placeLabel(
1490
2042
  d,
1491
- `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${d.type} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot)}${dUnit}`
2043
+ `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${material(d.type)} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot)}${dUnit}`
1492
2044
  )
1493
2045
  );
1494
2046
  }
1495
2047
 
1496
2048
  // src/renderers/construction.renderer.ts
1497
- var d33 = __toESM(require("d3"), 1);
1498
- var import_utils4 = require("@welldot/utils");
2049
+ var d34 = __toESM(require("d3"), 1);
2050
+ var import_utils5 = require("@welldot/utils");
2051
+ var entering = /* @__PURE__ */ new WeakSet();
2052
+ function constructionXScale(ctx) {
2053
+ const max6 = d34.max((0, import_utils5.getProfileDiamValues)(ctx.constructionData)) || 0;
2054
+ return d34.scaleLinear().domain([0, max6]).range([0, ctx.POCO_WIDTH]);
2055
+ }
2056
+ function outlineScales(ctx) {
2057
+ const x = constructionXScale(ctx);
2058
+ return {
2059
+ y: ctx.yScale,
2060
+ left: (d) => (ctx.POCO_CENTER - x(d)) / 2,
2061
+ right: (d) => (ctx.POCO_CENTER + x(d)) / 2,
2062
+ depthFrom: ctx.depthFrom,
2063
+ depthTo: ctx.depthTo
2064
+ };
2065
+ }
2066
+ var surfaceCaseDiameter = (ctx, diameter) => diameter * (1 + ctx.renderConfig.construction.surfaceCase.diameterPaddingRatio);
2067
+ function joinIntervalLayer(ctx, group, cls, data, key, tooltip, { shoe = false, pathFill = false, ends = false } = {}) {
2068
+ const sel = group.selectAll(`g.${cls}`).data(data, makeIntervalKey(key));
2069
+ sel.exit().transition(ctx.transition).style("opacity", 0).remove();
2070
+ const top = (d) => ctx.yScale(Math.max(d.from, ctx.depthFrom));
2071
+ const entered = sel.enter().append("g").attr("class", cls).each((_d, i, nodes) => entering.add(nodes[i])).on("mouseover", tooltip.show).on("mouseout", tooltip.hide);
2072
+ if (pathFill)
2073
+ entered.append("path").attr("class", `${cls}-fill`).attr("stroke", "none").attr("fill-rule", "evenodd").attr("d", "");
2074
+ else
2075
+ entered.append("rect").attr("class", `${cls}-fill`).attr("stroke", "none").attr("y", top).attr("height", 0);
2076
+ entered.append("path").attr("class", `${cls}-outline`).attr("fill", "none").attr("d", "");
2077
+ if (shoe)
2078
+ entered.append("path").attr("class", `${cls}-shoe`).attr("stroke-linejoin", "round").attr("d", "");
2079
+ if (ends)
2080
+ entered.append("path").attr("class", `${cls}-ends`).attr("fill", "none").attr("d", "");
2081
+ return entered.merge(sel);
2082
+ }
2083
+ function drawCementPad(ctx, pad) {
2084
+ const cls = ctx.classes.cementPad.item;
2085
+ const visible = !!pad?.thickness && ctx.depthFrom <= 0 && ctx.depthTo > 0;
2086
+ const sel = ctx.groups.cementPadGroup.selectAll(`g.${cls}`).data(visible ? [pad] : [], () => "cement_pad");
2087
+ sel.exit().remove();
2088
+ if (!visible) return;
2089
+ const entered = sel.enter().append("g").attr("class", cls).on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
2090
+ entered.append("path").attr("class", `${cls}-slab`);
2091
+ entered.append("path").attr("class", `${cls}-slab`);
2092
+ const x = constructionXScale(ctx);
2093
+ const { opening, outer } = getPadDiameters(
2094
+ ctx.constructionData,
2095
+ ctx.renderConfig.construction.surfaceCase.diameterPaddingRatio
2096
+ );
2097
+ const { slabs } = getCementPadGeometry(
2098
+ ctx.POCO_WIDTH,
2099
+ ctx.POCO_CENTER,
2100
+ ctx.yScale(0),
2101
+ ctx.renderConfig.construction.cementPad,
2102
+ x(opening),
2103
+ x(outer)
2104
+ );
2105
+ entered.merge(sel).selectAll("path").attr("d", (_d, i) => slabs[i] ?? "").attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth).attr("stroke-linejoin", "round");
2106
+ }
2107
+ function layoutConstruction(ctx, animate = false) {
2108
+ const c = ctx.classes;
2109
+ const scales = outlineScales(ctx);
2110
+ const data = ctx.constructionData;
2111
+ const motion = (sel) => animate ? withTransition(sel, ctx.transition) : sel;
2112
+ const yTop = (d) => ctx.yScale(Math.max(d.from, ctx.depthFrom));
2113
+ const yBottom = (d) => ctx.yScale(Math.min(d.to, ctx.depthTo));
2114
+ const collapsed = (d) => ({
2115
+ ...scales,
2116
+ y: () => yTop(d)
2117
+ });
2118
+ const shapeParts = (items, part, attr, shape) => {
2119
+ const parts = items.select(part);
2120
+ if (animate)
2121
+ parts.filter(function() {
2122
+ return entering.has(this.parentNode);
2123
+ }).attr(attr, (d) => shape(d, collapsed(d)));
2124
+ motion(parts).attr(attr, (d) => shape(d, scales));
2125
+ };
2126
+ const layoutLayer = (group, cls, diameter, outline, {
2127
+ shoe: shoe2,
2128
+ ends,
2129
+ fill
2130
+ } = {}) => {
2131
+ const items = group.selectAll(`g.${cls}`);
2132
+ shapeParts(items, `.${cls}-outline`, "d", outline);
2133
+ if (shoe2) shapeParts(items, `.${cls}-shoe`, "d", shoe2);
2134
+ if (ends) shapeParts(items, `.${cls}-ends`, "d", ends);
2135
+ if (fill) {
2136
+ shapeParts(items, `.${cls}-fill`, "d", fill);
2137
+ return;
2138
+ }
2139
+ motion(
2140
+ items.select(`.${cls}-fill`).attr("x", (d) => scales.left(diameter(d))).attr(
2141
+ "width",
2142
+ (d) => scales.right(diameter(d)) - scales.left(diameter(d))
2143
+ )
2144
+ ).attr("y", yTop).attr("height", (d) => Math.max(0, yBottom(d) - yTop(d)));
2145
+ };
2146
+ const walls = (d, s, dia = d.diameter) => pipeWallsPath(
2147
+ s.left(dia),
2148
+ s.right(dia),
2149
+ s.y(Math.max(d.from, s.depthFrom)),
2150
+ s.y(Math.min(d.to, s.depthTo))
2151
+ );
2152
+ layoutLayer(
2153
+ ctx.groups.holeGroup,
2154
+ c.boreHole.rect,
2155
+ (d) => d.diameter,
2156
+ (d, s) => boreHoleOutlinePath(d, data.bore_hole, s)
2157
+ );
2158
+ const pipes = [...data.well_case, ...data.well_screen];
2159
+ const insideSurface = [...data.bore_hole, ...data.hole_fill, ...pipes];
2160
+ const scDiameter = (d) => surfaceCaseDiameter(ctx, d.diameter);
2161
+ layoutLayer(
2162
+ ctx.groups.surfaceCaseGroup,
2163
+ c.surfaceCase.rect,
2164
+ scDiameter,
2165
+ (d, s) => walls(d, s, scDiameter(d)),
2166
+ // Only the ring outside the hole: the texture never shows under the casing.
2167
+ {
2168
+ fill: (d, s) => annulusFillPath({ ...d, diameter: scDiameter(d) }, insideSurface, s)
2169
+ }
2170
+ );
2171
+ layoutLayer(
2172
+ ctx.groups.holeFillGroup,
2173
+ c.holeFill.rect,
2174
+ (d) => d.diameter,
2175
+ (d, s) => holeFillOutlinePath(d, pipes, s),
2176
+ { fill: (d, s) => annulusFillPath(d, pipes, s) }
2177
+ );
2178
+ const string = [...pipes, ...data.reduction ?? []];
2179
+ const shoeCfg = ctx.renderConfig.construction.casingShoe;
2180
+ const endsCfg = ctx.renderConfig.construction.screenEnds;
2181
+ const room = (d, depth, size) => {
2182
+ const hole = diameterAt(data.bore_hole, depth);
2183
+ const annulus = hole !== void 0 && hole > d.diameter ? scales.left(d.diameter) - scales.left(hole) - 1 : size;
2184
+ return Math.max(2, Math.min(size, annulus));
2185
+ };
2186
+ const shoe = (d, s) => {
2187
+ const ends = getIntervalEnds(d, string, ctx.depthFrom, ctx.depthTo);
2188
+ if (!shoeCfg.active || ends.cutBottom || ends.joinedBottom) return "";
2189
+ return shoePath(
2190
+ s.left(d.diameter),
2191
+ s.right(d.diameter),
2192
+ s.y(Math.min(d.to, s.depthTo)),
2193
+ room(d, d.to, shoeCfg.size)
2194
+ );
2195
+ };
2196
+ const screenEnds = (d, s) => {
2197
+ if (!endsCfg.active) return "";
2198
+ const ends = getIntervalEnds(d, string, ctx.depthFrom, ctx.depthTo);
2199
+ const mark = (depth) => screenEndPath(s.left(d.diameter), s.right(d.diameter), s.y(depth));
2200
+ return (ends.cutTop ? "" : mark(d.from)) + (ends.cutBottom ? "" : mark(d.to));
2201
+ };
2202
+ layoutLayer(
2203
+ ctx.groups.wellCaseGroup,
2204
+ c.wellCase.rect,
2205
+ (d) => d.diameter,
2206
+ walls,
2207
+ { shoe }
2208
+ );
2209
+ layoutLayer(
2210
+ ctx.groups.wellScreenGroup,
2211
+ c.wellScreen.rect,
2212
+ (d) => d.diameter,
2213
+ walls,
2214
+ { shoe, ends: screenEnds }
2215
+ );
2216
+ const reductions = ctx.groups.reductionGroup.selectAll(`g.${c.reduction.item}`);
2217
+ const corners = (d, s) => {
2218
+ const t = s.y(Math.max(d.from, s.depthFrom));
2219
+ const b = s.y(Math.min(d.to, s.depthTo));
2220
+ return {
2221
+ tl: `${s.left(d.diam_from)},${t}`,
2222
+ tr: `${s.right(d.diam_from)},${t}`,
2223
+ bl: `${s.left(d.diam_to)},${b}`,
2224
+ br: `${s.right(d.diam_to)},${b}`
2225
+ };
2226
+ };
2227
+ shapeParts(reductions, `.${c.reduction.item}-fill`, "points", (d, s) => {
2228
+ const p = corners(d, s);
2229
+ return `${p.tl} ${p.tr} ${p.br} ${p.bl}`;
2230
+ });
2231
+ shapeParts(reductions, `.${c.reduction.item}-outline`, "d", (d, s) => {
2232
+ const p = corners(d, s);
2233
+ return `M${p.tl}L${p.bl}M${p.tr}L${p.br}`;
2234
+ });
2235
+ if (animate) {
2236
+ for (const group of [
2237
+ ctx.groups.holeGroup,
2238
+ ctx.groups.surfaceCaseGroup,
2239
+ ctx.groups.holeFillGroup,
2240
+ ctx.groups.wellCaseGroup,
2241
+ ctx.groups.wellScreenGroup,
2242
+ ctx.groups.reductionGroup
2243
+ ])
2244
+ group.selectAll("g").each(function() {
2245
+ entering.delete(this);
2246
+ });
2247
+ }
2248
+ }
1499
2249
  function drawConstructive(ctx, data) {
1500
2250
  const { getHeight, getYPos } = getYAxisFunctions(ctx.yScale, {
1501
2251
  from: ctx.depthFrom,
1502
2252
  to: ctx.depthTo
1503
2253
  });
1504
- const maxXValues = (0, import_utils4.getProfileDiamValues)(ctx.constructionData);
1505
- const maxXValueConstruction = d33.max(maxXValues) || 0;
1506
- const xScale = d33.scaleLinear().domain([0, maxXValueConstruction]).range([0, ctx.POCO_WIDTH]);
1507
- const rcc = ctx.renderConfig.construction;
1508
- const yPos = getYPos;
1509
- ctx.groups.constructionGroup.selectAll(`.${ctx.classes.cementPad.item}`).remove();
1510
- if (data.cement_pad && data.cement_pad.thickness && ctx.depthFrom === 0) {
1511
- const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad], () => "cement_pad");
1512
- cementPad.exit().remove();
1513
- const newCementPad = cementPad.enter().append("rect").attr("class", ctx.classes.cementPad.item).attr(
1514
- "x",
1515
- (d) => (ctx.POCO_CENTER - xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)) / 2
1516
- ).attr("y", (d) => {
1517
- return ctx.yScale(0) - ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1518
- }).attr(
1519
- "width",
1520
- (d) => xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)
1521
- ).attr("height", (d) => {
1522
- return ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1523
- }).attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth);
1524
- newCementPad.on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1525
- }
1526
- const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, makeIntervalKey("bore_hole"));
1527
- hole.exit().transition(ctx.transition).attr("height", 0).remove();
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"));
1532
- surfaceCaseGs.exit().transition(ctx.transition).style("opacity", 0).remove();
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);
1535
- newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1536
- newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1537
- const mergedSC = mergeEnter(newSC, surfaceCaseGs);
1538
- mergedSC.each((d, i, nodes) => {
1539
- const x = (ctx.POCO_CENTER - xScale(
1540
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1541
- )) / 2;
1542
- const w = xScale(
1543
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
2254
+ const c = ctx.classes;
2255
+ const theme = ctx.theme;
2256
+ drawCementPad(ctx, data.cement_pad);
2257
+ joinIntervalLayer(
2258
+ ctx,
2259
+ ctx.groups.holeGroup,
2260
+ c.boreHole.rect,
2261
+ data.bore_hole,
2262
+ "bore_hole",
2263
+ ctx.tooltips.hole
2264
+ ).attr("opacity", theme.boreHole.opacity).call((g) => {
2265
+ g.select(`.${c.boreHole.rect}-fill`).attr("fill", theme.boreHole.fill);
2266
+ g.select(`.${c.boreHole.rect}-outline`).attr("stroke", theme.boreHole.stroke).attr("stroke-width", theme.boreHole.strokeWidth).attr("stroke-dasharray", theme.boreHole.strokeDasharray);
2267
+ });
2268
+ joinIntervalLayer(
2269
+ ctx,
2270
+ ctx.groups.surfaceCaseGroup,
2271
+ c.surfaceCase.rect,
2272
+ data.surface_case,
2273
+ "surface_case",
2274
+ ctx.tooltips.surfaceCase,
2275
+ { pathFill: true }
2276
+ ).call((g) => {
2277
+ g.select(`.${c.surfaceCase.rect}-fill`).attr(
2278
+ "fill",
2279
+ ctx.textures.surface_case.url()
1544
2280
  );
1545
- const g = d33.select(nodes[i]);
1546
- g.select(".surface-case-fill").attr("x", x).attr("width", w).attr("fill", ctx.textures.surface_case.url());
1547
- const sideNodes = g.selectAll(".surface-case-side").nodes();
1548
- d33.select(sideNodes[0]).attr("x1", x).attr("x2", x);
1549
- d33.select(sideNodes[1]).attr("x1", x + w).attr("x2", x + w);
2281
+ g.select(`.${c.surfaceCase.rect}-outline`).attr("stroke", theme.surfaceCase.stroke).attr("stroke-width", theme.surfaceCase.strokeWidth);
1550
2282
  });
1551
- withTransition(
1552
- mergedSC.select(".surface-case-fill"),
1553
- ctx.transition
1554
- ).attr("y", (d) => getYPos(d)).attr("height", (d) => getHeight(d));
1555
- mergedSC.selectAll(".surface-case-side").attr("y1", (d) => getYPos(d)).attr(
1556
- "y2",
1557
- (d) => getYPos(d) + getHeight(d)
1558
- );
1559
- const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, makeIntervalKey("hole_fill"));
1560
- holeFill.exit().transition(ctx.transition).attr("height", 0).remove();
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"));
1565
- wellCase.exit().transition(ctx.transition).attr("height", 0).remove();
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"));
1570
- wellScreen.exit().transition(ctx.transition).attr("height", 0).remove();
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"));
2283
+ joinIntervalLayer(
2284
+ ctx,
2285
+ ctx.groups.holeFillGroup,
2286
+ c.holeFill.rect,
2287
+ data.hole_fill,
2288
+ "hole_fill",
2289
+ ctx.tooltips.holeFill,
2290
+ { pathFill: true }
2291
+ ).call((g) => {
2292
+ g.select(`.${c.holeFill.rect}-fill`).attr(
2293
+ "fill",
2294
+ (d) => ctx.textures[d.type].url()
2295
+ );
2296
+ g.select(`.${c.holeFill.rect}-outline`).attr("stroke", theme.holeFill.stroke).attr("stroke-width", theme.holeFill.strokeWidth);
2297
+ });
2298
+ for (const [group, cls, items, key, tooltip, fill, stroke] of [
2299
+ [
2300
+ ctx.groups.wellCaseGroup,
2301
+ c.wellCase.rect,
2302
+ data.well_case,
2303
+ "well_case",
2304
+ ctx.tooltips.wellCase,
2305
+ theme.wellCase.fill,
2306
+ theme.wellCase
2307
+ ],
2308
+ [
2309
+ ctx.groups.wellScreenGroup,
2310
+ c.wellScreen.rect,
2311
+ data.well_screen,
2312
+ "well_screen",
2313
+ ctx.tooltips.wellScreen,
2314
+ ctx.textures.well_screen.url(),
2315
+ theme.wellScreen
2316
+ ]
2317
+ ]) {
2318
+ joinIntervalLayer(ctx, group, cls, items, key, tooltip, {
2319
+ shoe: true,
2320
+ ends: cls === c.wellScreen.rect
2321
+ }).call((g) => {
2322
+ g.select(`.${cls}-fill`).attr("fill", fill);
2323
+ g.select(`.${cls}-outline`).attr("stroke", stroke.stroke).attr("stroke-width", stroke.strokeWidth);
2324
+ g.select(`.${cls}-shoe`).attr("fill", stroke.stroke).attr("stroke", stroke.stroke).attr("stroke-width", 0.5);
2325
+ g.select(`.${cls}-ends`).attr("stroke", stroke.stroke).attr("stroke-width", theme.wellScreen.endStrokeWidth).attr("stroke-dasharray", theme.wellScreen.endDasharray);
2326
+ });
2327
+ }
2328
+ const reduction = ctx.groups.reductionGroup.selectAll(`g.${c.reduction.item}`).data(data.reduction ?? [], makeIntervalKey("reduction"));
1582
2329
  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))
2330
+ const newReduction = reduction.enter().append("g").attr("class", c.reduction.item).each((_d, i, nodes) => entering.add(nodes[i])).on("mouseover", ctx.tooltips.reduction.show).on("mouseout", ctx.tooltips.reduction.hide);
2331
+ newReduction.append("polygon").attr("class", `${c.reduction.item}-fill`).attr("stroke", "none");
2332
+ newReduction.append("path").attr("class", `${c.reduction.item}-outline`).attr("fill", "none");
2333
+ newReduction.merge(reduction).call((g) => {
2334
+ g.select(`.${c.reduction.item}-fill`).attr("fill", theme.reduction.fill);
2335
+ g.select(`.${c.reduction.item}-outline`).attr("stroke", theme.reduction.stroke).attr("stroke-width", theme.reduction.strokeWidth);
2336
+ });
2337
+ const mergedConflicts = mergeConflicts(
2338
+ [
2339
+ ...getConflictAreas(data.well_case, data.well_screen),
2340
+ ...getConflictAreas(data.well_screen, data.well_case)
2341
+ ],
2342
+ 1
1593
2343
  );
1594
- const conflictAreas = [];
1595
- conflictAreas.push(...getConflictAreas(data.well_case, data.well_screen));
1596
- conflictAreas.push(...getConflictAreas(data.well_screen, data.well_case));
1597
- const mergedConflicts = mergeConflicts(conflictAreas, 1);
1598
- const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts, makeIntervalKey("conflict"));
2344
+ const xScale = constructionXScale(ctx);
2345
+ const yPos = getYPos;
2346
+ const conflict = ctx.groups.conflictGroup.selectAll(`.${c.conflict.rect}`).data(mergedConflicts, makeIntervalKey("conflict"));
1599
2347
  conflict.exit().transition(ctx.transition).attr("height", 0).remove();
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);
2348
+ const newConflict = conflict.enter().append("rect").attr("class", c.conflict.rect).attr("fill", ctx.textures.conflict.url()).attr("stroke", theme.conflict.stroke).attr("stroke-width", theme.conflict.strokeWidth).on("mouseover", ctx.tooltips.conflict.show).on("mouseout", ctx.tooltips.conflict.hide).attr("y", yPos).attr("height", 0);
1601
2349
  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);
2350
+ withTransition(mergedConflict, ctx.transition).attr("y", getYPos).attr("height", getHeight);
2351
+ layoutConstruction(ctx, true);
1603
2352
  }
1604
2353
 
1605
2354
  // src/renderers/fractures.renderer.ts
1606
- var d34 = __toESM(require("d3"), 1);
2355
+ var d35 = __toESM(require("d3"), 1);
1607
2356
  function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1608
2357
  const pts = [];
1609
2358
  for (let i = 0; i < steps; i++) {
@@ -1615,7 +2364,6 @@ function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1615
2364
  return pts;
1616
2365
  }
1617
2366
  function drawFractures(ctx, data) {
1618
- if (!data.length) return;
1619
2367
  const {
1620
2368
  groups: { fracturesGroup },
1621
2369
  POCO_WIDTH,
@@ -1637,10 +2385,10 @@ function drawFractures(ctx, data) {
1637
2385
  const merged = mergeEnter(entered, groups);
1638
2386
  merged.attr(
1639
2387
  "transform",
1640
- (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fracture.dip},${pocoCenterX},0)`
2388
+ (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fractureDip(fracture.dip)},${pocoCenterX},0)`
1641
2389
  );
1642
2390
  merged.each(function(fracture) {
1643
- const g = d34.select(this);
2391
+ const g = d35.select(this);
1644
2392
  g.selectAll("*").remove();
1645
2393
  const rng = makeSeededPrng(fracture.depth, 7919);
1646
2394
  const hitBuffer = fracture.swarm ? rf.hitBuffer.swarm : rf.hitBuffer.single;
@@ -1719,8 +2467,8 @@ function drawFractures(ctx, data) {
1719
2467
  }
1720
2468
 
1721
2469
  // src/renderers/highlights.renderer.ts
1722
- var d35 = __toESM(require("d3"), 1);
1723
- var import_utils5 = require("@welldot/utils");
2470
+ var d36 = __toESM(require("d3"), 1);
2471
+ var import_utils6 = require("@welldot/utils");
1724
2472
  function drawHighlightItem(ctx, group, x, y, w, h, label) {
1725
2473
  if (h <= 0 || w <= 0) return;
1726
2474
  const hlConfig = ctx.renderConfig.highlights;
@@ -1779,27 +2527,29 @@ function drawHighlights(ctx, hl) {
1779
2527
  h.label
1780
2528
  );
1781
2529
  });
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]);
2530
+ const maxXVals = (0, import_utils6.getProfileDiamValues)(ctx.constructionData);
2531
+ const xScaleConstr = d36.scaleLinear().domain([0, d36.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1784
2532
  const constructionConfig = ctx.renderConfig.construction;
1785
- if (hl.cement_pad && ctx.depthFrom === 0 && ctx.constructionData.cement_pad?.thickness) {
1786
- const cp = ctx.constructionData.cement_pad;
1787
- const cpPadWidth = cp.width * constructionConfig.cementPad.widthMultiplier * 1e3 / 2;
1788
- const cpX = (ctx.POCO_CENTER - xScaleConstr(cpPadWidth)) / 2;
1789
- const cpY = ctx.yScale(0) - ctx.yScale(
1790
- cp.thickness * constructionConfig.cementPad.thicknessMultiplier
2533
+ if (hl.cement_pad && ctx.depthFrom <= 0 && ctx.depthTo > 0 && ctx.constructionData.cement_pad?.thickness) {
2534
+ const { opening, outer } = getPadDiameters(
2535
+ ctx.constructionData,
2536
+ constructionConfig.surfaceCase.diameterPaddingRatio
1791
2537
  );
1792
- const cpW = xScaleConstr(cpPadWidth);
1793
- const cpH = ctx.yScale(
1794
- cp.thickness * constructionConfig.cementPad.thicknessMultiplier
2538
+ const pad = getCementPadRect(
2539
+ ctx.POCO_WIDTH,
2540
+ ctx.POCO_CENTER,
2541
+ ctx.yScale(0),
2542
+ constructionConfig.cementPad,
2543
+ xScaleConstr(opening),
2544
+ xScaleConstr(outer)
1795
2545
  );
1796
2546
  drawHighlightItem(
1797
2547
  ctx,
1798
2548
  ctx.groups.highlightsConstructionGroup,
1799
- cpX,
1800
- cpY,
1801
- cpW,
1802
- cpH,
2549
+ pad.x,
2550
+ pad.y,
2551
+ pad.width,
2552
+ pad.height,
1803
2553
  hl.cement_pad.label
1804
2554
  );
1805
2555
  }
@@ -1875,8 +2625,12 @@ function drawHighlights(ctx, hl) {
1875
2625
  // src/renderers/legend.renderer.ts
1876
2626
  var d3module = __toESM(require("d3"), 1);
1877
2627
  var import_defu = require("defu");
1878
- var d36 = { ...d3module };
1879
- function drawWellLegend(selector, profile, options = {}) {
2628
+ var import_utils7 = require("@welldot/utils");
2629
+ var d37 = { ...d3module };
2630
+ var SYMBOL_WIDTH = 24;
2631
+ var SYMBOL_HEIGHT = 12;
2632
+ var SYMBOL_MARGIN = 2;
2633
+ function getWellLegendItems(profile, labels = INTERACTIVE_RENDER_CONFIG.legend.labels) {
1880
2634
  const fractures = profile.fractures ?? [];
1881
2635
  const caves = profile.caves ?? [];
1882
2636
  const boreHoles = profile.bore_hole ?? [];
@@ -1896,6 +2650,8 @@ function drawWellLegend(selector, profile, options = {}) {
1896
2650
  const hasWellCase = wellCases.length > 0;
1897
2651
  const hasWellScreen = wellScreens.length > 0;
1898
2652
  const hasReduction = reductions.length > 0;
2653
+ const hasCentralizer = (profile.centralizers ?? []).length > 0;
2654
+ const hasPump = (0, import_utils7.getCurrentPump)(profile)?.intake_depth !== void 0;
1899
2655
  const hasCementPad = !!profile.cement_pad?.thickness;
1900
2656
  const hasConflict = mergeConflicts(
1901
2657
  [
@@ -1904,197 +2660,314 @@ function drawWellLegend(selector, profile, options = {}) {
1904
2660
  ],
1905
2661
  1
1906
2662
  ).length > 0;
1907
- const cfg = (0, import_defu.defu)(
1908
- options.config ?? {},
1909
- INTERACTIVE_RENDER_CONFIG.legend
1910
- );
1911
- const theme = (0, import_defu.defu)(options.theme ?? {}, DEFAULT_WELL_THEME);
1912
- const cls = options.classNames ? { ...DEFAULT_COMPONENTS_CLASS_NAMES.legend, ...options.classNames } : DEFAULT_COMPONENTS_CLASS_NAMES.legend;
1913
2663
  const items = [];
1914
2664
  if (hasSimple)
1915
2665
  items.push({
1916
2666
  kind: "fracture",
1917
- label: cfg.labels.fractureSingle,
2667
+ key: "fractureSingle",
2668
+ label: labels.fractureSingle,
1918
2669
  swarm: false,
1919
2670
  water_intake: false
1920
2671
  });
1921
2672
  if (hasSwarm)
1922
2673
  items.push({
1923
2674
  kind: "fracture",
1924
- label: cfg.labels.fractureSwarm,
2675
+ key: "fractureSwarm",
2676
+ label: labels.fractureSwarm,
1925
2677
  swarm: true,
1926
2678
  water_intake: false
1927
2679
  });
1928
2680
  if (hasWaterFx)
1929
2681
  items.push({
1930
2682
  kind: "fracture",
1931
- label: cfg.labels.fractureWater,
2683
+ key: "fractureWater",
2684
+ label: labels.fractureWater,
1932
2685
  swarm: false,
1933
2686
  water_intake: true
1934
2687
  });
1935
2688
  if (hasCaveDry)
1936
2689
  items.push({
1937
2690
  kind: "cave",
1938
- label: cfg.labels.caveDry,
2691
+ key: "caveDry",
2692
+ label: labels.caveDry,
1939
2693
  water_intake: false
1940
2694
  });
1941
2695
  if (hasCaveWet)
1942
- items.push({ kind: "cave", label: cfg.labels.caveWet, water_intake: true });
2696
+ items.push({
2697
+ kind: "cave",
2698
+ key: "caveWet",
2699
+ label: labels.caveWet,
2700
+ water_intake: true
2701
+ });
1943
2702
  if (hasCementPad)
1944
2703
  items.push({
1945
2704
  kind: "construction",
1946
- label: cfg.labels.cementPad,
2705
+ key: "cementPad",
2706
+ label: labels.cementPad,
1947
2707
  subKind: "cementPad"
1948
2708
  });
1949
2709
  if (hasBoreHole)
1950
2710
  items.push({
1951
2711
  kind: "construction",
1952
- label: cfg.labels.boreHole,
2712
+ key: "boreHole",
2713
+ label: labels.boreHole,
1953
2714
  subKind: "boreHole"
1954
2715
  });
1955
2716
  if (hasSurfaceCase)
1956
2717
  items.push({
1957
2718
  kind: "construction",
1958
- label: cfg.labels.surfaceCase,
2719
+ key: "surfaceCase",
2720
+ label: labels.surfaceCase,
1959
2721
  subKind: "surfaceCase"
1960
2722
  });
1961
2723
  if (hasHoleFillGravel)
1962
2724
  items.push({
1963
2725
  kind: "construction",
1964
- label: cfg.labels.holeFillGravel,
2726
+ key: "holeFillGravel",
2727
+ label: labels.holeFillGravel,
1965
2728
  subKind: "holeFillGravel"
1966
2729
  });
1967
2730
  if (hasHoleFillSeal)
1968
2731
  items.push({
1969
2732
  kind: "construction",
1970
- label: cfg.labels.holeFillSeal,
2733
+ key: "holeFillSeal",
2734
+ label: labels.holeFillSeal,
1971
2735
  subKind: "holeFillSeal"
1972
2736
  });
1973
2737
  if (hasWellCase)
1974
2738
  items.push({
1975
2739
  kind: "construction",
1976
- label: cfg.labels.wellCase,
2740
+ key: "wellCase",
2741
+ label: labels.wellCase,
1977
2742
  subKind: "wellCase"
1978
2743
  });
1979
2744
  if (hasWellScreen)
1980
2745
  items.push({
1981
2746
  kind: "construction",
1982
- label: cfg.labels.wellScreen,
2747
+ key: "wellScreen",
2748
+ label: labels.wellScreen,
1983
2749
  subKind: "wellScreen"
1984
2750
  });
1985
2751
  if (hasReduction)
1986
2752
  items.push({
1987
2753
  kind: "construction",
1988
- label: cfg.labels.reduction,
2754
+ key: "reduction",
2755
+ label: labels.reduction,
1989
2756
  subKind: "reduction"
1990
2757
  });
2758
+ if (hasCentralizer)
2759
+ items.push({
2760
+ kind: "construction",
2761
+ key: "centralizer",
2762
+ label: labels.centralizer,
2763
+ subKind: "centralizer"
2764
+ });
2765
+ if (hasPump)
2766
+ items.push({
2767
+ kind: "construction",
2768
+ key: "pump",
2769
+ label: labels.pump,
2770
+ subKind: "pump"
2771
+ });
1991
2772
  if (hasConflict)
1992
2773
  items.push({
1993
2774
  kind: "construction",
1994
- label: cfg.labels.conflict,
2775
+ key: "conflict",
2776
+ label: labels.conflict,
1995
2777
  subKind: "conflict"
1996
2778
  });
1997
- const svgEl = d36.select(selector);
2779
+ return items;
2780
+ }
2781
+ function drawLegendSymbol(symG, item, cx, rowSymY, { theme, cls, textures: legendTextures }) {
2782
+ const RC = "round";
2783
+ const rw = SYMBOL_WIDTH;
2784
+ const rh = SYMBOL_HEIGHT;
2785
+ if (item.kind === "fracture") {
2786
+ const color = item.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
2787
+ if (item.swarm) {
2788
+ [-4, 0, 4].forEach((offset) => {
2789
+ symG.append("polyline").attr("class", cls.fracturePoly).attr(
2790
+ "points",
2791
+ `${cx},${rowSymY + offset} ${cx + 6},${rowSymY + offset - 1} ${cx + 12},${rowSymY + offset + 1} ${cx + 18},${rowSymY + offset - 0.5} ${cx + 24},${rowSymY + offset}`
2792
+ ).attr("stroke", color).attr(
2793
+ "stroke-width",
2794
+ offset !== 0 ? theme.legend.fractureSideStrokeWidth : theme.legend.fractureStrokeWidth
2795
+ ).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2796
+ });
2797
+ } else {
2798
+ symG.append("polyline").attr("class", cls.fracturePoly).attr(
2799
+ "points",
2800
+ `${cx},${rowSymY} ${cx + 5},${rowSymY - 1.5} ${cx + 11},${rowSymY + 1} ${cx + 17},${rowSymY - 0.5} ${cx + 24},${rowSymY}`
2801
+ ).attr("stroke", color).attr("stroke-width", theme.legend.fractureStrokeWidth).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2802
+ }
2803
+ } else if (item.kind === "cave") {
2804
+ const color = item.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
2805
+ const texture = item.water_intake ? legendTextures.cave_wet : legendTextures.cave_dry;
2806
+ symG.append("rect").attr("class", cls.caveFill).attr("x", cx).attr("y", rowSymY - rh / 2).attr("width", rw).attr("height", rh).attr("fill", texture.url()).attr("stroke", color).attr("stroke-width", theme.legend.itemStrokeWidth);
2807
+ } else if (item.subKind === "surfaceCase") {
2808
+ 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");
2809
+ 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);
2810
+ 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);
2811
+ } else if (item.subKind === "centralizer") {
2812
+ const pipeW = rw * 0.4;
2813
+ const pipeL = cx + (rw - pipeW) / 2;
2814
+ const pipeR = pipeL + pipeW;
2815
+ const y1 = rowSymY - rh / 2;
2816
+ const y2 = rowSymY + rh / 2;
2817
+ const bowH = rh * 0.3;
2818
+ for (const x of [pipeL, pipeR]) {
2819
+ symG.append("line").attr("class", cls.constructionRect).attr("x1", x).attr("x2", x).attr("y1", y1).attr("y2", y2).attr("stroke", theme.wellCase.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
2820
+ }
2821
+ for (const [from, to] of [
2822
+ [pipeL, cx],
2823
+ [pipeR, cx + rw]
2824
+ ]) {
2825
+ symG.append("polyline").attr("class", cls.constructionRect).attr(
2826
+ "points",
2827
+ `${from},${rowSymY - bowH} ${to},${rowSymY} ${from},${rowSymY + bowH}`
2828
+ ).attr("fill", theme.centralizer.fill).attr("stroke", theme.centralizer.stroke).attr("stroke-width", theme.centralizer.strokeWidth);
2829
+ }
2830
+ } else if (item.subKind === "pump") {
2831
+ const y1 = rowSymY - rh / 2;
2832
+ const y2 = rowSymY + rh / 2;
2833
+ const midX = cx + rw / 2;
2834
+ const riserHalf = rw * 0.12;
2835
+ const bodyHalf = rw * 0.28;
2836
+ const bodyTop = y1 + rh * 0.4;
2837
+ symG.append("rect").attr("class", cls.constructionRect).attr("x", midX - riserHalf).attr("y", y1).attr("width", 2 * riserHalf).attr("height", bodyTop - y1).attr("fill", theme.pump.riserFill).attr("stroke", "none");
2838
+ symG.append("path").attr("class", cls.constructionRect).attr("d", pipeWallsPath(midX - riserHalf, midX + riserHalf, y1, bodyTop)).attr("fill", "none").attr("stroke", theme.pump.riserStroke).attr("stroke-width", theme.pump.riserStrokeWidth);
2839
+ symG.append("path").attr("class", cls.constructionRect).attr(
2840
+ "d",
2841
+ `M${midX - bodyHalf},${bodyTop}H${midX + bodyHalf}V${y2}H${midX - bodyHalf}Z`
2842
+ ).attr("fill", theme.pump.fill).attr("stroke", theme.pump.stroke).attr("stroke-width", theme.pump.strokeWidth);
2843
+ } else if (item.subKind === "reduction") {
2844
+ const topW = rw * 0.5;
2845
+ const y1 = rowSymY - rh / 2;
2846
+ const y2 = rowSymY + rh / 2;
2847
+ const midX = cx + rw / 2;
2848
+ const tl = `${midX - topW / 2},${y1}`;
2849
+ const tr = `${midX + topW / 2},${y1}`;
2850
+ symG.append("polygon").attr("class", cls.constructionRect).attr("points", `${tl} ${tr} ${cx + rw},${y2} ${cx},${y2}`).attr("fill", theme.reduction.fill).attr("stroke", "none");
2851
+ symG.append("path").attr("class", cls.constructionRect).attr("d", `M${tl}L${cx},${y2}M${tr}L${cx + rw},${y2}`).attr("fill", "none").attr("stroke", theme.reduction.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
2852
+ } else if (item.subKind === "wellCase" || item.subKind === "wellScreen" || item.subKind === "boreHole") {
2853
+ const y1 = rowSymY - rh / 2;
2854
+ const y2 = rowSymY + rh / 2;
2855
+ const shoe = 3;
2856
+ const xL = cx + shoe;
2857
+ const xR = cx + rw - shoe;
2858
+ const style = {
2859
+ wellCase: { fill: theme.wellCase.fill, stroke: theme.wellCase.stroke },
2860
+ wellScreen: {
2861
+ fill: legendTextures.well_screen.url(),
2862
+ stroke: theme.wellScreen.stroke
2863
+ },
2864
+ boreHole: { fill: theme.boreHole.fill, stroke: theme.boreHole.stroke }
2865
+ }[item.subKind];
2866
+ symG.append("rect").attr("class", cls.constructionRect).attr("x", xL).attr("y", y1).attr("width", xR - xL).attr("height", rh).attr("fill", style.fill).attr("stroke", "none");
2867
+ const walls = symG.append("path").attr("class", cls.constructionRect).attr(
2868
+ "d",
2869
+ pipeWallsPath(xL, xR, y1, y2) + (item.subKind === "boreHole" ? `M${xL},${y2}H${xR}` : "")
2870
+ ).attr("fill", "none").attr("stroke", style.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
2871
+ if (item.subKind === "boreHole")
2872
+ walls.attr("stroke-dasharray", theme.boreHole.strokeDasharray);
2873
+ if (item.subKind === "wellCase")
2874
+ symG.append("path").attr("class", cls.constructionRect).attr("d", shoePath(xL, xR, y2, shoe)).attr("fill", style.stroke).attr("stroke", "none");
2875
+ if (item.subKind === "wellScreen")
2876
+ symG.append("path").attr("class", cls.constructionRect).attr(
2877
+ "d",
2878
+ screenEndPath(xL, xR, y1 + 0.5) + screenEndPath(xL, xR, y2 - 0.5)
2879
+ ).attr("fill", "none").attr("stroke", style.stroke).attr("stroke-width", theme.wellScreen.endStrokeWidth).attr("stroke-dasharray", "2, 1.5");
2880
+ } else if (item.subKind === "cementPad") {
2881
+ const h = rh * 0.6;
2882
+ const { slabs } = getCementPadGeometry(
2883
+ rw,
2884
+ 2 * cx + rw,
2885
+ rowSymY + h / 2,
2886
+ { widthRatio: 1, height: h },
2887
+ rw * 0.3
2888
+ );
2889
+ for (const d of slabs)
2890
+ symG.append("path").attr("class", cls.constructionRect).attr("d", d).attr("fill", legendTextures.pad.url()).attr("stroke", theme.cementPad.stroke).attr("stroke-width", theme.legend.itemStrokeWidth).attr("stroke-linejoin", RC);
2891
+ } else {
2892
+ const symMap = {
2893
+ holeFillGravel: {
2894
+ fill: legendTextures.gravel_pack.url(),
2895
+ stroke: theme.holeFill.stroke
2896
+ },
2897
+ holeFillSeal: {
2898
+ fill: legendTextures.seal.url(),
2899
+ stroke: theme.holeFill.stroke
2900
+ },
2901
+ conflict: {
2902
+ fill: legendTextures.conflict.url(),
2903
+ stroke: theme.conflict.stroke
2904
+ }
2905
+ };
2906
+ const sym = symMap[item.subKind];
2907
+ const symRect = symG.append("rect").attr("class", cls.constructionRect).attr("x", cx).attr("y", rowSymY - rh / 2).attr("width", rw).attr("height", rh).attr("fill", sym.fill).attr("stroke", sym.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
2908
+ if (sym.dasharray) symRect.attr("stroke-dasharray", sym.dasharray);
2909
+ }
2910
+ }
2911
+ function resolveStyle(options) {
2912
+ return {
2913
+ theme: (0, import_defu.defu)(options.theme ?? {}, DEFAULT_WELL_THEME),
2914
+ cls: options.classNames ? { ...DEFAULT_COMPONENTS_CLASS_NAMES.legend, ...options.classNames } : DEFAULT_COMPONENTS_CLASS_NAMES.legend,
2915
+ textures: createWellTextures(options.textures)
2916
+ };
2917
+ }
2918
+ function drawWellLegendSymbol(selector, item, options = {}) {
2919
+ const style = resolveStyle(options);
2920
+ const svgEl = d37.select(selector);
2921
+ svgEl.selectAll("*").remove();
2922
+ svgEl.attr("width", SYMBOL_WIDTH + SYMBOL_MARGIN * 2).attr("height", SYMBOL_HEIGHT + SYMBOL_MARGIN * 2);
2923
+ Object.values(style.textures).forEach((t) => svgEl.call(t));
2924
+ const symG = svgEl.append("g").attr("class", style.cls.item);
2925
+ drawLegendSymbol(
2926
+ symG,
2927
+ item,
2928
+ SYMBOL_MARGIN,
2929
+ SYMBOL_MARGIN + SYMBOL_HEIGHT / 2,
2930
+ style
2931
+ );
2932
+ }
2933
+ function drawWellLegend(selector, profile, options = {}) {
2934
+ const cfg = (0, import_defu.defu)(
2935
+ options.config ?? {},
2936
+ INTERACTIVE_RENDER_CONFIG.legend
2937
+ );
2938
+ const items = getWellLegendItems(profile, cfg.labels);
2939
+ const svgEl = d37.select(selector);
1998
2940
  svgEl.selectAll("*").remove();
1999
2941
  if (items.length === 0) {
2000
2942
  svgEl.attr("width", 0).attr("height", 0);
2001
2943
  return;
2002
2944
  }
2003
- const legendTextures = createWellTextures(options.textures);
2004
- Object.values(legendTextures).forEach((t) => svgEl.call(t));
2945
+ const style = resolveStyle(options);
2946
+ const { theme, cls } = style;
2947
+ Object.values(style.textures).forEach((t) => svgEl.call(t));
2005
2948
  const itemsPerRow = cfg.maxWidth ? Math.max(1, Math.floor((cfg.maxWidth - cfg.padding * 2) / cfg.itemWidth)) : items.length;
2006
2949
  const numRows = Math.ceil(items.length / itemsPerRow);
2007
2950
  const rowWidth = Math.min(items.length, itemsPerRow) * cfg.itemWidth;
2008
2951
  const totalWidth = cfg.padding * 2 + rowWidth;
2009
2952
  const totalHeight = numRows * cfg.height;
2010
2953
  svgEl.attr("width", totalWidth).attr("height", totalHeight);
2011
- const RC = "round";
2012
2954
  const g = svgEl.append("g").attr("transform", `translate(${cfg.padding}, 2)`);
2013
2955
  g.append("rect").attr("class", cls.border).attr("x", 0).attr("y", 0).attr("width", totalWidth - cfg.padding).attr("height", totalHeight - 4).attr("fill", "white").attr("rx", cfg.borderRadius).attr("stroke-width", theme.legend.borderStrokeWidth);
2014
2956
  g.append("text").attr("class", cls.title).attr("x", 4).attr("y", 11).attr("font-size", theme.legend.fontSize).attr("font-family", theme.legend.fontFamily ?? "sans-serif").attr("font-weight", theme.legend.titleFontWeight ?? "bold").text(`${cfg.title}:`);
2015
- const rw = 24;
2016
- const rh = 12;
2017
2957
  items.forEach((item, i) => {
2018
2958
  const col = i % itemsPerRow;
2019
2959
  const row = Math.floor(i / itemsPerRow);
2020
2960
  const rowSymY = row * cfg.height + (cfg.height - 4) / 2;
2021
2961
  const cx = col * cfg.itemWidth;
2022
2962
  const symG = g.append("g").attr("class", cls.item);
2023
- if (item.kind === "fracture") {
2024
- const color = item.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
2025
- if (item.swarm) {
2026
- [-4, 0, 4].forEach((offset) => {
2027
- symG.append("polyline").attr("class", cls.fracturePoly).attr(
2028
- "points",
2029
- `${cx},${rowSymY + offset} ${cx + 6},${rowSymY + offset - 1} ${cx + 12},${rowSymY + offset + 1} ${cx + 18},${rowSymY + offset - 0.5} ${cx + 24},${rowSymY + offset}`
2030
- ).attr("stroke", color).attr(
2031
- "stroke-width",
2032
- offset !== 0 ? theme.legend.fractureSideStrokeWidth : theme.legend.fractureStrokeWidth
2033
- ).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2034
- });
2035
- } else {
2036
- symG.append("polyline").attr("class", cls.fracturePoly).attr(
2037
- "points",
2038
- `${cx},${rowSymY} ${cx + 5},${rowSymY - 1.5} ${cx + 11},${rowSymY + 1} ${cx + 17},${rowSymY - 0.5} ${cx + 24},${rowSymY}`
2039
- ).attr("stroke", color).attr("stroke-width", theme.legend.fractureStrokeWidth).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2040
- }
2041
- } else if (item.kind === "cave") {
2042
- const color = item.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
2043
- const texture = item.water_intake ? legendTextures.cave_wet : legendTextures.cave_dry;
2044
- symG.append("rect").attr("class", cls.caveFill).attr("x", cx).attr("y", rowSymY - rh / 2).attr("width", rw).attr("height", rh).attr("fill", texture.url()).attr("stroke", color).attr("stroke-width", theme.legend.itemStrokeWidth);
2045
- } else if (item.subKind === "surfaceCase") {
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");
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);
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);
2058
- } else {
2059
- const symMap = {
2060
- boreHole: {
2061
- fill: theme.boreHole.fill,
2062
- stroke: theme.boreHole.stroke,
2063
- dasharray: theme.boreHole.strokeDasharray
2064
- },
2065
- holeFillGravel: {
2066
- fill: legendTextures.gravel_pack.url(),
2067
- stroke: theme.holeFill.stroke
2068
- },
2069
- holeFillSeal: {
2070
- fill: legendTextures.seal.url(),
2071
- stroke: theme.holeFill.stroke
2072
- },
2073
- wellCase: { fill: theme.wellCase.fill, stroke: theme.wellCase.stroke },
2074
- wellScreen: {
2075
- fill: legendTextures.well_screen.url(),
2076
- stroke: theme.wellScreen.stroke
2077
- },
2078
- cementPad: {
2079
- fill: legendTextures.pad.url(),
2080
- stroke: theme.cementPad.stroke
2081
- },
2082
- conflict: {
2083
- fill: legendTextures.conflict.url(),
2084
- stroke: theme.conflict.stroke
2085
- }
2086
- };
2087
- const sym = symMap[item.subKind];
2088
- const symRect = symG.append("rect").attr("class", cls.constructionRect).attr("x", cx).attr("y", rowSymY - rh / 2).attr("width", rw).attr("height", rh).attr("fill", sym.fill).attr("stroke", sym.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
2089
- if (sym.dasharray) symRect.attr("stroke-dasharray", sym.dasharray);
2090
- }
2963
+ drawLegendSymbol(symG, item, cx, rowSymY, style);
2091
2964
  g.append("text").attr("class", cls.label).attr("x", cx + 28).attr("y", rowSymY + 3).attr("font-size", theme.legend.fontSize).attr("font-family", theme.legend.fontFamily ?? "sans-serif").attr("font-weight", theme.legend.labelFontWeight ?? 400).text(item.label);
2092
2965
  });
2093
2966
  }
2094
2967
 
2095
2968
  // src/renderers/lithology.renderer.ts
2969
+ var d38 = __toESM(require("d3"), 1);
2096
2970
  function drawLithology(ctx, data) {
2097
- if (!data.length) return;
2098
2971
  const {
2099
2972
  groups: { lithologyGroup },
2100
2973
  svg,
@@ -2118,10 +2991,219 @@ function drawLithology(ctx, data) {
2118
2991
  const merged = mergeEnter(newLayers, rects);
2119
2992
  withTransition(merged, transition).attr("y", getYPos).attr("height", getHeight).attr("fill", getLithologyFill(data, svg, ctx.theme.lithologyTexture));
2120
2993
  }
2994
+ function drawLithologySwatch(selector, layer, options) {
2995
+ const { size, theme } = options;
2996
+ const svg = d38.select(selector);
2997
+ svg.selectAll("*").remove();
2998
+ svg.attr("width", size).attr("height", size);
2999
+ const fill = getLithologyFill([layer], svg, {
3000
+ ...theme.lithologyTexture,
3001
+ // Denser pattern so a few strokes show inside the small square.
3002
+ size: Math.max(12, size * 3)
3003
+ });
3004
+ svg.append("rect").attr("x", 0.5).attr("y", 0.5).attr("width", size - 1).attr("height", size - 1).attr("fill", fill(layer)).attr("stroke", theme.lithology.stroke).attr("stroke-width", 0.5);
3005
+ }
3006
+
3007
+ // src/renderers/markers.renderer.ts
3008
+ function drawMarkers(ctx, data) {
3009
+ const group = ctx.groups.markersGroup;
3010
+ group.selectAll("*").remove();
3011
+ const cfg = ctx.renderConfig.markers;
3012
+ const theme = ctx.theme.markers;
3013
+ const r = cfg.radius;
3014
+ if (cfg.lithology) {
3015
+ const cx = (ctx.geoXLeft + ctx.geoXRight) / 2;
3016
+ getLithologyMarkers(data.lithology, ctx.depthFrom, ctx.depthTo).forEach(
3017
+ (marker) => {
3018
+ const top = ctx.yScale(marker.from);
3019
+ const bottom = ctx.yScale(marker.to);
3020
+ if (bottom - top < r) return;
3021
+ const cy = bottom - top >= 2 * r + 4 ? top + r + 2 : (top + bottom) / 2;
3022
+ const g = group.append("g").attr("class", ctx.classes.markers.lithology);
3023
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", theme.fill).attr("stroke", theme.stroke).attr("stroke-width", theme.strokeWidth);
3024
+ g.append("text").attr("x", cx).attr("y", cy).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", theme.fontSize).attr("font-family", theme.fontFamily ?? "sans-serif").attr("font-weight", theme.fontWeight ?? 600).attr("fill", theme.color).text(String(marker.index));
3025
+ }
3026
+ );
3027
+ }
3028
+ if (cfg.fractures) {
3029
+ const x = ctx.pocoCenterX + ctx.POCO_WIDTH * ctx.renderConfig.fractures.widthMultiplier / 2 + 3;
3030
+ getFractureMarkers(data.fractures, ctx.depthFrom, ctx.depthTo).forEach(
3031
+ (marker) => {
3032
+ group.append("text").attr("class", ctx.classes.markers.fracture).attr("x", x).attr("y", ctx.yScale(marker.depth)).attr("dominant-baseline", "central").attr("font-size", theme.fontSize).attr("font-family", theme.fontFamily ?? "sans-serif").attr("font-weight", theme.fontWeight ?? 600).attr("fill", theme.color).text(marker.label);
3033
+ }
3034
+ );
3035
+ }
3036
+ }
3037
+
3038
+ // src/renderers/overlap.renderer.ts
3039
+ var import_textures3 = __toESM(require("textures"), 1);
3040
+ var import_utils8 = require("@welldot/utils");
3041
+ function drawOverlap(ctx) {
3042
+ const group = ctx.groups.overlapGroup;
3043
+ group.selectAll("*").remove();
3044
+ const { repeatedTo, depthFrom } = ctx;
3045
+ if (repeatedTo == null || repeatedTo <= depthFrom) return;
3046
+ const theme = ctx.theme.overlap;
3047
+ const { margins } = ctx.state;
3048
+ const overview = ctx.renderConfig.overview;
3049
+ const showsDescription = hasDescriptionColumn(ctx);
3050
+ const x0 = -margins.left + (overview.active ? overview.x + overview.width + 4 : 0);
3051
+ const x1 = showsDescription ? ctx.geoXRight + ctx.renderConfig.labels.descriptionXOffset - 4 : ctx.svgWidth - margins.left;
3052
+ const y0 = ctx.yScale(depthFrom);
3053
+ const y1 = ctx.yScale(repeatedTo);
3054
+ const hatch = import_textures3.default.lines().size(theme.size).strokeWidth(theme.strokeWidth).stroke(theme.stroke);
3055
+ ctx.svg.call(hatch);
3056
+ group.append("rect").attr("class", ctx.classes.overlap.band).attr("x", x0).attr("y", y0).attr("width", Math.max(0, x1 - x0)).attr("height", Math.max(0, y1 - y0)).attr("fill", hatch.url()).style("pointer-events", "none");
3057
+ group.append("line").attr("x1", x0).attr("x2", ctx.svgWidth - margins.left).attr("y1", y1).attr("y2", y1).attr("stroke", theme.ruleStroke).attr("stroke-width", 0.6).attr("stroke-dasharray", "3, 2");
3058
+ const whole = (m) => (0, import_utils8.formatNumber)(ctx.units.length === "ft" ? m * 3.28084 : m, {
3059
+ maximumFractionDigits: 0,
3060
+ locale: ctx.locale
3061
+ });
3062
+ const unit = getLengthUnit(ctx.units.length);
3063
+ const range = `${whole(depthFrom)}\u2013${whole(repeatedTo)} ${unit}`;
3064
+ group.append("text").attr("class", ctx.classes.overlap.label).attr("x", ctx.svgWidth - margins.left - 2).attr("y", y0 + theme.fontSize + 1).attr("text-anchor", "end").attr("font-size", theme.fontSize).attr("font-family", theme.fontFamily ?? "monospace").attr("fill", theme.labelColor).text(formatOverlapLabel(ctx.renderConfig.overlap.label, range));
3065
+ }
3066
+
3067
+ // src/renderers/overview.renderer.ts
3068
+ function drawOverview(ctx, lithology) {
3069
+ const group = ctx.groups.overviewGroup;
3070
+ group.selectAll("*").remove();
3071
+ const cfg = ctx.renderConfig.overview;
3072
+ if (!cfg.active || lithology.length === 0 || ctx.totalDepth <= 0) return;
3073
+ const theme = ctx.theme.overview;
3074
+ const height = Math.min(cfg.height, ctx.state.height);
3075
+ const { y, window } = getOverviewGeometry(
3076
+ ctx.totalDepth,
3077
+ height,
3078
+ ctx.depthFrom,
3079
+ ctx.depthTo
3080
+ );
3081
+ lithology.forEach((layer) => {
3082
+ group.append("rect").attr("class", ctx.classes.overview.item).attr("x", cfg.x).attr("y", y(layer.from)).attr("width", cfg.width).attr("height", Math.max(0, y(layer.to) - y(layer.from))).attr("fill", layer.color || theme.emptyFill);
3083
+ });
3084
+ group.append("rect").attr("x", cfg.x).attr("y", 0).attr("width", cfg.width).attr("height", y(ctx.totalDepth)).attr("fill", "none").attr("stroke", theme.stroke).attr("stroke-width", theme.strokeWidth);
3085
+ group.append("rect").attr("class", ctx.classes.overview.window).attr("x", cfg.x - 2).attr("y", window.y).attr("width", cfg.width + 4).attr("height", window.height).attr("fill", "none").attr("stroke", theme.windowStroke).attr("stroke-width", theme.windowStrokeWidth);
3086
+ }
3087
+
3088
+ // src/renderers/pump.renderer.ts
3089
+ var d39 = __toESM(require("d3"), 1);
3090
+ var import_utils9 = require("@welldot/utils");
3091
+ function getPumpSpans(panelTop, panelBottom, bodyTop, intakeY) {
3092
+ const riserBottom = Math.min(Math.max(panelTop, bodyTop), panelBottom);
3093
+ const top = Math.max(bodyTop, panelTop);
3094
+ const bottom = Math.min(intakeY, panelBottom);
3095
+ return {
3096
+ riserBottom,
3097
+ body: bottom > top ? { top, bottom, cutTop: top > bodyTop, cutBottom: bottom < intakeY } : null
3098
+ };
3099
+ }
3100
+ var riserBreakAmplitude = (width) => Math.min(2, width / 3);
3101
+ function getRiserTop(xLeft, xRight, y, broken) {
3102
+ if (!broken) return { edge: `H${xRight}`, breakLine: "" };
3103
+ const a = riserBreakAmplitude(xRight - xLeft);
3104
+ const w = xRight - xLeft;
3105
+ const zigzag = `L${xLeft + w / 3},${y - a}L${xLeft + 2 * w / 3},${y + a}L${xRight},${y}`;
3106
+ return { edge: zigzag, breakLine: `M${xLeft},${y}${zigzag}` };
3107
+ }
3108
+ var BODY_MAX_FILL = 0.85;
3109
+ var RISER_MAX_FILL = 0.7;
3110
+ function getPumpDiameters(intakeD, narrowestD, riserDiameter, cfg) {
3111
+ const maxBody = intakeD * BODY_MAX_FILL;
3112
+ const riser = Math.min(
3113
+ riserDiameter ?? intakeD * cfg.riserWidthRatio,
3114
+ narrowestD * RISER_MAX_FILL,
3115
+ maxBody * RISER_MAX_FILL
3116
+ );
3117
+ const body = Math.min(
3118
+ Math.max(intakeD * cfg.bodyWidthRatio, riser * 1.4),
3119
+ maxBody
3120
+ );
3121
+ return { riser, body };
3122
+ }
3123
+ function drawPump(ctx, pump) {
3124
+ const group = ctx.groups.pumpGroup;
3125
+ const cls = ctx.classes.pump;
3126
+ const cfg = ctx.renderConfig.construction.pump;
3127
+ const theme = ctx.theme.pump;
3128
+ const intake = pump?.intake_depth;
3129
+ const visible = !!pump && cfg.active && intake !== void 0 && intake > 0 && // the riser crosses this panel when the panel starts above the intake
3130
+ ctx.depthFrom < intake;
3131
+ const data = visible ? [pump] : [];
3132
+ const selection = group.selectAll(`g.${cls.item}`).data(data, (d) => d.id);
3133
+ selection.exit().remove();
3134
+ if (!visible) return;
3135
+ const maxXValue = d39.max((0, import_utils9.getProfileDiamValues)(ctx.constructionData)) || 0;
3136
+ const xScale = d39.scaleLinear().domain([0, maxXValue]).range([0, ctx.POCO_WIDTH]);
3137
+ const pipes = [
3138
+ ...ctx.constructionData.well_case,
3139
+ ...ctx.constructionData.well_screen
3140
+ ];
3141
+ const casingD = diameterAt(pipes, intake) ?? diameterAt(ctx.constructionData.bore_hole, intake) ?? maxXValue;
3142
+ const narrowestD = Math.min(
3143
+ casingD,
3144
+ ...[...pipes, ...ctx.constructionData.bore_hole].filter((i) => i.from < intake && i.to > 0).map((i) => i.diameter)
3145
+ );
3146
+ const { riser: riserD, body: bodyD } = getPumpDiameters(
3147
+ casingD,
3148
+ narrowestD,
3149
+ pump.riser_diameter,
3150
+ cfg
3151
+ );
3152
+ const riserLeft = (ctx.POCO_CENTER - xScale(riserD)) / 2;
3153
+ const riserRight = (ctx.POCO_CENTER + xScale(riserD)) / 2;
3154
+ const bodyLeft = (ctx.POCO_CENTER - xScale(bodyD)) / 2;
3155
+ const bodyRight = (ctx.POCO_CENTER + xScale(bodyD)) / 2;
3156
+ const yIntake = ctx.yScale(intake);
3157
+ const yBodyTop = yIntake - cfg.bodyHeight;
3158
+ const headDepth = Math.min(
3159
+ 0,
3160
+ ...pipes.filter((p) => p.from < 0).map((p) => p.from)
3161
+ );
3162
+ const headInPanel = ctx.depthFrom <= headDepth;
3163
+ const padOnHead = headDepth === 0 && !!ctx.constructionData.cement_pad?.thickness;
3164
+ const yRiserTop = headInPanel ? ctx.yScale(headDepth) - (padOnHead ? ctx.renderConfig.construction.cementPad.height - riserBreakAmplitude(xScale(riserD)) : 0) : ctx.yScale(ctx.depthFrom);
3165
+ const { riserBottom: yRiserBottom, body } = getPumpSpans(
3166
+ yRiserTop,
3167
+ ctx.yScale(ctx.depthTo),
3168
+ yBodyTop,
3169
+ yIntake
3170
+ );
3171
+ const r = Math.min(3, (bodyRight - bodyLeft) / 4);
3172
+ const entered = selection.enter().append("g").attr("class", cls.item).on("mouseover", ctx.tooltips.pump.show).on("mouseout", ctx.tooltips.pump.hide);
3173
+ entered.append("path").attr("class", `${cls.item}-riser-fill`);
3174
+ entered.append("path").attr("class", `${cls.item}-riser`);
3175
+ entered.append("path").attr("class", `${cls.item}-body`);
3176
+ entered.append("path").attr("class", `${cls.item}-intake`);
3177
+ const merged = entered.merge(selection);
3178
+ const top = getRiserTop(riserLeft, riserRight, yRiserTop, headInPanel);
3179
+ merged.select(`.${cls.item}-riser-fill`).attr(
3180
+ "d",
3181
+ yRiserBottom > yRiserTop ? `M${riserLeft},${yRiserTop}${top.edge}V${yRiserBottom}H${riserLeft}Z` : ""
3182
+ ).attr("fill", theme.riserFill).attr("stroke", "none");
3183
+ merged.select(`.${cls.item}-riser`).attr(
3184
+ "d",
3185
+ yRiserBottom > yRiserTop ? `M${riserLeft},${yRiserTop}V${yRiserBottom}M${riserRight},${yRiserTop}V${yRiserBottom}${top.breakLine}` : ""
3186
+ ).attr("stroke-linejoin", "round").attr("fill", "none").attr("stroke", theme.riserStroke).attr("stroke-width", theme.riserStrokeWidth);
3187
+ let bodyPath = "";
3188
+ if (body) {
3189
+ const rt = body.cutTop ? 0 : Math.min(r, (body.bottom - body.top) / 2);
3190
+ const rb = body.cutBottom ? 0 : Math.min(r, (body.bottom - body.top) / 2);
3191
+ const { top: top2, bottom } = body;
3192
+ bodyPath = `M${bodyLeft + rt},${top2}H${bodyRight - rt}Q${bodyRight},${top2} ${bodyRight},${top2 + rt}V${bottom - rb}Q${bodyRight},${bottom} ${bodyRight - rb},${bottom}H${bodyLeft + rb}Q${bodyLeft},${bottom} ${bodyLeft},${bottom - rb}V${top2 + rt}Q${bodyLeft},${top2} ${bodyLeft + rt},${top2}Z`;
3193
+ }
3194
+ merged.select(`.${cls.item}-body`).attr("d", bodyPath).attr("fill", theme.fill).attr("stroke", theme.stroke).attr("stroke-width", theme.strokeWidth);
3195
+ const hatchY = [
3196
+ yIntake - cfg.bodyHeight * 0.3,
3197
+ yIntake - cfg.bodyHeight * 0.15
3198
+ ].filter((y) => body && y > body.top && y < body.bottom);
3199
+ merged.select(`.${cls.item}-intake`).attr(
3200
+ "d",
3201
+ hatchY.map((y) => `M${bodyLeft + 1.5},${y}H${bodyRight - 1.5}`).join("")
3202
+ ).attr("fill", "none").attr("stroke", theme.stroke).attr("stroke-width", 0.8);
3203
+ }
2121
3204
 
2122
3205
  // src/renderers/unit-labels.renderer.ts
2123
3206
  function drawUnitLabels(ctx, data) {
2124
- if (!ctx.renderConfig.unitLabels.active || !data.length) return;
2125
3207
  const {
2126
3208
  groups: { unitLabelsGroup },
2127
3209
  depthFrom,
@@ -2132,6 +3214,7 @@ function drawUnitLabels(ctx, data) {
2132
3214
  classes
2133
3215
  } = ctx;
2134
3216
  unitLabelsGroup.selectAll("*").remove();
3217
+ if (!renderConfig.unitLabels.active || !data.length) return;
2135
3218
  const ru = renderConfig.unitLabels;
2136
3219
  const rut = theme.unitLabels;
2137
3220
  const sw = ru.stripWidth;
@@ -2212,7 +3295,7 @@ function buildSvgStyleBlock(theme) {
2212
3295
  }
2213
3296
 
2214
3297
  // src/Renderer.ts
2215
- var d37 = { ...d3module2, tip: import_d3_tip2.default };
3298
+ var d310 = { ...d3module2, tip: import_d3_tip2.default };
2216
3299
  var WellRenderer = class {
2217
3300
  /**
2218
3301
  * @param svgs - One entry per SVG panel; multi-entry for split-panel layouts.
@@ -2272,12 +3355,14 @@ var WellRenderer = class {
2272
3355
  const { selector, height, width, margins } = inst;
2273
3356
  const svgWidth = width + margins.left + margins.right;
2274
3357
  const svgHeight = height + margins.top + margins.bottom;
2275
- const svg = d37.select(selector);
3358
+ const svg = d310.select(selector);
2276
3359
  svg.selectAll("*").remove();
2277
3360
  svg.attr("height", svgHeight).attr("width", svgWidth);
2278
3361
  const defs = svg.append("defs");
2279
3362
  defs.append("style").text(buildSvgStyleBlock(this.theme));
2280
3363
  defs.append("clipPath").attr("id", clipId).append("rect").attr("id", clipRectId).attr("x", 0).attr("y", 0).attr("width", 1e5).attr("height", 1e4);
3364
+ svg.append("g").attr("class", this.classes.overview.group).attr("transform", `translate(0, ${margins.top})`);
3365
+ svg.append("g").attr("class", this.classes.columnHeaders.group);
2281
3366
  const poco = svg.append("g").attr("class", this.classes.wellGroup);
2282
3367
  const geologic = poco.append("g").attr("class", this.classes.geologicGroup).attr("transform", `translate(${margins.left}, ${margins.top})`);
2283
3368
  geologic.append("g").attr("class", this.classes.yAxis);
@@ -2292,6 +3377,7 @@ var WellRenderer = class {
2292
3377
  );
2293
3378
  const fracturesGroup = poco.append("g").attr("class", this.classes.fractures.group).attr("transform", `translate(${margins.left}, ${margins.top})`).attr("clip-path", `url(#${clipId})`);
2294
3379
  fracturesGroup.append("g").attr("class", this.classes.highlights.fracturesGroup);
3380
+ poco.append("g").attr("class", this.classes.markers.group).attr("transform", `translate(${margins.left}, ${margins.top})`);
2295
3381
  construction.append("g").attr("class", this.classes.cementPad.group);
2296
3382
  construction.append("g").attr("class", this.classes.boreHole.group);
2297
3383
  construction.append("g").attr("class", this.classes.surfaceCase.group);
@@ -2299,11 +3385,23 @@ var WellRenderer = class {
2299
3385
  construction.append("g").attr("class", this.classes.wellCase.group);
2300
3386
  construction.append("g").attr("class", this.classes.wellScreen.group);
2301
3387
  construction.append("g").attr("class", this.classes.reduction.group);
3388
+ construction.append("g").attr("class", this.classes.pump.group);
3389
+ construction.append("g").attr("class", this.classes.centralizer.group);
2302
3390
  construction.append("g").attr("class", this.classes.conflict.group);
2303
3391
  construction.append("g").attr("class", this.classes.constructionLabels.group);
2304
3392
  construction.append("g").attr("class", this.classes.highlights.constructionGroup);
3393
+ poco.append("g").attr("class", this.classes.overlap.group).attr("transform", `translate(${margins.left}, ${margins.top})`);
2305
3394
  Object.values(this.textures).forEach((texture) => svg.call(texture));
2306
- return { svg, height, width, margins, clipId, clipRectId };
3395
+ return {
3396
+ svg,
3397
+ height,
3398
+ width,
3399
+ margins,
3400
+ clipId,
3401
+ clipRectId,
3402
+ depthRange: inst.depthRange,
3403
+ repeatedTo: inst.repeatedTo
3404
+ };
2307
3405
  }
2308
3406
  /** Renders a standalone legend into the element matched by `selector`. */
2309
3407
  renderLegend(selector, profile) {
@@ -2314,6 +3412,30 @@ var WellRenderer = class {
2314
3412
  textures: this.renderConfig.textures
2315
3413
  });
2316
3414
  }
3415
+ /** Lists the legend entries `profile` needs, labeled from `renderConfig.legend.labels`. */
3416
+ getLegendItems(profile) {
3417
+ return getWellLegendItems(profile, this.renderConfig.legend.labels);
3418
+ }
3419
+ /**
3420
+ * Renders one legend entry's symbol (see {@link getLegendItems}) into its
3421
+ * own SVG matched by `selector`, with the textures it needs defined inside.
3422
+ */
3423
+ renderLegendSymbol(selector, item) {
3424
+ drawWellLegendSymbol(selector, item, {
3425
+ theme: this.theme,
3426
+ classNames: this.classes.legend,
3427
+ textures: this.renderConfig.textures
3428
+ });
3429
+ }
3430
+ /**
3431
+ * Renders one lithology layer's fill as a `size`×`size` square into its
3432
+ * own SVG matched by `selector`, with its texture defined inside (e.g. for
3433
+ * a swatch in a PDF table). Call after `prepareSvg()`, which loads the
3434
+ * FGDC textures.
3435
+ */
3436
+ renderLithologySwatch(selector, layer, size = 10) {
3437
+ drawLithologySwatch(selector, layer, { size, theme: this.theme });
3438
+ }
2317
3439
  /**
2318
3440
  * Multiplies the current zoom scale by `factor` (e.g. `1.25` to zoom in,
2319
3441
  * `1 / 1.25` to zoom out) on every panel. No-op if both `renderConfig.zoom`
@@ -2330,7 +3452,7 @@ var WellRenderer = class {
2330
3452
  * (defaults to `1`, i.e. the initial fit-to-container view).
2331
3453
  */
2332
3454
  resetZoom() {
2333
- const target = d37.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
3455
+ const target = d310.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
2334
3456
  for (const state of this.instanceStates) {
2335
3457
  if (!state.zoomNode) continue;
2336
3458
  asSvgElement(state.svg).transition().call(state.zoomNode.transform, target);
@@ -2342,7 +3464,46 @@ var WellRenderer = class {
2342
3464
  */
2343
3465
  getZoomScale() {
2344
3466
  const node = this.instanceStates[0]?.svg.node();
2345
- return node ? d37.zoomTransform(node).k : 1;
3467
+ return node ? d310.zoomTransform(node).k : 1;
3468
+ }
3469
+ /**
3470
+ * Removes everything drawn on every panel (layers, labels, axis,
3471
+ * highlights) and keeps the structure `prepareSvg()` built, so the next
3472
+ * `draw()` needs no new `prepareSvg()`. `draw()` calls it for an empty well.
3473
+ */
3474
+ clear() {
3475
+ const c = this.classes;
3476
+ const layerGroups = [
3477
+ c.overview.group,
3478
+ c.columnHeaders.group,
3479
+ c.yAxis,
3480
+ c.unitLabels.group,
3481
+ c.lithology.group,
3482
+ c.caves.group,
3483
+ c.labels.lithology.group,
3484
+ c.highlights.geologicGroup,
3485
+ c.highlights.constructionGroup,
3486
+ c.highlights.fracturesGroup,
3487
+ c.markers.group,
3488
+ c.cementPad.group,
3489
+ c.boreHole.group,
3490
+ c.surfaceCase.group,
3491
+ c.holeFill.group,
3492
+ c.wellCase.group,
3493
+ c.wellScreen.group,
3494
+ c.reduction.group,
3495
+ c.pump.group,
3496
+ c.centralizer.group,
3497
+ c.conflict.group,
3498
+ c.constructionLabels.group,
3499
+ c.overlap.group
3500
+ ];
3501
+ for (const { svg } of this.instanceStates) {
3502
+ for (const cls of layerGroups) {
3503
+ svg.select(`.${cls}`).selectAll("*").interrupt().remove();
3504
+ }
3505
+ svg.select(`.${c.fractures.group}`).selectAll(`g.${c.fractures.item}`).interrupt().remove();
3506
+ }
2346
3507
  }
2347
3508
  /**
2348
3509
  * Initialises the SVG DOM structure and preloads FGDC textures.
@@ -2355,14 +3516,19 @@ var WellRenderer = class {
2355
3516
  );
2356
3517
  }
2357
3518
  /**
2358
- * Renders the well profile into all configured SVG panels.
3519
+ * Renders the well profile into all configured SVG panels. Layers the
3520
+ * profile no longer has are removed; an empty well (`isWellEmpty`) clears
3521
+ * the panels (see {@link clear}).
2359
3522
  * @param profile - The well data to render.
2360
3523
  * @param options.units - Override the instance-level measurement units for this render.
2361
3524
  * @param options.locale - Override the instance-level locale for this render.
2362
3525
  * @param options.highlights - Highlight overlays to display on top of the profile.
2363
3526
  */
2364
3527
  draw(profile, options = {}) {
2365
- if ((0, import_core.isWellEmpty)(profile)) return;
3528
+ if ((0, import_core4.isWellEmpty)(profile)) {
3529
+ this.clear();
3530
+ return;
3531
+ }
2366
3532
  if (profile.version !== 2) {
2367
3533
  console.warn(
2368
3534
  `[WellRenderer] Received a .well file with version ${profile.version ?? "unknown"}. Only v2 is supported. Normalize with deserializeWell() from @welldot/core before rendering.`
@@ -2373,36 +3539,19 @@ var WellRenderer = class {
2373
3539
  this.locale = options.locale;
2374
3540
  }
2375
3541
  const highlights = options.highlights ?? {};
2376
- const maxValues = (0, import_utils6.getProfileLastItemsDepths)(profile);
2377
- const maxYValues = d37.max(maxValues) || 0;
2378
- if (this.instanceStates.length === 1) {
2379
- this.drawLogToInstance(
2380
- this.instanceStates[0],
2381
- profile,
2382
- 0,
2383
- maxYValues,
2384
- highlights
2385
- );
2386
- return;
2387
- }
2388
- const totalHeight = this.instanceStates.reduce((s, st) => s + st.height, 0) || 1;
2389
- const yScaleGlobal = d37.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
2390
- let currentDepth = 0;
2391
- for (const state of this.instanceStates) {
2392
- const maxSvgDepth = yScaleGlobal.invert(
2393
- yScaleGlobal(currentDepth) + state.height
2394
- );
2395
- this.drawLogToInstance(
2396
- state,
2397
- profile,
2398
- currentDepth,
2399
- maxSvgDepth,
2400
- highlights
2401
- );
2402
- currentDepth = maxSvgDepth;
2403
- }
3542
+ const maxValues = (0, import_utils10.getProfileLastItemsDepths)(profile);
3543
+ const maxYValues = d310.max(maxValues) || 0;
3544
+ const windows = resolvePanelWindows(
3545
+ this.instanceStates,
3546
+ maxYValues,
3547
+ getProfileTopDepth(profile)
3548
+ );
3549
+ this.instanceStates.forEach((state, i) => {
3550
+ const [from, to] = windows[i];
3551
+ this.drawLogToInstance(state, profile, from, to, maxYValues, highlights);
3552
+ });
2404
3553
  }
2405
- drawLogToInstance(state, profile, depthFrom, depthTo, highlights = {}) {
3554
+ drawLogToInstance(state, profile, depthFrom, depthTo, totalDepth, highlights = {}) {
2406
3555
  const svg = state.svg;
2407
3556
  const pocoGroup = svg.select(`.${this.classes.wellGroup}`);
2408
3557
  const lithologyGroup = svg.select(`.${this.classes.lithology.group}`);
@@ -2419,6 +3568,8 @@ var WellRenderer = class {
2419
3568
  const wellCaseGroup = svg.select(`.${this.classes.wellCase.group}`);
2420
3569
  const wellScreenGroup = svg.select(`.${this.classes.wellScreen.group}`);
2421
3570
  const reductionGroup = svg.select(`.${this.classes.reduction.group}`);
3571
+ const centralizerGroup = svg.select(`.${this.classes.centralizer.group}`);
3572
+ const pumpGroup = svg.select(`.${this.classes.pump.group}`);
2422
3573
  const conflictGroup = svg.select(`.${this.classes.conflict.group}`);
2423
3574
  const constructionLabelsGroup = svg.select(
2424
3575
  `.${this.classes.constructionLabels.group}`
@@ -2433,13 +3584,19 @@ var WellRenderer = class {
2433
3584
  `.${this.classes.highlights.fracturesGroup}`
2434
3585
  );
2435
3586
  const unitLabelsGroup = svg.select(`.${this.classes.unitLabels.group}`);
3587
+ const overviewGroup = svg.select(`.${this.classes.overview.group}`);
3588
+ const columnHeadersGroup = svg.select(
3589
+ `.${this.classes.columnHeaders.group}`
3590
+ );
3591
+ const markersGroup = svg.select(`.${this.classes.markers.group}`);
3592
+ const overlapGroup = svg.select(`.${this.classes.overlap.group}`);
2436
3593
  const svgWidth = state.width + state.margins.left + state.margins.right;
2437
3594
  const svgHeight = state.height + state.margins.top + state.margins.bottom;
2438
3595
  const { pocoWidthRatio, pocoCenterRatio } = this.renderConfig.layout;
2439
3596
  const POCO_WIDTH = svgWidth * pocoWidthRatio;
2440
3597
  const POCO_CENTER = svgWidth * pocoCenterRatio;
2441
3598
  const { duration, ease } = this.renderConfig.animation;
2442
- const transition = d37.transition().duration(duration).ease(ease);
3599
+ const transition = d310.transition().duration(duration).ease(ease);
2443
3600
  const tooltips = populateTooltips(
2444
3601
  svg,
2445
3602
  this.classes,
@@ -2464,14 +3621,15 @@ var WellRenderer = class {
2464
3621
  well_case: profile.well_case,
2465
3622
  well_screen: profile.well_screen,
2466
3623
  reduction: profile.reduction,
3624
+ centralizers: profile.centralizers ?? [],
2467
3625
  fractures: profile.fractures
2468
3626
  };
2469
3627
  const contentHeight = svgHeight - state.margins.top - state.margins.bottom;
2470
- const yScaleLocal = d37.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
3628
+ const yScaleLocal = d310.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
2471
3629
  const depthFactor = this.units.length === "ft" ? 3.28084 : 1;
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), {
3630
+ const yScaleAxis = d310.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
3631
+ const yAxis = d310.axisLeft(yScaleAxis).tickFormat(
3632
+ (d) => `${(0, import_utils10.formatNumber)(Number(d), {
2475
3633
  maximumFractionDigits: this.units.length === "ft" ? 1 : 2,
2476
3634
  locale: this.locale
2477
3635
  })}${getLengthUnit(this.units.length)}`
@@ -2512,10 +3670,16 @@ var WellRenderer = class {
2512
3670
  wellCaseGroup,
2513
3671
  wellScreenGroup,
2514
3672
  reductionGroup,
3673
+ centralizerGroup,
3674
+ pumpGroup,
2515
3675
  conflictGroup,
2516
3676
  highlightsGeologicGroup,
2517
3677
  highlightsConstructionGroup,
2518
- highlightsFracturesGroup
3678
+ highlightsFracturesGroup,
3679
+ overviewGroup,
3680
+ columnHeadersGroup,
3681
+ markersGroup,
3682
+ overlapGroup
2519
3683
  };
2520
3684
  const ctx = {
2521
3685
  svg,
@@ -2529,6 +3693,8 @@ var WellRenderer = class {
2529
3693
  geoXRight,
2530
3694
  depthFrom,
2531
3695
  depthTo,
3696
+ totalDepth,
3697
+ repeatedTo: state.repeatedTo,
2532
3698
  yScale: yScaleLocal,
2533
3699
  transition,
2534
3700
  tooltips,
@@ -2542,6 +3708,7 @@ var WellRenderer = class {
2542
3708
  groups
2543
3709
  };
2544
3710
  const inDepth = filterByDepth(depthFrom, depthTo);
3711
+ const currentPump = (0, import_utils10.getCurrentPump)(profile);
2545
3712
  const filteredConstruction = {
2546
3713
  ...constructionData,
2547
3714
  bore_hole: constructionData.bore_hole.filter(inDepth),
@@ -2549,7 +3716,8 @@ var WellRenderer = class {
2549
3716
  surface_case: constructionData.surface_case.filter(inDepth),
2550
3717
  well_case: constructionData.well_case.filter(inDepth),
2551
3718
  well_screen: constructionData.well_screen.filter(inDepth),
2552
- reduction: constructionData.reduction?.filter(inDepth) ?? []
3719
+ reduction: constructionData.reduction?.filter(inDepth) ?? [],
3720
+ centralizers: constructionData.centralizers?.filter(inDepth) ?? []
2553
3721
  };
2554
3722
  const zooming = (e) => {
2555
3723
  const transform = e.transform;
@@ -2565,33 +3733,14 @@ var WellRenderer = class {
2565
3733
  if (!d) return null;
2566
3734
  return transform.k * spanH(d);
2567
3735
  });
2568
- surfaceCaseGroup.selectAll(`g.${this.classes.surfaceCase.rect}`).each((d, i, nodes) => {
2569
- if (!d) return;
2570
- const sc = d;
2571
- const g = d37.select(nodes[i]);
2572
- const y = transform.applyY(spanY(sc));
2573
- const h = transform.k * spanH(sc);
2574
- g.select(".surface-case-fill").attr("y", y).attr("height", h);
2575
- g.selectAll(".surface-case-side").attr("y1", y).attr("y2", y + h);
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
- });
3736
+ layoutConstruction(zoomedCtx);
3737
+ drawCementPad(zoomedCtx, constructionData.cement_pad);
2589
3738
  fracturesGroup.selectAll(`g.${this.classes.fractures.item}`).attr("transform", (d) => {
2590
3739
  if (!d) return null;
2591
3740
  const f = d;
2592
3741
  const cx = pocoCenterX;
2593
3742
  const cy = transform.applyY(yScaleLocal(f.depth));
2594
- return `translate(0,${cy}) rotate(${f.dip},${cx},0)`;
3743
+ return `translate(0,${cy}) rotate(${fractureDip(f.dip)},${cx},0)`;
2595
3744
  });
2596
3745
  drawCaves(zoomedCtx, geologicData.caves?.filter(inDepth) ?? []);
2597
3746
  drawUnitLabels(zoomedCtx, profile.lithology?.filter(inDepth) ?? []);
@@ -2602,10 +3751,17 @@ var WellRenderer = class {
2602
3751
  ) ?? [],
2603
3752
  caves: profile.caves?.filter(inDepth) ?? []
2604
3753
  });
3754
+ drawPump(zoomedCtx, currentPump);
3755
+ drawCentralizers(zoomedCtx, filteredConstruction);
2605
3756
  drawConstructionLabels(zoomedCtx, {
2606
3757
  well_case: constructionData.well_case.filter(inDepth),
2607
3758
  well_screen: constructionData.well_screen.filter(inDepth)
2608
3759
  });
3760
+ drawMarkers(zoomedCtx, {
3761
+ lithology: profile.lithology ?? [],
3762
+ fractures: profile.fractures ?? []
3763
+ });
3764
+ drawOverlap(zoomedCtx);
2609
3765
  drawHighlights(zoomedCtx, highlights);
2610
3766
  this.onZoom?.(transform.k);
2611
3767
  };
@@ -2628,10 +3784,19 @@ var WellRenderer = class {
2628
3784
  ) ?? []
2629
3785
  );
2630
3786
  drawConstructive(ctx, filteredConstruction);
3787
+ drawPump(ctx, currentPump);
3788
+ drawCentralizers(ctx, filteredConstruction);
2631
3789
  drawConstructionLabels(ctx, {
2632
3790
  well_case: filteredConstruction.well_case,
2633
3791
  well_screen: filteredConstruction.well_screen
2634
3792
  });
3793
+ drawMarkers(ctx, {
3794
+ lithology: profile.lithology ?? [],
3795
+ fractures: profile.fractures ?? []
3796
+ });
3797
+ drawOverview(ctx, profile.lithology ?? []);
3798
+ drawColumnHeaders(ctx);
3799
+ drawOverlap(ctx);
2635
3800
  svg.select(`#${state.clipRectId}`).attr("y", 0).attr("height", svgHeight);
2636
3801
  drawHighlights(ctx, highlights);
2637
3802
  };
@@ -2647,7 +3812,7 @@ var WellRenderer = class {
2647
3812
  }
2648
3813
  const { zoom: zoomEnabled, pan: panEnabled } = this.renderConfig;
2649
3814
  if (zoomEnabled || panEnabled) {
2650
- const zoomNode = d37.zoom();
3815
+ const zoomNode = d310.zoom();
2651
3816
  if (zoomEnabled || panEnabled) zoomNode.on("zoom", zooming);
2652
3817
  zoomNode.scaleExtent(
2653
3818
  zoomEnabled ? [
@@ -2663,7 +3828,7 @@ var WellRenderer = class {
2663
3828
  if (initialZoom !== 1) {
2664
3829
  asSvgElement(svg).call(
2665
3830
  zoomNode.transform,
2666
- d37.zoomIdentity.scale(initialZoom)
3831
+ d310.zoomIdentity.scale(initialZoom)
2667
3832
  );
2668
3833
  }
2669
3834
  }
@@ -2678,10 +3843,13 @@ var WellRenderer = class {
2678
3843
  WellRenderer,
2679
3844
  applyRenderLocale,
2680
3845
  drawWellLegend,
3846
+ drawWellLegendSymbol,
2681
3847
  formatDiameter,
2682
3848
  formatLength,
2683
3849
  getDiameterUnit,
2684
3850
  getLengthUnit,
3851
+ getProfileTopDepth,
3852
+ getWellLegendItems,
2685
3853
  importFgdcTextures,
2686
3854
  resolveRenderLabel
2687
3855
  });