@welldot/render 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
@@ -387,6 +390,7 @@ var RENDER_LABELS = {
387
390
  wellScreen: { pt: "Filtro", en: "Screen" },
388
391
  reduction: { pt: "Redu\xE7\xE3o", en: "Reduction" },
389
392
  centralizer: { pt: "Centralizador", en: "Centralizer" },
393
+ pump: { pt: "Bomba e edutor", en: "Pump and riser" },
390
394
  cementPad: { pt: "Laje de cimento", en: "Cement pad" },
391
395
  conflict: { pt: "Conflito", en: "Conflict" }
392
396
  }
@@ -410,7 +414,9 @@ var RENDER_LABELS = {
410
414
  wellCase: { title: { pt: "REVESTIMENTO", en: "CASING" } },
411
415
  wellScreen: {
412
416
  title: { pt: "FILTROS", en: "SCREENS" },
413
- slot: { pt: "Ranhura:", en: "Slot:" }
417
+ slot: { pt: "Ranhura:", en: "Slot:" },
418
+ material: { pt: "Material:", en: "Material:" },
419
+ slotType: { pt: "Tipo:", en: "Type:" }
414
420
  },
415
421
  reduction: { title: { pt: "REDU\xC7\xC3O", en: "REDUCTION" } },
416
422
  centralizer: {
@@ -418,6 +424,28 @@ var RENDER_LABELS = {
418
424
  depth: { pt: "Profundidade:", en: "Depth:" },
419
425
  spacing: { pt: "Espa\xE7amento:", en: "Spacing:" }
420
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
+ },
421
449
  conflict: { title: { pt: "CONFLITO", en: "CONFLICT" } },
422
450
  fracture: {
423
451
  title: { pt: "FRATURA", en: "FRACTURE" },
@@ -443,6 +471,19 @@ var RENDER_LABELS = {
443
471
  fractureWater: { pt: "fratura aberta", en: "open fracture" },
444
472
  cave: { pt: "caverna", en: "cave" },
445
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
+ }
446
487
  }
447
488
  };
448
489
  function resolveGroup(group, locale) {
@@ -464,6 +505,7 @@ function resolveTooltipLabels(locale) {
464
505
  wellScreen: resolveGroup(t.wellScreen, locale),
465
506
  reduction: resolveGroup(t.reduction, locale),
466
507
  centralizer: resolveGroup(t.centralizer, locale),
508
+ pump: resolveGroup(t.pump, locale),
467
509
  conflict: resolveGroup(t.conflict, locale),
468
510
  fracture: resolveGroup(t.fracture, locale),
469
511
  cementPad: resolveGroup(t.cementPad, locale),
@@ -485,7 +527,19 @@ function applyRenderLocale(config, locale) {
485
527
  tooltipLabels: resolveTooltipLabels(locale),
486
528
  labels: {
487
529
  ...config.labels,
488
- 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)
489
543
  }
490
544
  };
491
545
  }
@@ -511,7 +565,7 @@ var DEFAULT_WELL_THEME = {
511
565
  crackStrokeWidth: 0.7
512
566
  }
513
567
  },
514
- cementPad: { stroke: "#303030", strokeWidth: 2 },
568
+ cementPad: { stroke: "#303030", strokeWidth: 1 },
515
569
  boreHole: {
516
570
  fill: "#ffffff",
517
571
  stroke: "#191919",
@@ -520,11 +574,24 @@ var DEFAULT_WELL_THEME = {
520
574
  strokeWidth: 1.2
521
575
  },
522
576
  surfaceCase: { stroke: "#000000", strokeWidth: 4 },
523
- holeFill: { stroke: "#303030", strokeWidth: 2 },
577
+ holeFill: { stroke: "#303030", strokeWidth: 1.2 },
524
578
  wellCase: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
525
- wellScreen: { stroke: "#303030", strokeWidth: 2 },
579
+ wellScreen: {
580
+ stroke: "#303030",
581
+ strokeWidth: 2,
582
+ endStrokeWidth: 0.75,
583
+ endDasharray: "3, 2"
584
+ },
526
585
  reduction: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
527
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
+ },
528
595
  conflict: { stroke: "#e52117", strokeWidth: 4 },
529
596
  unitLabels: {
530
597
  geologicFill: "#f0f0f0",
@@ -560,6 +627,39 @@ var DEFAULT_WELL_THEME = {
560
627
  headerFontWeight: 600,
561
628
  bodyFontWeight: 400
562
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
+ },
563
663
  legend: {
564
664
  borderStrokeWidth: 0.8,
565
665
  fractureStrokeWidth: 1.5,
@@ -617,9 +717,17 @@ var STATIC_RENDER_CONFIG = {
617
717
  }
618
718
  },
619
719
  construction: {
620
- cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
720
+ cementPad: { widthRatio: 1.4, height: 6 },
721
+ casingShoe: { active: true, size: 4 },
722
+ screenEnds: { active: true },
621
723
  surfaceCase: { diameterPaddingRatio: 0.1 },
622
- centralizer: { markerHalfHeight: 4 }
724
+ centralizer: { markerHalfHeight: 4 },
725
+ pump: {
726
+ active: true,
727
+ bodyHeight: 22,
728
+ bodyWidthRatio: 0.6,
729
+ riserWidthRatio: 0.3
730
+ }
623
731
  },
624
732
  tooltipLabels: resolveTooltipLabels("pt"),
625
733
  labels: {
@@ -632,7 +740,11 @@ var STATIC_RENDER_CONFIG = {
632
740
  stackingLineHeight: 10,
633
741
  stackingGap: 0,
634
742
  fractureLabelLeaderGap: 5,
635
- lithology: true
743
+ lithology: true,
744
+ continuation: {
745
+ active: false,
746
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, "pt")
747
+ }
636
748
  },
637
749
  unitLabels: {
638
750
  active: true,
@@ -691,10 +803,21 @@ var STATIC_RENDER_CONFIG = {
691
803
  wellScreen: "Filtro",
692
804
  reduction: "Redu\xE7\xE3o",
693
805
  centralizer: "Centralizador",
806
+ pump: "Bomba e edutor",
694
807
  cementPad: "Laje de cimento",
695
808
  conflict: "Conflito"
696
809
  }
697
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
+ },
698
821
  highlights: DEFAULT_HIGHLIGHT_CONFIG
699
822
  };
700
823
  var INTERACTIVE_RENDER_CONFIG = {
@@ -730,9 +853,17 @@ var INTERACTIVE_RENDER_CONFIG = {
730
853
  }
731
854
  },
732
855
  construction: {
733
- cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
856
+ cementPad: { widthRatio: 1.4, height: 6 },
857
+ casingShoe: { active: true, size: 4 },
858
+ screenEnds: { active: true },
734
859
  surfaceCase: { diameterPaddingRatio: 0.1 },
735
- centralizer: { markerHalfHeight: 4 }
860
+ centralizer: { markerHalfHeight: 4 },
861
+ pump: {
862
+ active: true,
863
+ bodyHeight: 22,
864
+ bodyWidthRatio: 0.6,
865
+ riserWidthRatio: 0.3
866
+ }
736
867
  },
737
868
  tooltipLabels: resolveTooltipLabels("pt"),
738
869
  labels: {
@@ -743,7 +874,11 @@ var INTERACTIVE_RENDER_CONFIG = {
743
874
  descriptionXOffset: 78,
744
875
  descriptionMaxWidth: 226,
745
876
  stackingLineHeight: 11,
746
- stackingGap: 0
877
+ stackingGap: 0,
878
+ continuation: {
879
+ active: false,
880
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, "pt")
881
+ }
747
882
  },
748
883
  unitLabels: {
749
884
  active: false,
@@ -801,10 +936,21 @@ var INTERACTIVE_RENDER_CONFIG = {
801
936
  wellScreen: "Filtro",
802
937
  reduction: "Redu\xE7\xE3o",
803
938
  centralizer: "Centralizador",
939
+ pump: "Bomba e edutor",
804
940
  cementPad: "Laje de cimento",
805
941
  conflict: "Conflito"
806
942
  }
807
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
+ },
808
954
  highlights: DEFAULT_HIGHLIGHT_CONFIG
809
955
  };
810
956
 
@@ -821,6 +967,8 @@ var import_defu2 = require("defu");
821
967
 
822
968
  // src/configs/render.textures.ts
823
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)}`;
824
972
  function createWellTextures(cfg) {
825
973
  const pad = {
826
974
  background: "#ffffff",
@@ -868,7 +1016,9 @@ function createWellTextures(cfg) {
868
1016
  ...cfg?.surface_case
869
1017
  };
870
1018
  return {
871
- 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),
872
1022
  conflict: import_textures.default.lines().heavier().stroke(conflict.stroke),
873
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),
874
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),
@@ -880,8 +1030,8 @@ function createWellTextures(cfg) {
880
1030
  }
881
1031
 
882
1032
  // src/Renderer.ts
883
- var import_core = require("@welldot/core");
884
- var import_utils7 = require("@welldot/utils");
1033
+ var import_core4 = require("@welldot/core");
1034
+ var import_utils10 = require("@welldot/utils");
885
1035
 
886
1036
  // src/configs/render.classnames.ts
887
1037
  var DEFAULT_COMPONENTS_CLASS_NAMES = {
@@ -943,6 +1093,10 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
943
1093
  group: "centralizer",
944
1094
  item: "centralizer-marker"
945
1095
  },
1096
+ pump: {
1097
+ group: "pump",
1098
+ item: "pump-item"
1099
+ },
946
1100
  reduction: {
947
1101
  group: "reduction",
948
1102
  item: "reduction-poly"
@@ -974,6 +1128,26 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
974
1128
  caveFill: "cave-fill",
975
1129
  constructionRect: "construction-rect"
976
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
+ },
977
1151
  highlights: {
978
1152
  geologicGroup: "wp-highlights-geologic",
979
1153
  constructionGroup: "wp-highlights-construction",
@@ -988,6 +1162,156 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
988
1162
  // src/utils/render.utils.ts
989
1163
  var import_textures2 = __toESM(require("textures"), 1);
990
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
+
991
1315
  // src/utils/d3.utils.ts
992
1316
  function mergeEnter(enter, update) {
993
1317
  return enter.merge(update);
@@ -1058,6 +1382,7 @@ var makeSeededPrng = (seed, scramble = 6271) => {
1058
1382
  // src/utils/tooltips.utils.ts
1059
1383
  var import_d3_tip = __toESM(require("d3-tip"), 1);
1060
1384
  var import_sanitize_html = __toESM(require("sanitize-html"), 1);
1385
+ var import_core2 = require("@welldot/core");
1061
1386
  var import_utils2 = require("@welldot/utils");
1062
1387
  var esc = (v) => (0, import_sanitize_html.default)(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
1063
1388
  var _tooltipCache = /* @__PURE__ */ new WeakMap();
@@ -1068,6 +1393,7 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1068
1393
  if (cached && cached.signature === signature) {
1069
1394
  return cached.tooltips;
1070
1395
  }
1396
+ const material = (value) => (0, import_core2.getVocabLabel)(import_core2.CONSTRUCTION_MATERIALS, value, locale);
1071
1397
  const tipsText = {
1072
1398
  geology: (_, d) => `
1073
1399
  <span class="${customClasses.tooltip.title}">${labels.geology.title}</span>
@@ -1100,14 +1426,15 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1100
1426
  <span class="${customClasses.tooltip.secondaryInfo}">
1101
1427
  <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}
1102
1428
  </span>
1103
- <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>
1104
1430
  `,
1105
1431
  wellScreen: (_, d) => `
1106
1432
  <span class="${customClasses.tooltip.title}">${labels.wellScreen.title}</span>
1107
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>
1108
1434
  <span class="${customClasses.tooltip.secondaryInfo}">
1109
1435
  <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
1110
- <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>` : ""}
1111
1438
  <span class="${customClasses.tooltip.secondaryInfo}">
1112
1439
  <strong>${labels.wellScreen.slot}</strong> ${esc((0, import_utils2.formatNumber)(d.screen_slot, { maximumFractionDigits: 2, locale }))} mm
1113
1440
  </span>
@@ -1118,17 +1445,29 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1118
1445
  <span class="${customClasses.tooltip.secondaryInfo}">
1119
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))}
1120
1447
  </span>
1121
- <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>
1122
1449
  `,
1123
1450
  centralizer: (_, d) => `
1124
1451
  <span class="${customClasses.tooltip.title}">${labels.centralizer.title}</span>
1125
1452
  <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.centralizer.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
1126
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>` : ""}
1127
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>` : ""}
1128
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</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>
1129
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>` : ""}
1130
1457
  ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
1131
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
+ },
1132
1471
  conflict: (_, d) => `
1133
1472
  <span class="${customClasses.tooltip.title}">${labels.conflict.title}</span>
1134
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>
@@ -1139,14 +1478,14 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1139
1478
  <span class="${customClasses.tooltip.title}">${title}</span>
1140
1479
  <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.fracture.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
1141
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>` : ""}
1142
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.dip}</strong> ${esc(d.dip)}\xB0</span>
1143
- <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>` : ""}
1144
1483
  ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
1145
1484
  `;
1146
1485
  },
1147
1486
  cementPad: (_, d) => `
1148
1487
  <span class="${customClasses.tooltip.title}">${labels.cementPad.title}</span>
1149
- <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>
1150
1489
  <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cementPad.thickness}</strong>
1151
1490
  ${esc(formatLength(d.thickness, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
1152
1491
  <span class="${customClasses.tooltip.secondaryInfo}">
@@ -1184,6 +1523,17 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
1184
1523
  };
1185
1524
 
1186
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
+ }
1187
1537
  var _fgdcTextures = {};
1188
1538
  var _fgdcTexturesPromise = null;
1189
1539
  var preloadFgdcTextures = () => {
@@ -1292,14 +1642,23 @@ function drawAnnotationLabels(ctx, data) {
1292
1642
  }
1293
1643
  const items = [];
1294
1644
  if (active.has("lithology")) {
1645
+ const continuation = s.continuation;
1295
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;
1296
1649
  items.push({
1297
1650
  sortDepth: d.from,
1298
1651
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
1299
1652
  originX: ctx.geoXRight,
1300
1653
  originY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
1301
- header: `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`,
1302
- 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" } : {}
1303
1662
  });
1304
1663
  });
1305
1664
  }
@@ -1312,7 +1671,8 @@ function drawAnnotationLabels(ctx, data) {
1312
1671
  originX: ctx.pocoCenterX,
1313
1672
  originY: ctx.yScale(d.depth),
1314
1673
  header: `${formatLength(d.depth, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
1315
- description: d.description || ""
1674
+ description: d.description || "",
1675
+ ...show.has("rules") ? { rule: "dashed" } : {}
1316
1676
  });
1317
1677
  });
1318
1678
  }
@@ -1332,14 +1692,15 @@ function drawAnnotationLabels(ctx, data) {
1332
1692
  }
1333
1693
  items.sort((a, b) => a.sortDepth - b.sortDepth);
1334
1694
  const labelPositions = [];
1335
- let currY = 0;
1695
+ let currY = ctx.repeatedTo != null ? s.stackingLineHeight + 2 : 0;
1336
1696
  const charsPerLine = Math.max(
1337
1697
  1,
1338
1698
  Math.floor(s.descriptionMaxWidth / (st.fontSize * 0.6))
1339
1699
  );
1340
1700
  items.forEach((item) => {
1341
- const descLines = Math.ceil((item.description.length || 1) / charsPerLine);
1342
- 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;
1343
1704
  const yLabel = Math.max(item.baseY, currY + s.stackingGap);
1344
1705
  currY = yLabel + estH;
1345
1706
  labelPositions.push({ item, yLabel, estH });
@@ -1356,16 +1717,41 @@ function drawAnnotationLabels(ctx, data) {
1356
1717
  if (show.has("description")) {
1357
1718
  const lineH = s.stackingLineHeight;
1358
1719
  const bgFill = st.annotationBg ?? "white";
1720
+ const swatchFill = labelPositions.some((p) => p.item.swatch) ? getLithologyFill(data.lithology, ctx.svg, ctx.theme.lithologyTexture) : null;
1359
1721
  labelPositions.forEach(({ item, yLabel, estH }) => {
1360
1722
  const labelG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.label).attr("transform", `translate(${labelX},${yLabel})`);
1361
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");
1362
- 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
+ }
1363
1745
  if (item.description) {
1364
1746
  const lines = wrapText(item.description, charsPerLine);
1365
- 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);
1366
1748
  lines.forEach((line, i) => {
1367
1749
  bodyEl.append("tspan").attr("x", 0).attr("dy", i === 0 ? 0 : lineH).text(line);
1368
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);
1369
1755
  }
1370
1756
  });
1371
1757
  }
@@ -1386,7 +1772,6 @@ var makePointKey = (type) => (d, i) => {
1386
1772
 
1387
1773
  // src/renderers/caves.renderer.ts
1388
1774
  function drawCaves(ctx, data) {
1389
- if (!data.length) return;
1390
1775
  const {
1391
1776
  groups: { cavesGroup },
1392
1777
  geoXLeft,
@@ -1396,7 +1781,7 @@ function drawCaves(ctx, data) {
1396
1781
  yScale,
1397
1782
  renderConfig,
1398
1783
  theme,
1399
- textures: textures3,
1784
+ textures: textures4,
1400
1785
  classes,
1401
1786
  tooltips
1402
1787
  } = ctx;
@@ -1433,7 +1818,7 @@ function drawCaves(ctx, data) {
1433
1818
  const botPath = botCut ? `M ${geoXLeft.toFixed(1)},${yBot.toFixed(1)} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath(botPts);
1434
1819
  const botReversed = botCut ? `L ${geoXLeft.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath([...botPts].reverse(), { omitMoveTo: true });
1435
1820
  const closedPath = `${topPath} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}${botReversed} L ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} Z`;
1436
- const caveTexture = cave.water_intake ? textures3.cave_wet : textures3.cave_dry;
1821
+ const caveTexture = cave.water_intake ? textures4.cave_wet : textures4.cave_dry;
1437
1822
  const caveStroke = cave.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
1438
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");
1439
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");
@@ -1445,9 +1830,6 @@ function drawCaves(ctx, data) {
1445
1830
  // src/renderers/centralizers.renderer.ts
1446
1831
  var d32 = __toESM(require("d3"), 1);
1447
1832
  var import_utils3 = require("@welldot/utils");
1448
- function diameterAt(items, depth) {
1449
- return items.find((i) => depth >= i.from && depth <= i.to)?.diameter;
1450
- }
1451
1833
  function drawCentralizers(ctx, data) {
1452
1834
  const group = ctx.groups.centralizerGroup;
1453
1835
  const cls = ctx.classes.centralizer;
@@ -1484,8 +1866,108 @@ function drawCentralizers(ctx, data) {
1484
1866
  });
1485
1867
  }
1486
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
+
1487
1968
  // src/renderers/construction-labels.renderer.ts
1488
1969
  var d33 = __toESM(require("d3"), 1);
1970
+ var import_core3 = require("@welldot/core");
1489
1971
  var import_utils4 = require("@welldot/utils");
1490
1972
  function drawConstructionLabels(ctx, data) {
1491
1973
  if (!ctx.renderConfig.constructionLabels.active) return;
@@ -1494,16 +1976,18 @@ function drawConstructionLabels(ctx, data) {
1494
1976
  const clct = ctx.theme.constructionLabels;
1495
1977
  const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter, ctx.locale);
1496
1978
  const dUnit = getDiameterUnit(ctx.units.diameter, ctx.locale);
1979
+ const material = (value) => (0, import_core3.getVocabLabel)(import_core3.CONSTRUCTION_MATERIALS, value, ctx.locale);
1497
1980
  const maxXValues = (0, import_utils4.getProfileDiamValues)(ctx.constructionData);
1498
1981
  const xScale = d33.scaleLinear().domain([0, d33.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
1499
1982
  const occupied = [];
1500
- const placeLabel = (d, text) => {
1983
+ const onRight = clc.side === "right";
1984
+ const continuation = ctx.renderConfig.labels.continuation;
1985
+ const placeLabel = (d, label) => {
1501
1986
  const clampedFrom = Math.max(d.from, ctx.depthFrom);
1502
1987
  const clampedTo = Math.min(d.to, ctx.depthTo);
1503
1988
  if (clampedFrom >= clampedTo) return;
1989
+ const text = continuation?.active && d.from < ctx.depthFrom ? `\u2191 ${label}` : label;
1504
1990
  const midY = ctx.yScale((clampedFrom + clampedTo) / 2);
1505
- const leftEdgeX = (ctx.POCO_CENTER - xScale(d.diameter)) / 2;
1506
- const labelRightX = leftEdgeX - clc.xOffset;
1507
1991
  const charW = clct.fontSize * 0.52;
1508
1992
  const lineH = clct.fontSize * 1.35;
1509
1993
  const vertPad = 6;
@@ -1520,16 +2004,17 @@ function drawConstructionLabels(ctx, data) {
1520
2004
  lines = [text];
1521
2005
  boxW = text.length * charW + 8;
1522
2006
  }
2007
+ const labelRightX = onRight ? (ctx.POCO_CENTER + ctx.POCO_WIDTH) / 2 + clc.xOffset + boxW : (ctx.POCO_CENTER - xScale(d.diameter)) / 2 - clc.xOffset;
1523
2008
  const labelH = lines.length * lineH + vertPad;
1524
- let labelY = midY - labelH / 2;
2009
+ let labelY = onRight ? ctx.yScale(clampedFrom) + 1 : midY - labelH / 2;
1525
2010
  for (const pos of occupied) {
1526
2011
  if (labelY < pos.bot && labelY + labelH > pos.top) labelY = pos.bot + 2;
1527
2012
  }
1528
2013
  occupied.push({ top: labelY, bot: labelY + labelH });
1529
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);
1530
- 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");
1531
2016
  lines.forEach((line, i) => {
1532
- 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);
1533
2018
  });
1534
2019
  };
1535
2020
  let lastDiam = 0;
@@ -1542,7 +2027,7 @@ function drawConstructionLabels(ctx, data) {
1542
2027
  }).forEach(
1543
2028
  (d) => placeLabel(
1544
2029
  d,
1545
- `${clc.labels.wellCasePrefix} ${fmtD(d.diameter)}${dUnit} ${d.type}`
2030
+ `${clc.labels.wellCasePrefix} ${fmtD(d.diameter)}${dUnit} ${material(d.type)}`
1546
2031
  )
1547
2032
  );
1548
2033
  lastDiam = 0;
@@ -1555,7 +2040,7 @@ function drawConstructionLabels(ctx, data) {
1555
2040
  }).forEach(
1556
2041
  (d) => placeLabel(
1557
2042
  d,
1558
- `${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}`
1559
2044
  )
1560
2045
  );
1561
2046
  }
@@ -1563,110 +2048,307 @@ function drawConstructionLabels(ctx, data) {
1563
2048
  // src/renderers/construction.renderer.ts
1564
2049
  var d34 = __toESM(require("d3"), 1);
1565
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
+ }
1566
2249
  function drawConstructive(ctx, data) {
1567
2250
  const { getHeight, getYPos } = getYAxisFunctions(ctx.yScale, {
1568
2251
  from: ctx.depthFrom,
1569
2252
  to: ctx.depthTo
1570
2253
  });
1571
- const maxXValues = (0, import_utils5.getProfileDiamValues)(ctx.constructionData);
1572
- const maxXValueConstruction = d34.max(maxXValues) || 0;
1573
- const xScale = d34.scaleLinear().domain([0, maxXValueConstruction]).range([0, ctx.POCO_WIDTH]);
1574
- const rcc = ctx.renderConfig.construction;
1575
- const yPos = getYPos;
1576
- ctx.groups.constructionGroup.selectAll(`.${ctx.classes.cementPad.item}`).remove();
1577
- if (data.cement_pad && data.cement_pad.thickness && ctx.depthFrom === 0) {
1578
- const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad], () => "cement_pad");
1579
- cementPad.exit().remove();
1580
- const newCementPad = cementPad.enter().append("rect").attr("class", ctx.classes.cementPad.item).attr(
1581
- "x",
1582
- (d) => (ctx.POCO_CENTER - xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)) / 2
1583
- ).attr("y", (d) => {
1584
- return ctx.yScale(0) - ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1585
- }).attr(
1586
- "width",
1587
- (d) => xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)
1588
- ).attr("height", (d) => {
1589
- return ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1590
- }).attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth);
1591
- newCementPad.on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1592
- }
1593
- const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, makeIntervalKey("bore_hole"));
1594
- hole.exit().transition(ctx.transition).attr("height", 0).remove();
1595
- 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);
1596
- const mergedHole = mergeEnter(newHole, hole).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1597
- withTransition(mergedHole, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1598
- const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case, makeIntervalKey("surface_case"));
1599
- surfaceCaseGs.exit().transition(ctx.transition).style("opacity", 0).remove();
1600
- const newSC = surfaceCaseGs.enter().append("g").attr("class", ctx.classes.surfaceCase.rect).on("mouseover", ctx.tooltips.surfaceCase.show).on("mouseout", ctx.tooltips.surfaceCase.hide);
1601
- newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none").attr("y", yPos).attr("height", 0);
1602
- newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1603
- newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1604
- const mergedSC = mergeEnter(newSC, surfaceCaseGs);
1605
- mergedSC.each((d, i, nodes) => {
1606
- const x = (ctx.POCO_CENTER - xScale(
1607
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1608
- )) / 2;
1609
- const w = xScale(
1610
- 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()
1611
2280
  );
1612
- const g = d34.select(nodes[i]);
1613
- g.select(".surface-case-fill").attr("x", x).attr("width", w).attr("fill", ctx.textures.surface_case.url());
1614
- const sideNodes = g.selectAll(".surface-case-side").nodes();
1615
- d34.select(sideNodes[0]).attr("x1", x).attr("x2", x);
1616
- d34.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);
1617
2282
  });
1618
- withTransition(
1619
- mergedSC.select(".surface-case-fill"),
1620
- ctx.transition
1621
- ).attr("y", (d) => getYPos(d)).attr("height", (d) => getHeight(d));
1622
- mergedSC.selectAll(".surface-case-side").attr("y1", (d) => getYPos(d)).attr(
1623
- "y2",
1624
- (d) => getYPos(d) + getHeight(d)
1625
- );
1626
- const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, makeIntervalKey("hole_fill"));
1627
- holeFill.exit().transition(ctx.transition).attr("height", 0).remove();
1628
- 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);
1629
- const mergedHoleFill = mergeEnter(newHoleFill, holeFill).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1630
- withTransition(mergedHoleFill, ctx.transition).attr("y", getYPos).attr("height", getHeight).attr("fill", (d) => ctx.textures[d.type].url());
1631
- const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case, makeIntervalKey("well_case"));
1632
- wellCase.exit().transition(ctx.transition).attr("height", 0).remove();
1633
- 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);
1634
- const mergedWellCase = mergeEnter(newWellCase, wellCase).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1635
- withTransition(mergedWellCase, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1636
- const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen, makeIntervalKey("well_screen"));
1637
- wellScreen.exit().transition(ctx.transition).attr("height", 0).remove();
1638
- 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);
1639
- const mergedWellScreen = mergeEnter(newWellScreen, wellScreen).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1640
- withTransition(mergedWellScreen, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1641
- const reductionPoints = (d, yTop, yBot) => {
1642
- const xTopLeft = (ctx.POCO_CENTER - xScale(d.diam_from)) / 2;
1643
- const xTopRight = (ctx.POCO_CENTER + xScale(d.diam_from)) / 2;
1644
- const xBotLeft = (ctx.POCO_CENTER - xScale(d.diam_to)) / 2;
1645
- const xBotRight = (ctx.POCO_CENTER + xScale(d.diam_to)) / 2;
1646
- return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
1647
- };
1648
- 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"));
1649
2329
  reduction.exit().transition(ctx.transition).style("opacity", 0).remove();
1650
- const collapsedPoints = (d) => {
1651
- const r = d;
1652
- const y = getYPos(r);
1653
- return reductionPoints(r, y, y);
1654
- };
1655
- 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);
1656
- const mergedReduction = mergeEnter(newReduction, reduction);
1657
- withTransition(mergedReduction, ctx.transition).attr(
1658
- "points",
1659
- (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
1660
2343
  );
1661
- const conflictAreas = [];
1662
- conflictAreas.push(...getConflictAreas(data.well_case, data.well_screen));
1663
- conflictAreas.push(...getConflictAreas(data.well_screen, data.well_case));
1664
- const mergedConflicts = mergeConflicts(conflictAreas, 1);
1665
- 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"));
1666
2347
  conflict.exit().transition(ctx.transition).attr("height", 0).remove();
1667
- 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);
1668
2349
  const mergedConflict = mergeEnter(newConflict, conflict).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1669
- 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);
1670
2352
  }
1671
2353
 
1672
2354
  // src/renderers/fractures.renderer.ts
@@ -1682,7 +2364,6 @@ function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1682
2364
  return pts;
1683
2365
  }
1684
2366
  function drawFractures(ctx, data) {
1685
- if (!data.length) return;
1686
2367
  const {
1687
2368
  groups: { fracturesGroup },
1688
2369
  POCO_WIDTH,
@@ -1704,7 +2385,7 @@ function drawFractures(ctx, data) {
1704
2385
  const merged = mergeEnter(entered, groups);
1705
2386
  merged.attr(
1706
2387
  "transform",
1707
- (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fracture.dip},${pocoCenterX},0)`
2388
+ (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fractureDip(fracture.dip)},${pocoCenterX},0)`
1708
2389
  );
1709
2390
  merged.each(function(fracture) {
1710
2391
  const g = d35.select(this);
@@ -1849,24 +2530,26 @@ function drawHighlights(ctx, hl) {
1849
2530
  const maxXVals = (0, import_utils6.getProfileDiamValues)(ctx.constructionData);
1850
2531
  const xScaleConstr = d36.scaleLinear().domain([0, d36.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1851
2532
  const constructionConfig = ctx.renderConfig.construction;
1852
- if (hl.cement_pad && ctx.depthFrom === 0 && ctx.constructionData.cement_pad?.thickness) {
1853
- const cp = ctx.constructionData.cement_pad;
1854
- const cpPadWidth = cp.width * constructionConfig.cementPad.widthMultiplier * 1e3 / 2;
1855
- const cpX = (ctx.POCO_CENTER - xScaleConstr(cpPadWidth)) / 2;
1856
- const cpY = ctx.yScale(0) - ctx.yScale(
1857
- 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
1858
2537
  );
1859
- const cpW = xScaleConstr(cpPadWidth);
1860
- const cpH = ctx.yScale(
1861
- 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)
1862
2545
  );
1863
2546
  drawHighlightItem(
1864
2547
  ctx,
1865
2548
  ctx.groups.highlightsConstructionGroup,
1866
- cpX,
1867
- cpY,
1868
- cpW,
1869
- cpH,
2549
+ pad.x,
2550
+ pad.y,
2551
+ pad.width,
2552
+ pad.height,
1870
2553
  hl.cement_pad.label
1871
2554
  );
1872
2555
  }
@@ -1942,8 +2625,12 @@ function drawHighlights(ctx, hl) {
1942
2625
  // src/renderers/legend.renderer.ts
1943
2626
  var d3module = __toESM(require("d3"), 1);
1944
2627
  var import_defu = require("defu");
2628
+ var import_utils7 = require("@welldot/utils");
1945
2629
  var d37 = { ...d3module };
1946
- function drawWellLegend(selector, profile, options = {}) {
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) {
1947
2634
  const fractures = profile.fractures ?? [];
1948
2635
  const caves = profile.caves ?? [];
1949
2636
  const boreHoles = profile.bore_hole ?? [];
@@ -1964,6 +2651,7 @@ function drawWellLegend(selector, profile, options = {}) {
1964
2651
  const hasWellScreen = wellScreens.length > 0;
1965
2652
  const hasReduction = reductions.length > 0;
1966
2653
  const hasCentralizer = (profile.centralizers ?? []).length > 0;
2654
+ const hasPump = (0, import_utils7.getCurrentPump)(profile)?.intake_depth !== void 0;
1967
2655
  const hasCementPad = !!profile.cement_pad?.thickness;
1968
2656
  const hasConflict = mergeConflicts(
1969
2657
  [
@@ -1972,222 +2660,314 @@ function drawWellLegend(selector, profile, options = {}) {
1972
2660
  ],
1973
2661
  1
1974
2662
  ).length > 0;
1975
- const cfg = (0, import_defu.defu)(
1976
- options.config ?? {},
1977
- INTERACTIVE_RENDER_CONFIG.legend
1978
- );
1979
- const theme = (0, import_defu.defu)(options.theme ?? {}, DEFAULT_WELL_THEME);
1980
- const cls = options.classNames ? { ...DEFAULT_COMPONENTS_CLASS_NAMES.legend, ...options.classNames } : DEFAULT_COMPONENTS_CLASS_NAMES.legend;
1981
2663
  const items = [];
1982
2664
  if (hasSimple)
1983
2665
  items.push({
1984
2666
  kind: "fracture",
1985
- label: cfg.labels.fractureSingle,
2667
+ key: "fractureSingle",
2668
+ label: labels.fractureSingle,
1986
2669
  swarm: false,
1987
2670
  water_intake: false
1988
2671
  });
1989
2672
  if (hasSwarm)
1990
2673
  items.push({
1991
2674
  kind: "fracture",
1992
- label: cfg.labels.fractureSwarm,
2675
+ key: "fractureSwarm",
2676
+ label: labels.fractureSwarm,
1993
2677
  swarm: true,
1994
2678
  water_intake: false
1995
2679
  });
1996
2680
  if (hasWaterFx)
1997
2681
  items.push({
1998
2682
  kind: "fracture",
1999
- label: cfg.labels.fractureWater,
2683
+ key: "fractureWater",
2684
+ label: labels.fractureWater,
2000
2685
  swarm: false,
2001
2686
  water_intake: true
2002
2687
  });
2003
2688
  if (hasCaveDry)
2004
2689
  items.push({
2005
2690
  kind: "cave",
2006
- label: cfg.labels.caveDry,
2691
+ key: "caveDry",
2692
+ label: labels.caveDry,
2007
2693
  water_intake: false
2008
2694
  });
2009
2695
  if (hasCaveWet)
2010
- 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
+ });
2011
2702
  if (hasCementPad)
2012
2703
  items.push({
2013
2704
  kind: "construction",
2014
- label: cfg.labels.cementPad,
2705
+ key: "cementPad",
2706
+ label: labels.cementPad,
2015
2707
  subKind: "cementPad"
2016
2708
  });
2017
2709
  if (hasBoreHole)
2018
2710
  items.push({
2019
2711
  kind: "construction",
2020
- label: cfg.labels.boreHole,
2712
+ key: "boreHole",
2713
+ label: labels.boreHole,
2021
2714
  subKind: "boreHole"
2022
2715
  });
2023
2716
  if (hasSurfaceCase)
2024
2717
  items.push({
2025
2718
  kind: "construction",
2026
- label: cfg.labels.surfaceCase,
2719
+ key: "surfaceCase",
2720
+ label: labels.surfaceCase,
2027
2721
  subKind: "surfaceCase"
2028
2722
  });
2029
2723
  if (hasHoleFillGravel)
2030
2724
  items.push({
2031
2725
  kind: "construction",
2032
- label: cfg.labels.holeFillGravel,
2726
+ key: "holeFillGravel",
2727
+ label: labels.holeFillGravel,
2033
2728
  subKind: "holeFillGravel"
2034
2729
  });
2035
2730
  if (hasHoleFillSeal)
2036
2731
  items.push({
2037
2732
  kind: "construction",
2038
- label: cfg.labels.holeFillSeal,
2733
+ key: "holeFillSeal",
2734
+ label: labels.holeFillSeal,
2039
2735
  subKind: "holeFillSeal"
2040
2736
  });
2041
2737
  if (hasWellCase)
2042
2738
  items.push({
2043
2739
  kind: "construction",
2044
- label: cfg.labels.wellCase,
2740
+ key: "wellCase",
2741
+ label: labels.wellCase,
2045
2742
  subKind: "wellCase"
2046
2743
  });
2047
2744
  if (hasWellScreen)
2048
2745
  items.push({
2049
2746
  kind: "construction",
2050
- label: cfg.labels.wellScreen,
2747
+ key: "wellScreen",
2748
+ label: labels.wellScreen,
2051
2749
  subKind: "wellScreen"
2052
2750
  });
2053
2751
  if (hasReduction)
2054
2752
  items.push({
2055
2753
  kind: "construction",
2056
- label: cfg.labels.reduction,
2754
+ key: "reduction",
2755
+ label: labels.reduction,
2057
2756
  subKind: "reduction"
2058
2757
  });
2059
2758
  if (hasCentralizer)
2060
2759
  items.push({
2061
2760
  kind: "construction",
2062
- label: cfg.labels.centralizer,
2761
+ key: "centralizer",
2762
+ label: labels.centralizer,
2063
2763
  subKind: "centralizer"
2064
2764
  });
2765
+ if (hasPump)
2766
+ items.push({
2767
+ kind: "construction",
2768
+ key: "pump",
2769
+ label: labels.pump,
2770
+ subKind: "pump"
2771
+ });
2065
2772
  if (hasConflict)
2066
2773
  items.push({
2067
2774
  kind: "construction",
2068
- label: cfg.labels.conflict,
2775
+ key: "conflict",
2776
+ label: labels.conflict,
2069
2777
  subKind: "conflict"
2070
2778
  });
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);
2071
2939
  const svgEl = d37.select(selector);
2072
2940
  svgEl.selectAll("*").remove();
2073
2941
  if (items.length === 0) {
2074
2942
  svgEl.attr("width", 0).attr("height", 0);
2075
2943
  return;
2076
2944
  }
2077
- const legendTextures = createWellTextures(options.textures);
2078
- 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));
2079
2948
  const itemsPerRow = cfg.maxWidth ? Math.max(1, Math.floor((cfg.maxWidth - cfg.padding * 2) / cfg.itemWidth)) : items.length;
2080
2949
  const numRows = Math.ceil(items.length / itemsPerRow);
2081
2950
  const rowWidth = Math.min(items.length, itemsPerRow) * cfg.itemWidth;
2082
2951
  const totalWidth = cfg.padding * 2 + rowWidth;
2083
2952
  const totalHeight = numRows * cfg.height;
2084
2953
  svgEl.attr("width", totalWidth).attr("height", totalHeight);
2085
- const RC = "round";
2086
2954
  const g = svgEl.append("g").attr("transform", `translate(${cfg.padding}, 2)`);
2087
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);
2088
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}:`);
2089
- const rw = 24;
2090
- const rh = 12;
2091
2957
  items.forEach((item, i) => {
2092
2958
  const col = i % itemsPerRow;
2093
2959
  const row = Math.floor(i / itemsPerRow);
2094
2960
  const rowSymY = row * cfg.height + (cfg.height - 4) / 2;
2095
2961
  const cx = col * cfg.itemWidth;
2096
2962
  const symG = g.append("g").attr("class", cls.item);
2097
- if (item.kind === "fracture") {
2098
- const color = item.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
2099
- if (item.swarm) {
2100
- [-4, 0, 4].forEach((offset) => {
2101
- symG.append("polyline").attr("class", cls.fracturePoly).attr(
2102
- "points",
2103
- `${cx},${rowSymY + offset} ${cx + 6},${rowSymY + offset - 1} ${cx + 12},${rowSymY + offset + 1} ${cx + 18},${rowSymY + offset - 0.5} ${cx + 24},${rowSymY + offset}`
2104
- ).attr("stroke", color).attr(
2105
- "stroke-width",
2106
- offset !== 0 ? theme.legend.fractureSideStrokeWidth : theme.legend.fractureStrokeWidth
2107
- ).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2108
- });
2109
- } else {
2110
- symG.append("polyline").attr("class", cls.fracturePoly).attr(
2111
- "points",
2112
- `${cx},${rowSymY} ${cx + 5},${rowSymY - 1.5} ${cx + 11},${rowSymY + 1} ${cx + 17},${rowSymY - 0.5} ${cx + 24},${rowSymY}`
2113
- ).attr("stroke", color).attr("stroke-width", theme.legend.fractureStrokeWidth).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2114
- }
2115
- } else if (item.kind === "cave") {
2116
- const color = item.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
2117
- const texture = item.water_intake ? legendTextures.cave_wet : legendTextures.cave_dry;
2118
- 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);
2119
- } else if (item.subKind === "surfaceCase") {
2120
- 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");
2121
- 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);
2122
- 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);
2123
- } else if (item.subKind === "centralizer") {
2124
- const pipeW = rw * 0.4;
2125
- const pipeL = cx + (rw - pipeW) / 2;
2126
- const pipeR = pipeL + pipeW;
2127
- const y1 = rowSymY - rh / 2;
2128
- const y2 = rowSymY + rh / 2;
2129
- const bowH = rh * 0.3;
2130
- for (const x of [pipeL, pipeR]) {
2131
- 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);
2132
- }
2133
- for (const [from, to] of [
2134
- [pipeL, cx],
2135
- [pipeR, cx + rw]
2136
- ]) {
2137
- symG.append("polyline").attr("class", cls.constructionRect).attr(
2138
- "points",
2139
- `${from},${rowSymY - bowH} ${to},${rowSymY} ${from},${rowSymY + bowH}`
2140
- ).attr("fill", theme.centralizer.fill).attr("stroke", theme.centralizer.stroke).attr("stroke-width", theme.centralizer.strokeWidth);
2141
- }
2142
- } else if (item.subKind === "reduction") {
2143
- const topW = rw * 0.5;
2144
- const y1 = rowSymY - rh / 2;
2145
- const y2 = rowSymY + rh / 2;
2146
- const midX = cx + rw / 2;
2147
- symG.append("polygon").attr("class", cls.constructionRect).attr(
2148
- "points",
2149
- `${midX - topW / 2},${y1} ${midX + topW / 2},${y1} ${cx + rw},${y2} ${cx},${y2}`
2150
- ).attr("fill", theme.reduction.fill).attr("stroke", theme.reduction.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
2151
- } else {
2152
- const symMap = {
2153
- boreHole: {
2154
- fill: theme.boreHole.fill,
2155
- stroke: theme.boreHole.stroke,
2156
- dasharray: theme.boreHole.strokeDasharray
2157
- },
2158
- holeFillGravel: {
2159
- fill: legendTextures.gravel_pack.url(),
2160
- stroke: theme.holeFill.stroke
2161
- },
2162
- holeFillSeal: {
2163
- fill: legendTextures.seal.url(),
2164
- stroke: theme.holeFill.stroke
2165
- },
2166
- wellCase: { fill: theme.wellCase.fill, stroke: theme.wellCase.stroke },
2167
- wellScreen: {
2168
- fill: legendTextures.well_screen.url(),
2169
- stroke: theme.wellScreen.stroke
2170
- },
2171
- cementPad: {
2172
- fill: legendTextures.pad.url(),
2173
- stroke: theme.cementPad.stroke
2174
- },
2175
- conflict: {
2176
- fill: legendTextures.conflict.url(),
2177
- stroke: theme.conflict.stroke
2178
- }
2179
- };
2180
- const sym = symMap[item.subKind];
2181
- 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);
2182
- if (sym.dasharray) symRect.attr("stroke-dasharray", sym.dasharray);
2183
- }
2963
+ drawLegendSymbol(symG, item, cx, rowSymY, style);
2184
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);
2185
2965
  });
2186
2966
  }
2187
2967
 
2188
2968
  // src/renderers/lithology.renderer.ts
2969
+ var d38 = __toESM(require("d3"), 1);
2189
2970
  function drawLithology(ctx, data) {
2190
- if (!data.length) return;
2191
2971
  const {
2192
2972
  groups: { lithologyGroup },
2193
2973
  svg,
@@ -2211,10 +2991,219 @@ function drawLithology(ctx, data) {
2211
2991
  const merged = mergeEnter(newLayers, rects);
2212
2992
  withTransition(merged, transition).attr("y", getYPos).attr("height", getHeight).attr("fill", getLithologyFill(data, svg, ctx.theme.lithologyTexture));
2213
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
+ }
2214
3204
 
2215
3205
  // src/renderers/unit-labels.renderer.ts
2216
3206
  function drawUnitLabels(ctx, data) {
2217
- if (!ctx.renderConfig.unitLabels.active || !data.length) return;
2218
3207
  const {
2219
3208
  groups: { unitLabelsGroup },
2220
3209
  depthFrom,
@@ -2225,6 +3214,7 @@ function drawUnitLabels(ctx, data) {
2225
3214
  classes
2226
3215
  } = ctx;
2227
3216
  unitLabelsGroup.selectAll("*").remove();
3217
+ if (!renderConfig.unitLabels.active || !data.length) return;
2228
3218
  const ru = renderConfig.unitLabels;
2229
3219
  const rut = theme.unitLabels;
2230
3220
  const sw = ru.stripWidth;
@@ -2305,7 +3295,7 @@ function buildSvgStyleBlock(theme) {
2305
3295
  }
2306
3296
 
2307
3297
  // src/Renderer.ts
2308
- var d38 = { ...d3module2, tip: import_d3_tip2.default };
3298
+ var d310 = { ...d3module2, tip: import_d3_tip2.default };
2309
3299
  var WellRenderer = class {
2310
3300
  /**
2311
3301
  * @param svgs - One entry per SVG panel; multi-entry for split-panel layouts.
@@ -2365,12 +3355,14 @@ var WellRenderer = class {
2365
3355
  const { selector, height, width, margins } = inst;
2366
3356
  const svgWidth = width + margins.left + margins.right;
2367
3357
  const svgHeight = height + margins.top + margins.bottom;
2368
- const svg = d38.select(selector);
3358
+ const svg = d310.select(selector);
2369
3359
  svg.selectAll("*").remove();
2370
3360
  svg.attr("height", svgHeight).attr("width", svgWidth);
2371
3361
  const defs = svg.append("defs");
2372
3362
  defs.append("style").text(buildSvgStyleBlock(this.theme));
2373
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);
2374
3366
  const poco = svg.append("g").attr("class", this.classes.wellGroup);
2375
3367
  const geologic = poco.append("g").attr("class", this.classes.geologicGroup).attr("transform", `translate(${margins.left}, ${margins.top})`);
2376
3368
  geologic.append("g").attr("class", this.classes.yAxis);
@@ -2385,6 +3377,7 @@ var WellRenderer = class {
2385
3377
  );
2386
3378
  const fracturesGroup = poco.append("g").attr("class", this.classes.fractures.group).attr("transform", `translate(${margins.left}, ${margins.top})`).attr("clip-path", `url(#${clipId})`);
2387
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})`);
2388
3381
  construction.append("g").attr("class", this.classes.cementPad.group);
2389
3382
  construction.append("g").attr("class", this.classes.boreHole.group);
2390
3383
  construction.append("g").attr("class", this.classes.surfaceCase.group);
@@ -2392,12 +3385,23 @@ var WellRenderer = class {
2392
3385
  construction.append("g").attr("class", this.classes.wellCase.group);
2393
3386
  construction.append("g").attr("class", this.classes.wellScreen.group);
2394
3387
  construction.append("g").attr("class", this.classes.reduction.group);
3388
+ construction.append("g").attr("class", this.classes.pump.group);
2395
3389
  construction.append("g").attr("class", this.classes.centralizer.group);
2396
3390
  construction.append("g").attr("class", this.classes.conflict.group);
2397
3391
  construction.append("g").attr("class", this.classes.constructionLabels.group);
2398
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})`);
2399
3394
  Object.values(this.textures).forEach((texture) => svg.call(texture));
2400
- 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
+ };
2401
3405
  }
2402
3406
  /** Renders a standalone legend into the element matched by `selector`. */
2403
3407
  renderLegend(selector, profile) {
@@ -2408,6 +3412,30 @@ var WellRenderer = class {
2408
3412
  textures: this.renderConfig.textures
2409
3413
  });
2410
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
+ }
2411
3439
  /**
2412
3440
  * Multiplies the current zoom scale by `factor` (e.g. `1.25` to zoom in,
2413
3441
  * `1 / 1.25` to zoom out) on every panel. No-op if both `renderConfig.zoom`
@@ -2424,7 +3452,7 @@ var WellRenderer = class {
2424
3452
  * (defaults to `1`, i.e. the initial fit-to-container view).
2425
3453
  */
2426
3454
  resetZoom() {
2427
- const target = d38.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
3455
+ const target = d310.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
2428
3456
  for (const state of this.instanceStates) {
2429
3457
  if (!state.zoomNode) continue;
2430
3458
  asSvgElement(state.svg).transition().call(state.zoomNode.transform, target);
@@ -2436,7 +3464,46 @@ var WellRenderer = class {
2436
3464
  */
2437
3465
  getZoomScale() {
2438
3466
  const node = this.instanceStates[0]?.svg.node();
2439
- return node ? d38.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
+ }
2440
3507
  }
2441
3508
  /**
2442
3509
  * Initialises the SVG DOM structure and preloads FGDC textures.
@@ -2449,15 +3516,20 @@ var WellRenderer = class {
2449
3516
  );
2450
3517
  }
2451
3518
  /**
2452
- * 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}).
2453
3522
  * @param profile - The well data to render.
2454
3523
  * @param options.units - Override the instance-level measurement units for this render.
2455
3524
  * @param options.locale - Override the instance-level locale for this render.
2456
3525
  * @param options.highlights - Highlight overlays to display on top of the profile.
2457
3526
  */
2458
3527
  draw(profile, options = {}) {
2459
- if ((0, import_core.isWellEmpty)(profile)) return;
2460
- if (profile.version !== 2) {
3528
+ if ((0, import_core4.isWellEmpty)(profile)) {
3529
+ this.clear();
3530
+ return;
3531
+ }
3532
+ if ((0, import_core4.parseFormatVersion)(profile.version)?.major !== 2) {
2461
3533
  console.warn(
2462
3534
  `[WellRenderer] Received a .well file with version ${profile.version ?? "unknown"}. Only v2 is supported. Normalize with deserializeWell() from @welldot/core before rendering.`
2463
3535
  );
@@ -2467,36 +3539,19 @@ var WellRenderer = class {
2467
3539
  this.locale = options.locale;
2468
3540
  }
2469
3541
  const highlights = options.highlights ?? {};
2470
- const maxValues = (0, import_utils7.getProfileLastItemsDepths)(profile);
2471
- const maxYValues = d38.max(maxValues) || 0;
2472
- if (this.instanceStates.length === 1) {
2473
- this.drawLogToInstance(
2474
- this.instanceStates[0],
2475
- profile,
2476
- 0,
2477
- maxYValues,
2478
- highlights
2479
- );
2480
- return;
2481
- }
2482
- const totalHeight = this.instanceStates.reduce((s, st) => s + st.height, 0) || 1;
2483
- const yScaleGlobal = d38.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
2484
- let currentDepth = 0;
2485
- for (const state of this.instanceStates) {
2486
- const maxSvgDepth = yScaleGlobal.invert(
2487
- yScaleGlobal(currentDepth) + state.height
2488
- );
2489
- this.drawLogToInstance(
2490
- state,
2491
- profile,
2492
- currentDepth,
2493
- maxSvgDepth,
2494
- highlights
2495
- );
2496
- currentDepth = maxSvgDepth;
2497
- }
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
+ });
2498
3553
  }
2499
- drawLogToInstance(state, profile, depthFrom, depthTo, highlights = {}) {
3554
+ drawLogToInstance(state, profile, depthFrom, depthTo, totalDepth, highlights = {}) {
2500
3555
  const svg = state.svg;
2501
3556
  const pocoGroup = svg.select(`.${this.classes.wellGroup}`);
2502
3557
  const lithologyGroup = svg.select(`.${this.classes.lithology.group}`);
@@ -2514,6 +3569,7 @@ var WellRenderer = class {
2514
3569
  const wellScreenGroup = svg.select(`.${this.classes.wellScreen.group}`);
2515
3570
  const reductionGroup = svg.select(`.${this.classes.reduction.group}`);
2516
3571
  const centralizerGroup = svg.select(`.${this.classes.centralizer.group}`);
3572
+ const pumpGroup = svg.select(`.${this.classes.pump.group}`);
2517
3573
  const conflictGroup = svg.select(`.${this.classes.conflict.group}`);
2518
3574
  const constructionLabelsGroup = svg.select(
2519
3575
  `.${this.classes.constructionLabels.group}`
@@ -2528,13 +3584,19 @@ var WellRenderer = class {
2528
3584
  `.${this.classes.highlights.fracturesGroup}`
2529
3585
  );
2530
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}`);
2531
3593
  const svgWidth = state.width + state.margins.left + state.margins.right;
2532
3594
  const svgHeight = state.height + state.margins.top + state.margins.bottom;
2533
3595
  const { pocoWidthRatio, pocoCenterRatio } = this.renderConfig.layout;
2534
3596
  const POCO_WIDTH = svgWidth * pocoWidthRatio;
2535
3597
  const POCO_CENTER = svgWidth * pocoCenterRatio;
2536
3598
  const { duration, ease } = this.renderConfig.animation;
2537
- const transition = d38.transition().duration(duration).ease(ease);
3599
+ const transition = d310.transition().duration(duration).ease(ease);
2538
3600
  const tooltips = populateTooltips(
2539
3601
  svg,
2540
3602
  this.classes,
@@ -2563,11 +3625,11 @@ var WellRenderer = class {
2563
3625
  fractures: profile.fractures
2564
3626
  };
2565
3627
  const contentHeight = svgHeight - state.margins.top - state.margins.bottom;
2566
- const yScaleLocal = d38.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
3628
+ const yScaleLocal = d310.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
2567
3629
  const depthFactor = this.units.length === "ft" ? 3.28084 : 1;
2568
- const yScaleAxis = d38.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
2569
- const yAxis = d38.axisLeft(yScaleAxis).tickFormat(
2570
- (d) => `${(0, import_utils7.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), {
2571
3633
  maximumFractionDigits: this.units.length === "ft" ? 1 : 2,
2572
3634
  locale: this.locale
2573
3635
  })}${getLengthUnit(this.units.length)}`
@@ -2609,10 +3671,15 @@ var WellRenderer = class {
2609
3671
  wellScreenGroup,
2610
3672
  reductionGroup,
2611
3673
  centralizerGroup,
3674
+ pumpGroup,
2612
3675
  conflictGroup,
2613
3676
  highlightsGeologicGroup,
2614
3677
  highlightsConstructionGroup,
2615
- highlightsFracturesGroup
3678
+ highlightsFracturesGroup,
3679
+ overviewGroup,
3680
+ columnHeadersGroup,
3681
+ markersGroup,
3682
+ overlapGroup
2616
3683
  };
2617
3684
  const ctx = {
2618
3685
  svg,
@@ -2626,6 +3693,8 @@ var WellRenderer = class {
2626
3693
  geoXRight,
2627
3694
  depthFrom,
2628
3695
  depthTo,
3696
+ totalDepth,
3697
+ repeatedTo: state.repeatedTo,
2629
3698
  yScale: yScaleLocal,
2630
3699
  transition,
2631
3700
  tooltips,
@@ -2639,6 +3708,7 @@ var WellRenderer = class {
2639
3708
  groups
2640
3709
  };
2641
3710
  const inDepth = filterByDepth(depthFrom, depthTo);
3711
+ const currentPump = (0, import_utils10.getCurrentPump)(profile);
2642
3712
  const filteredConstruction = {
2643
3713
  ...constructionData,
2644
3714
  bore_hole: constructionData.bore_hole.filter(inDepth),
@@ -2663,33 +3733,14 @@ var WellRenderer = class {
2663
3733
  if (!d) return null;
2664
3734
  return transform.k * spanH(d);
2665
3735
  });
2666
- surfaceCaseGroup.selectAll(`g.${this.classes.surfaceCase.rect}`).each((d, i, nodes) => {
2667
- if (!d) return;
2668
- const sc = d;
2669
- const g = d38.select(nodes[i]);
2670
- const y = transform.applyY(spanY(sc));
2671
- const h = transform.k * spanH(sc);
2672
- g.select(".surface-case-fill").attr("y", y).attr("height", h);
2673
- g.selectAll(".surface-case-side").attr("y1", y).attr("y2", y + h);
2674
- });
2675
- const maxXValsReduction = (0, import_utils7.getProfileDiamValues)(constructionData);
2676
- const xScaleReduction = d38.scaleLinear().domain([0, d38.max(maxXValsReduction) || 0]).range([0, POCO_WIDTH]);
2677
- reductionGroup.selectAll(`polygon.${this.classes.reduction.item}`).attr("points", (d) => {
2678
- const r = d;
2679
- const yTop = transform.applyY(spanY(r));
2680
- const yBot = yTop + transform.k * spanH(r);
2681
- const xTopLeft = (POCO_CENTER - xScaleReduction(r.diam_from)) / 2;
2682
- const xTopRight = (POCO_CENTER + xScaleReduction(r.diam_from)) / 2;
2683
- const xBotLeft = (POCO_CENTER - xScaleReduction(r.diam_to)) / 2;
2684
- const xBotRight = (POCO_CENTER + xScaleReduction(r.diam_to)) / 2;
2685
- return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
2686
- });
3736
+ layoutConstruction(zoomedCtx);
3737
+ drawCementPad(zoomedCtx, constructionData.cement_pad);
2687
3738
  fracturesGroup.selectAll(`g.${this.classes.fractures.item}`).attr("transform", (d) => {
2688
3739
  if (!d) return null;
2689
3740
  const f = d;
2690
3741
  const cx = pocoCenterX;
2691
3742
  const cy = transform.applyY(yScaleLocal(f.depth));
2692
- return `translate(0,${cy}) rotate(${f.dip},${cx},0)`;
3743
+ return `translate(0,${cy}) rotate(${fractureDip(f.dip)},${cx},0)`;
2693
3744
  });
2694
3745
  drawCaves(zoomedCtx, geologicData.caves?.filter(inDepth) ?? []);
2695
3746
  drawUnitLabels(zoomedCtx, profile.lithology?.filter(inDepth) ?? []);
@@ -2700,11 +3751,17 @@ var WellRenderer = class {
2700
3751
  ) ?? [],
2701
3752
  caves: profile.caves?.filter(inDepth) ?? []
2702
3753
  });
3754
+ drawPump(zoomedCtx, currentPump);
2703
3755
  drawCentralizers(zoomedCtx, filteredConstruction);
2704
3756
  drawConstructionLabels(zoomedCtx, {
2705
3757
  well_case: constructionData.well_case.filter(inDepth),
2706
3758
  well_screen: constructionData.well_screen.filter(inDepth)
2707
3759
  });
3760
+ drawMarkers(zoomedCtx, {
3761
+ lithology: profile.lithology ?? [],
3762
+ fractures: profile.fractures ?? []
3763
+ });
3764
+ drawOverlap(zoomedCtx);
2708
3765
  drawHighlights(zoomedCtx, highlights);
2709
3766
  this.onZoom?.(transform.k);
2710
3767
  };
@@ -2727,11 +3784,19 @@ var WellRenderer = class {
2727
3784
  ) ?? []
2728
3785
  );
2729
3786
  drawConstructive(ctx, filteredConstruction);
3787
+ drawPump(ctx, currentPump);
2730
3788
  drawCentralizers(ctx, filteredConstruction);
2731
3789
  drawConstructionLabels(ctx, {
2732
3790
  well_case: filteredConstruction.well_case,
2733
3791
  well_screen: filteredConstruction.well_screen
2734
3792
  });
3793
+ drawMarkers(ctx, {
3794
+ lithology: profile.lithology ?? [],
3795
+ fractures: profile.fractures ?? []
3796
+ });
3797
+ drawOverview(ctx, profile.lithology ?? []);
3798
+ drawColumnHeaders(ctx);
3799
+ drawOverlap(ctx);
2735
3800
  svg.select(`#${state.clipRectId}`).attr("y", 0).attr("height", svgHeight);
2736
3801
  drawHighlights(ctx, highlights);
2737
3802
  };
@@ -2747,7 +3812,7 @@ var WellRenderer = class {
2747
3812
  }
2748
3813
  const { zoom: zoomEnabled, pan: panEnabled } = this.renderConfig;
2749
3814
  if (zoomEnabled || panEnabled) {
2750
- const zoomNode = d38.zoom();
3815
+ const zoomNode = d310.zoom();
2751
3816
  if (zoomEnabled || panEnabled) zoomNode.on("zoom", zooming);
2752
3817
  zoomNode.scaleExtent(
2753
3818
  zoomEnabled ? [
@@ -2763,7 +3828,7 @@ var WellRenderer = class {
2763
3828
  if (initialZoom !== 1) {
2764
3829
  asSvgElement(svg).call(
2765
3830
  zoomNode.transform,
2766
- d38.zoomIdentity.scale(initialZoom)
3831
+ d310.zoomIdentity.scale(initialZoom)
2767
3832
  );
2768
3833
  }
2769
3834
  }
@@ -2778,10 +3843,13 @@ var WellRenderer = class {
2778
3843
  WellRenderer,
2779
3844
  applyRenderLocale,
2780
3845
  drawWellLegend,
3846
+ drawWellLegendSymbol,
2781
3847
  formatDiameter,
2782
3848
  formatLength,
2783
3849
  getDiameterUnit,
2784
3850
  getLengthUnit,
3851
+ getProfileTopDepth,
3852
+ getWellLegendItems,
2785
3853
  importFgdcTextures,
2786
3854
  resolveRenderLabel
2787
3855
  });