@welldot/render 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,12 @@
1
+ import {
2
+ importFgdcTextures
3
+ } from "./chunk-ODRL222Y.js";
4
+
1
5
  // src/configs/render.configs.ts
2
6
  import { easeCubic } from "d3";
3
7
 
4
8
  // src/utils/format.utils.ts
9
+ import { resolveLanguageText } from "@welldot/core";
5
10
  import { formatNumber } from "@welldot/utils";
6
11
  var MM_TO_INCHES = 0.0393701;
7
12
  var M_TO_FT = 3.28084;
@@ -19,8 +24,7 @@ function getDiameterUnit(units, locale) {
19
24
  return locale === "en" ? "in." : '"';
20
25
  }
21
26
  function resolveRenderLabel(value, locale) {
22
- if (typeof value === "string") return value;
23
- return locale === "en" && value.en ? value.en : value.pt;
27
+ return resolveLanguageText(value, locale, "pt");
24
28
  }
25
29
 
26
30
  // src/configs/render.configs.ts
@@ -46,6 +50,7 @@ var RENDER_LABELS = {
46
50
  wellScreen: { pt: "Filtro", en: "Screen" },
47
51
  reduction: { pt: "Redu\xE7\xE3o", en: "Reduction" },
48
52
  centralizer: { pt: "Centralizador", en: "Centralizer" },
53
+ pump: { pt: "Bomba e edutor", en: "Pump and riser" },
49
54
  cementPad: { pt: "Laje de cimento", en: "Cement pad" },
50
55
  conflict: { pt: "Conflito", en: "Conflict" }
51
56
  }
@@ -69,7 +74,9 @@ var RENDER_LABELS = {
69
74
  wellCase: { title: { pt: "REVESTIMENTO", en: "CASING" } },
70
75
  wellScreen: {
71
76
  title: { pt: "FILTROS", en: "SCREENS" },
72
- slot: { pt: "Ranhura:", en: "Slot:" }
77
+ slot: { pt: "Ranhura:", en: "Slot:" },
78
+ material: { pt: "Material:", en: "Material:" },
79
+ slotType: { pt: "Tipo:", en: "Type:" }
73
80
  },
74
81
  reduction: { title: { pt: "REDU\xC7\xC3O", en: "REDUCTION" } },
75
82
  centralizer: {
@@ -77,6 +84,28 @@ var RENDER_LABELS = {
77
84
  depth: { pt: "Profundidade:", en: "Depth:" },
78
85
  spacing: { pt: "Espa\xE7amento:", en: "Spacing:" }
79
86
  },
87
+ pump: {
88
+ title: { pt: "BOMBA", en: "PUMP" },
89
+ intakeDepth: { pt: "Profundidade do crivo:", en: "Intake depth:" },
90
+ model: { pt: "Modelo:", en: "Model:" },
91
+ power: { pt: "Pot\xEAncia:", en: "Power:" },
92
+ riser: { pt: "Edutor:", en: "Riser:" },
93
+ type_submersible: { pt: "Bomba submersa", en: "Submersible pump" },
94
+ type_vertical_turbine: {
95
+ pt: "Bomba de eixo vertical",
96
+ en: "Vertical turbine pump"
97
+ },
98
+ type_jet: { pt: "Bomba injetora", en: "Jet pump" },
99
+ type_progressive_cavity: {
100
+ pt: "Bomba helicoidal",
101
+ en: "Progressive cavity pump"
102
+ },
103
+ type_hand_pump: { pt: "Bomba manual", en: "Hand pump" },
104
+ type_compressor_airlift: {
105
+ pt: "Compressor (air-lift)",
106
+ en: "Compressor (air-lift)"
107
+ }
108
+ },
80
109
  conflict: { title: { pt: "CONFLITO", en: "CONFLICT" } },
81
110
  fracture: {
82
111
  title: { pt: "FRATURA", en: "FRACTURE" },
@@ -102,6 +131,19 @@ var RENDER_LABELS = {
102
131
  fractureWater: { pt: "fratura aberta", en: "open fracture" },
103
132
  cave: { pt: "caverna", en: "cave" },
104
133
  caveWater: { pt: "caverna \xFAmida", en: "wet cave" }
134
+ },
135
+ columnHeaders: {
136
+ depth: { pt: "PROF.", en: "DEPTH" },
137
+ lithology: { pt: "LIT.", en: "LITH." },
138
+ construction: { pt: "CONSTRU\xC7\xC3O", en: "CONSTRUCTION" },
139
+ description: { pt: "DESCRI\xC7\xC3O GEOL\xD3GICA", en: "GEOLOGICAL DESCRIPTION" }
140
+ },
141
+ continuation: { prefix: { pt: "continua\xE7\xE3o", en: "continued" } },
142
+ overlap: {
143
+ label: {
144
+ pt: "{range} repetido da p\xE1g. anterior",
145
+ en: "{range} repeated from previous page"
146
+ }
105
147
  }
106
148
  };
107
149
  function resolveGroup(group, locale) {
@@ -123,6 +165,7 @@ function resolveTooltipLabels(locale) {
123
165
  wellScreen: resolveGroup(t.wellScreen, locale),
124
166
  reduction: resolveGroup(t.reduction, locale),
125
167
  centralizer: resolveGroup(t.centralizer, locale),
168
+ pump: resolveGroup(t.pump, locale),
126
169
  conflict: resolveGroup(t.conflict, locale),
127
170
  fracture: resolveGroup(t.fracture, locale),
128
171
  cementPad: resolveGroup(t.cementPad, locale),
@@ -144,7 +187,19 @@ function applyRenderLocale(config, locale) {
144
187
  tooltipLabels: resolveTooltipLabels(locale),
145
188
  labels: {
146
189
  ...config.labels,
147
- typeLabels: resolveGroup(RENDER_LABELS.typeLabels, locale)
190
+ typeLabels: resolveGroup(RENDER_LABELS.typeLabels, locale),
191
+ continuation: {
192
+ ...config.labels.continuation,
193
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, locale)
194
+ }
195
+ },
196
+ columnHeaders: {
197
+ ...config.columnHeaders,
198
+ labels: resolveGroup(RENDER_LABELS.columnHeaders, locale)
199
+ },
200
+ overlap: {
201
+ ...config.overlap,
202
+ label: resolveRenderLabel(RENDER_LABELS.overlap.label, locale)
148
203
  }
149
204
  };
150
205
  }
@@ -170,7 +225,7 @@ var DEFAULT_WELL_THEME = {
170
225
  crackStrokeWidth: 0.7
171
226
  }
172
227
  },
173
- cementPad: { stroke: "#303030", strokeWidth: 2 },
228
+ cementPad: { stroke: "#303030", strokeWidth: 1 },
174
229
  boreHole: {
175
230
  fill: "#ffffff",
176
231
  stroke: "#191919",
@@ -179,11 +234,24 @@ var DEFAULT_WELL_THEME = {
179
234
  strokeWidth: 1.2
180
235
  },
181
236
  surfaceCase: { stroke: "#000000", strokeWidth: 4 },
182
- holeFill: { stroke: "#303030", strokeWidth: 2 },
237
+ holeFill: { stroke: "#303030", strokeWidth: 1.2 },
183
238
  wellCase: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
184
- wellScreen: { stroke: "#303030", strokeWidth: 2 },
239
+ wellScreen: {
240
+ stroke: "#303030",
241
+ strokeWidth: 2,
242
+ endStrokeWidth: 0.75,
243
+ endDasharray: "3, 2"
244
+ },
185
245
  reduction: { fill: "#ffffff", stroke: "#303030", strokeWidth: 2 },
186
246
  centralizer: { fill: "none", stroke: "#303030", strokeWidth: 1.4 },
247
+ pump: {
248
+ fill: "#5b6b7a",
249
+ stroke: "#202020",
250
+ strokeWidth: 1.2,
251
+ riserFill: "#a3adb7",
252
+ riserStroke: "#2f3a44",
253
+ riserStrokeWidth: 1
254
+ },
187
255
  conflict: { stroke: "#e52117", strokeWidth: 4 },
188
256
  unitLabels: {
189
257
  geologicFill: "#f0f0f0",
@@ -219,6 +287,39 @@ var DEFAULT_WELL_THEME = {
219
287
  headerFontWeight: 600,
220
288
  bodyFontWeight: 400
221
289
  },
290
+ overview: {
291
+ stroke: "#303030",
292
+ strokeWidth: 0.5,
293
+ emptyFill: "#d9d9d9",
294
+ windowStroke: "#c0392b",
295
+ windowStrokeWidth: 1.2
296
+ },
297
+ columnHeaders: {
298
+ fontSize: 6.5,
299
+ color: "#303030",
300
+ fontFamily: "sans-serif",
301
+ fontWeight: 600,
302
+ ruleStroke: "#101010",
303
+ ruleStrokeWidth: 1
304
+ },
305
+ markers: {
306
+ fill: "#ffffff",
307
+ stroke: "#101010",
308
+ strokeWidth: 0.6,
309
+ color: "#101010",
310
+ fontSize: 6,
311
+ fontFamily: "sans-serif",
312
+ fontWeight: 600
313
+ },
314
+ overlap: {
315
+ stroke: "#9a9a9a",
316
+ strokeWidth: 0.6,
317
+ size: 6,
318
+ ruleStroke: "#9a9a9a",
319
+ labelColor: "#555555",
320
+ fontSize: 6.5,
321
+ fontFamily: "monospace"
322
+ },
222
323
  legend: {
223
324
  borderStrokeWidth: 0.8,
224
325
  fractureStrokeWidth: 1.5,
@@ -276,9 +377,17 @@ var STATIC_RENDER_CONFIG = {
276
377
  }
277
378
  },
278
379
  construction: {
279
- cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
380
+ cementPad: { widthRatio: 1.4, height: 6 },
381
+ casingShoe: { active: true, size: 4 },
382
+ screenEnds: { active: true },
280
383
  surfaceCase: { diameterPaddingRatio: 0.1 },
281
- centralizer: { markerHalfHeight: 4 }
384
+ centralizer: { markerHalfHeight: 4 },
385
+ pump: {
386
+ active: true,
387
+ bodyHeight: 22,
388
+ bodyWidthRatio: 0.6,
389
+ riserWidthRatio: 0.3
390
+ }
282
391
  },
283
392
  tooltipLabels: resolveTooltipLabels("pt"),
284
393
  labels: {
@@ -291,7 +400,11 @@ var STATIC_RENDER_CONFIG = {
291
400
  stackingLineHeight: 10,
292
401
  stackingGap: 0,
293
402
  fractureLabelLeaderGap: 5,
294
- lithology: true
403
+ lithology: true,
404
+ continuation: {
405
+ active: false,
406
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, "pt")
407
+ }
295
408
  },
296
409
  unitLabels: {
297
410
  active: true,
@@ -350,10 +463,21 @@ var STATIC_RENDER_CONFIG = {
350
463
  wellScreen: "Filtro",
351
464
  reduction: "Redu\xE7\xE3o",
352
465
  centralizer: "Centralizador",
466
+ pump: "Bomba e edutor",
353
467
  cementPad: "Laje de cimento",
354
468
  conflict: "Conflito"
355
469
  }
356
470
  },
471
+ overview: { active: false, x: 4, width: 8, height: 200 },
472
+ columnHeaders: {
473
+ active: false,
474
+ height: 14,
475
+ labels: resolveGroup(RENDER_LABELS.columnHeaders, "pt")
476
+ },
477
+ markers: { lithology: false, fractures: false, radius: 4.5 },
478
+ overlap: {
479
+ label: resolveRenderLabel(RENDER_LABELS.overlap.label, "pt")
480
+ },
357
481
  highlights: DEFAULT_HIGHLIGHT_CONFIG
358
482
  };
359
483
  var INTERACTIVE_RENDER_CONFIG = {
@@ -389,9 +513,17 @@ var INTERACTIVE_RENDER_CONFIG = {
389
513
  }
390
514
  },
391
515
  construction: {
392
- cementPad: { widthMultiplier: 0.9, thicknessMultiplier: 1.3 },
516
+ cementPad: { widthRatio: 1.4, height: 6 },
517
+ casingShoe: { active: true, size: 4 },
518
+ screenEnds: { active: true },
393
519
  surfaceCase: { diameterPaddingRatio: 0.1 },
394
- centralizer: { markerHalfHeight: 4 }
520
+ centralizer: { markerHalfHeight: 4 },
521
+ pump: {
522
+ active: true,
523
+ bodyHeight: 22,
524
+ bodyWidthRatio: 0.6,
525
+ riserWidthRatio: 0.3
526
+ }
395
527
  },
396
528
  tooltipLabels: resolveTooltipLabels("pt"),
397
529
  labels: {
@@ -402,7 +534,11 @@ var INTERACTIVE_RENDER_CONFIG = {
402
534
  descriptionXOffset: 78,
403
535
  descriptionMaxWidth: 226,
404
536
  stackingLineHeight: 11,
405
- stackingGap: 0
537
+ stackingGap: 0,
538
+ continuation: {
539
+ active: false,
540
+ prefix: resolveRenderLabel(RENDER_LABELS.continuation.prefix, "pt")
541
+ }
406
542
  },
407
543
  unitLabels: {
408
544
  active: false,
@@ -460,19 +596,24 @@ var INTERACTIVE_RENDER_CONFIG = {
460
596
  wellScreen: "Filtro",
461
597
  reduction: "Redu\xE7\xE3o",
462
598
  centralizer: "Centralizador",
599
+ pump: "Bomba e edutor",
463
600
  cementPad: "Laje de cimento",
464
601
  conflict: "Conflito"
465
602
  }
466
603
  },
604
+ overview: { active: false, x: 4, width: 8, height: 200 },
605
+ columnHeaders: {
606
+ active: false,
607
+ height: 14,
608
+ labels: resolveGroup(RENDER_LABELS.columnHeaders, "pt")
609
+ },
610
+ markers: { lithology: false, fractures: false, radius: 4.5 },
611
+ overlap: {
612
+ label: resolveRenderLabel(RENDER_LABELS.overlap.label, "pt")
613
+ },
467
614
  highlights: DEFAULT_HIGHLIGHT_CONFIG
468
615
  };
469
616
 
470
- // src/utils/fgdcTextures.ts
471
- async function importFgdcTextures() {
472
- const fgdcTextures = await import("./fgdcTextures-P5DTFNPT.js");
473
- return fgdcTextures.default;
474
- }
475
-
476
617
  // src/Renderer.ts
477
618
  import * as d3module2 from "d3";
478
619
  import d3tip2 from "d3-tip";
@@ -480,6 +621,8 @@ import { defu as defu2 } from "defu";
480
621
 
481
622
  // src/configs/render.textures.ts
482
623
  import textures from "textures";
624
+ 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`;
625
+ 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)}`;
483
626
  function createWellTextures(cfg) {
484
627
  const pad = {
485
628
  background: "#ffffff",
@@ -527,7 +670,9 @@ function createWellTextures(cfg) {
527
670
  ...cfg?.surface_case
528
671
  };
529
672
  return {
530
- pad: textures.lines().heavier(10).thinner(1.5).stroke(pad.stroke).background(pad.background),
673
+ // Concrete: aggregate (small triangles and dots) on a light ground, so it
674
+ // reads apart from the seal's diagonals and the gravel pack's circles.
675
+ pad: textures.paths().d(concreteAggregate).size(6).strokeWidth(0.4).stroke(pad.stroke).fill(pad.stroke).background(pad.background),
531
676
  conflict: textures.lines().heavier().stroke(conflict.stroke),
532
677
  cave_dry: textures.lines().size(cave_dry.size).orientation("6/8").heavier(0.3).thinner(0.8).background(cave_dry.background).stroke(cave_dry.stroke),
533
678
  cave_wet: textures.lines().size(cave_wet.size).orientation("6/8").heavier(0.3).thinner(0.8).background(cave_wet.background).stroke(cave_wet.stroke),
@@ -543,8 +688,8 @@ import {
543
688
  isWellEmpty
544
689
  } from "@welldot/core";
545
690
  import {
546
- formatNumber as formatNumber3,
547
- getProfileDiamValues as getProfileDiamValues5,
691
+ formatNumber as formatNumber4,
692
+ getCurrentPump as getCurrentPump2,
548
693
  getProfileLastItemsDepths
549
694
  } from "@welldot/utils";
550
695
 
@@ -608,6 +753,10 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
608
753
  group: "centralizer",
609
754
  item: "centralizer-marker"
610
755
  },
756
+ pump: {
757
+ group: "pump",
758
+ item: "pump-item"
759
+ },
611
760
  reduction: {
612
761
  group: "reduction",
613
762
  item: "reduction-poly"
@@ -639,6 +788,26 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
639
788
  caveFill: "cave-fill",
640
789
  constructionRect: "construction-rect"
641
790
  },
791
+ overview: {
792
+ group: "wp-overview",
793
+ item: "wp-overview-item",
794
+ window: "wp-overview-window"
795
+ },
796
+ columnHeaders: {
797
+ group: "wp-column-headers",
798
+ text: "wp-column-header",
799
+ rule: "wp-column-headers-rule"
800
+ },
801
+ markers: {
802
+ group: "wp-markers",
803
+ lithology: "wp-marker-lithology",
804
+ fracture: "wp-marker-fracture"
805
+ },
806
+ overlap: {
807
+ group: "wp-overlap",
808
+ band: "wp-overlap-band",
809
+ label: "wp-overlap-label"
810
+ },
642
811
  highlights: {
643
812
  geologicGroup: "wp-highlights-geologic",
644
813
  constructionGroup: "wp-highlights-construction",
@@ -653,6 +822,156 @@ var DEFAULT_COMPONENTS_CLASS_NAMES = {
653
822
  // src/utils/render.utils.ts
654
823
  import textures2 from "textures";
655
824
 
825
+ // src/utils/construction.geometry.ts
826
+ var JOINT_EPS = 1e-3;
827
+ function diameterAt(items, depth) {
828
+ return items.find((i) => depth >= i.from && depth <= i.to)?.diameter;
829
+ }
830
+ function getIntervalEnds(item, string, depthFrom, depthTo) {
831
+ return {
832
+ cutTop: item.from < depthFrom - JOINT_EPS,
833
+ cutBottom: item.to > depthTo + JOINT_EPS,
834
+ joinedTop: string.some(
835
+ (o) => o !== item && Math.abs(o.to - item.from) < JOINT_EPS
836
+ ),
837
+ joinedBottom: string.some(
838
+ (o) => o !== item && Math.abs(o.from - item.to) < JOINT_EPS
839
+ )
840
+ };
841
+ }
842
+ function pipeWallsPath(xLeft, xRight, yTop, yBottom) {
843
+ return `M${xLeft},${yTop}V${yBottom}M${xRight},${yTop}V${yBottom}`;
844
+ }
845
+ function shoePath(xLeft, xRight, y, size) {
846
+ return `M${xLeft},${y - size}L${xLeft},${y}L${xLeft - size},${y}ZM${xRight},${y - size}L${xRight},${y}L${xRight + size},${y}Z`;
847
+ }
848
+ function screenEndPath(xLeft, xRight, y) {
849
+ return `M${xLeft},${y}H${xRight}`;
850
+ }
851
+ var clampDepth = (s, depth) => Math.min(Math.max(depth, s.depthFrom), s.depthTo);
852
+ var inPanel = (s, depth) => depth >= s.depthFrom - JOINT_EPS && depth <= s.depthTo + JOINT_EPS;
853
+ var shoulders = (s, a, b, y) => `M${s.left(a)},${y}H${s.left(b)}M${s.right(a)},${y}H${s.right(b)}`;
854
+ function boreHoleOutlinePath(item, holes, s) {
855
+ const yTop = s.y(clampDepth(s, item.from));
856
+ const yBottom = s.y(clampDepth(s, item.to));
857
+ let d = pipeWallsPath(
858
+ s.left(item.diameter),
859
+ s.right(item.diameter),
860
+ yTop,
861
+ yBottom
862
+ );
863
+ if (item.from > JOINT_EPS && inPanel(s, item.from)) {
864
+ const above = holes.find(
865
+ (o) => o !== item && o.from < item.from && o.to >= item.from - JOINT_EPS
866
+ );
867
+ if (above && above.diameter < item.diameter)
868
+ d += shoulders(s, above.diameter, item.diameter, yTop);
869
+ }
870
+ if (inPanel(s, item.to)) {
871
+ const below = holes.find(
872
+ (o) => o !== item && o.from <= item.to + JOINT_EPS && o.to > item.to
873
+ );
874
+ if (!below) {
875
+ d += `M${s.left(item.diameter)},${yBottom}H${s.right(item.diameter)}`;
876
+ } else if (below.diameter < item.diameter) {
877
+ d += shoulders(s, item.diameter, below.diameter, yBottom);
878
+ }
879
+ }
880
+ return d;
881
+ }
882
+ function holeFillOutlinePath(item, pipes, s) {
883
+ const yTop = s.y(clampDepth(s, item.from));
884
+ const yBottom = s.y(clampDepth(s, item.to));
885
+ const xL = s.left(item.diameter);
886
+ const xR = s.right(item.diameter);
887
+ let d = pipeWallsPath(xL, xR, yTop, yBottom);
888
+ const interfaces = [item.from, item.to].filter(
889
+ (depth) => depth > JOINT_EPS && inPanel(s, depth)
890
+ );
891
+ for (const depth of interfaces) {
892
+ const y = s.y(depth);
893
+ const pipe = diameterAt(pipes, depth);
894
+ if (pipe !== void 0 && pipe < item.diameter) {
895
+ d += `M${xL},${y}H${s.left(pipe)}M${s.right(pipe)},${y}H${xR}`;
896
+ } else {
897
+ d += `M${xL},${y}H${xR}`;
898
+ }
899
+ }
900
+ return d;
901
+ }
902
+ function pipeSlices(pipes, from, to) {
903
+ 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);
904
+ const slices = [];
905
+ for (let i = 1; i < cuts.length; i++) {
906
+ const a = cuts[i - 1];
907
+ const b = cuts[i];
908
+ const mid = (a + b) / 2;
909
+ const widest = Math.max(
910
+ 0,
911
+ ...pipes.filter((p) => p.from <= mid && p.to >= mid).map((p) => p.diameter)
912
+ );
913
+ if (widest === 0) continue;
914
+ const last = slices[slices.length - 1];
915
+ if (last && last.to === a && last.diameter === widest) last.to = b;
916
+ else slices.push({ from: a, to: b, diameter: widest });
917
+ }
918
+ return slices;
919
+ }
920
+ function annulusFillPath(item, pipes, s) {
921
+ const from = clampDepth(s, item.from);
922
+ const to = clampDepth(s, item.to);
923
+ 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`;
924
+ const inner = pipes.filter((p) => p.diameter < item.diameter);
925
+ return box(item.diameter, from, to) + pipeSlices(inner, from, to).map((p) => box(p.diameter, p.from, p.to)).join("");
926
+ }
927
+ function getPadDiameters(data, surfaceCasePaddingRatio) {
928
+ const surfaceCase = diameterAt(data.surface_case, 0);
929
+ const paddedSurfaceCase = surfaceCase === void 0 ? void 0 : surfaceCase * (1 + surfaceCasePaddingRatio);
930
+ const casing = diameterAt([...data.well_case, ...data.well_screen], 0);
931
+ const hole = diameterAt(data.bore_hole, 0);
932
+ const opening = casing ?? paddedSurfaceCase ?? hole ?? 0;
933
+ const outer = Math.max(
934
+ opening,
935
+ paddedSurfaceCase ?? 0,
936
+ hole ?? 0,
937
+ diameterAt(data.hole_fill, 0) ?? 0
938
+ );
939
+ return { opening, outer };
940
+ }
941
+ function getCementPadGeometry(pocoWidth, pocoCenter, surfaceY, config, openingWidth = 0, outerWidth = openingWidth) {
942
+ const h = config.height;
943
+ const opening = Math.max(0, openingWidth);
944
+ const width = Math.max(
945
+ pocoWidth * config.widthRatio,
946
+ opening + 3 * h,
947
+ outerWidth + 2 * h
948
+ );
949
+ const xLeft = (pocoCenter - width) / 2;
950
+ const xRight = xLeft + width;
951
+ const oLeft = (pocoCenter - opening) / 2;
952
+ const oRight = oLeft + opening;
953
+ const yTop = surfaceY - h;
954
+ const a = h * 0.3;
955
+ 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}`;
956
+ const bbox = { x: xLeft, y: yTop, width, height: h };
957
+ if (opening === 0) {
958
+ return {
959
+ bbox,
960
+ slabs: [
961
+ `M${xLeft},${yTop}H${xRight}${breakLine(xRight, yTop, surfaceY, 1)}H${xLeft}${breakLine(xLeft, surfaceY, yTop, -1)}Z`,
962
+ ""
963
+ ]
964
+ };
965
+ }
966
+ return {
967
+ bbox,
968
+ slabs: [
969
+ `M${oLeft},${yTop}H${xLeft}${breakLine(xLeft, yTop, surfaceY, -1)}H${oLeft}Z`,
970
+ `M${oRight},${yTop}H${xRight}${breakLine(xRight, yTop, surfaceY, 1)}H${oRight}Z`
971
+ ]
972
+ };
973
+ }
974
+
656
975
  // src/utils/d3.utils.ts
657
976
  function mergeEnter(enter, update) {
658
977
  return enter.merge(update);
@@ -723,6 +1042,14 @@ var makeSeededPrng = (seed, scramble = 6271) => {
723
1042
  // src/utils/tooltips.utils.ts
724
1043
  import d3tip from "d3-tip";
725
1044
  import sanitizeHtml from "sanitize-html";
1045
+ import {
1046
+ CEMENT_PAD_TYPES,
1047
+ CENTRALIZER_TYPES,
1048
+ CONSTRUCTION_MATERIALS,
1049
+ SCREEN_SLOT_TYPES,
1050
+ getVocabLabel,
1051
+ powerFromCanonical
1052
+ } from "@welldot/core";
726
1053
  import { formatNumber as formatNumber2 } from "@welldot/utils";
727
1054
  var esc = (v) => sanitizeHtml(String(v ?? ""), { allowedTags: [], allowedAttributes: {} });
728
1055
  var _tooltipCache = /* @__PURE__ */ new WeakMap();
@@ -733,6 +1060,7 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
733
1060
  if (cached && cached.signature === signature) {
734
1061
  return cached.tooltips;
735
1062
  }
1063
+ const material = (value) => getVocabLabel(CONSTRUCTION_MATERIALS, value, locale);
736
1064
  const tipsText = {
737
1065
  geology: (_, d) => `
738
1066
  <span class="${customClasses.tooltip.title}">${labels.geology.title}</span>
@@ -765,14 +1093,15 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
765
1093
  <span class="${customClasses.tooltip.secondaryInfo}">
766
1094
  <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}
767
1095
  </span>
768
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
1096
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(material(d.type))}</span>
769
1097
  `,
770
1098
  wellScreen: (_, d) => `
771
1099
  <span class="${customClasses.tooltip.title}">${labels.wellScreen.title}</span>
772
1100
  <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>
773
1101
  <span class="${customClasses.tooltip.secondaryInfo}">
774
1102
  <strong>${labels.common.diameter}</strong> ${esc(formatDiameter(d.diameter, units.diameter, locale))} ${esc(getDiameterUnit(units.diameter, locale))}</span>
775
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
1103
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.wellScreen.material}</strong> ${esc(material(d.type))}</span>
1104
+ ${d.slot_type ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.wellScreen.slotType}</strong> ${esc(getVocabLabel(SCREEN_SLOT_TYPES, d.slot_type, locale))}</span>` : ""}
776
1105
  <span class="${customClasses.tooltip.secondaryInfo}">
777
1106
  <strong>${labels.wellScreen.slot}</strong> ${esc(formatNumber2(d.screen_slot, { maximumFractionDigits: 2, locale }))} mm
778
1107
  </span>
@@ -783,17 +1112,29 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
783
1112
  <span class="${customClasses.tooltip.secondaryInfo}">
784
1113
  <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))}
785
1114
  </span>
786
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
1115
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(material(d.type))}</span>
787
1116
  `,
788
1117
  centralizer: (_, d) => `
789
1118
  <span class="${customClasses.tooltip.title}">${labels.centralizer.title}</span>
790
1119
  <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.centralizer.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
791
1120
  ${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>` : ""}
792
1121
  ${d.spacing ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.centralizer.spacing}</strong> ${esc(formatLength(d.spacing, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>` : ""}
793
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(d.type)}</span>
1122
+ <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.type}</strong> ${esc(getVocabLabel(CENTRALIZER_TYPES, d.type, locale))}</span>
794
1123
  ${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>` : ""}
795
1124
  ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
796
1125
  `,
1126
+ pump: (_, d) => {
1127
+ const typeLabel = labels.pump[`type_${d.type}`] ?? d.type;
1128
+ const model = [d.manufacturer, d.model].filter(Boolean).join(" ");
1129
+ return `
1130
+ <span class="${customClasses.tooltip.title}">${labels.pump.title}</span>
1131
+ <span class="${customClasses.tooltip.primaryInfo}">${esc(typeLabel)}</span>
1132
+ ${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>` : ""}
1133
+ ${model ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.pump.model}</strong> ${esc(model)}</span>` : ""}
1134
+ ${d.rated_power !== void 0 ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.pump.power}</strong> ${esc(formatNumber2(powerFromCanonical(d.rated_power, units.power ?? "kW"), { maximumFractionDigits: 2, locale }))} ${esc(units.power ?? "kW")}</span>` : ""}
1135
+ ${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>` : ""}
1136
+ `;
1137
+ },
797
1138
  conflict: (_, d) => `
798
1139
  <span class="${customClasses.tooltip.title}">${labels.conflict.title}</span>
799
1140
  <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>
@@ -804,14 +1145,14 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
804
1145
  <span class="${customClasses.tooltip.title}">${title}</span>
805
1146
  <span class="${customClasses.tooltip.primaryInfo}"><strong>${labels.fracture.depth}</strong> ${esc(formatLength(d.depth, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
806
1147
  ${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>` : ""}
807
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.dip}</strong> ${esc(d.dip)}\xB0</span>
808
- <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.azimuth}</strong> ${esc(d.azimuth)}\xB0</span>
1148
+ ${typeof d.dip === "number" ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.dip}</strong> ${esc(d.dip)}\xB0</span>` : ""}
1149
+ ${typeof d.azimuth === "number" ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.fracture.azimuth}</strong> ${esc(d.azimuth)}\xB0</span>` : ""}
809
1150
  ${d.description ? `<span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.common.description}</strong> ${esc(d.description)}</span>` : ""}
810
1151
  `;
811
1152
  },
812
1153
  cementPad: (_, d) => `
813
1154
  <span class="${customClasses.tooltip.title}">${labels.cementPad.title}</span>
814
- <span class="${customClasses.tooltip.primaryInfo}">${esc(d.type)}</span>
1155
+ <span class="${customClasses.tooltip.primaryInfo}">${esc(getVocabLabel(CEMENT_PAD_TYPES, d.type, locale))}</span>
815
1156
  <span class="${customClasses.tooltip.secondaryInfo}"><strong>${labels.cementPad.thickness}</strong>
816
1157
  ${esc(formatLength(d.thickness, units.length, locale))} ${esc(getLengthUnit(units.length))}</span>
817
1158
  <span class="${customClasses.tooltip.secondaryInfo}">
@@ -849,6 +1190,17 @@ var populateTooltips = (svg, customClasses, units, tooltipConfig, labels, locale
849
1190
  };
850
1191
 
851
1192
  // src/utils/render.utils.ts
1193
+ var fractureDip = (dip) => typeof dip === "number" && Number.isFinite(dip) ? dip : 0;
1194
+ function getCementPadRect(pocoWidth, pocoCenter, surfaceY, config, openingWidth = 0, outerWidth = openingWidth) {
1195
+ return getCementPadGeometry(
1196
+ pocoWidth,
1197
+ pocoCenter,
1198
+ surfaceY,
1199
+ config,
1200
+ openingWidth,
1201
+ outerWidth
1202
+ ).bbox;
1203
+ }
852
1204
  var _fgdcTextures = {};
853
1205
  var _fgdcTexturesPromise = null;
854
1206
  var preloadFgdcTextures = () => {
@@ -957,14 +1309,23 @@ function drawAnnotationLabels(ctx, data) {
957
1309
  }
958
1310
  const items = [];
959
1311
  if (active.has("lithology")) {
1312
+ const continuation = s.continuation;
960
1313
  data.lithology.filter((d) => d.from < ctx.depthTo).forEach((d) => {
1314
+ const range = `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`;
1315
+ const continues = continuation?.active && d.from < ctx.depthFrom;
961
1316
  items.push({
962
1317
  sortDepth: d.from,
963
1318
  baseY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
964
1319
  originX: ctx.geoXRight,
965
1320
  originY: ctx.yScale(Math.max(d.from, ctx.depthFrom)),
966
- header: `${formatLength(d.from, ctx.units.length, ctx.locale)}\u2013${formatLength(d.to, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)}`,
967
- description: d.description || ""
1321
+ header: continues ? `\u2191 ${continuation.prefix} \xB7 ${range}` : range,
1322
+ description: continues ? "" : d.description || "",
1323
+ ...show.has("units") ? {
1324
+ unit: d.geologic_unit || void 0,
1325
+ aquifer: continues ? void 0 : d.aquifer_unit || void 0
1326
+ } : {},
1327
+ ...show.has("swatch") && !continues ? { swatch: d } : {},
1328
+ ...show.has("rules") ? { rule: continues ? "dashed" : "solid" } : {}
968
1329
  });
969
1330
  });
970
1331
  }
@@ -977,7 +1338,8 @@ function drawAnnotationLabels(ctx, data) {
977
1338
  originX: ctx.pocoCenterX,
978
1339
  originY: ctx.yScale(d.depth),
979
1340
  header: `${formatLength(d.depth, ctx.units.length, ctx.locale)}${getLengthUnit(ctx.units.length)} \xB7 ${typeLabel}`,
980
- description: d.description || ""
1341
+ description: d.description || "",
1342
+ ...show.has("rules") ? { rule: "dashed" } : {}
981
1343
  });
982
1344
  });
983
1345
  }
@@ -997,14 +1359,15 @@ function drawAnnotationLabels(ctx, data) {
997
1359
  }
998
1360
  items.sort((a, b) => a.sortDepth - b.sortDepth);
999
1361
  const labelPositions = [];
1000
- let currY = 0;
1362
+ let currY = ctx.repeatedTo != null ? s.stackingLineHeight + 2 : 0;
1001
1363
  const charsPerLine = Math.max(
1002
1364
  1,
1003
1365
  Math.floor(s.descriptionMaxWidth / (st.fontSize * 0.6))
1004
1366
  );
1005
1367
  items.forEach((item) => {
1006
- const descLines = Math.ceil((item.description.length || 1) / charsPerLine);
1007
- const estH = (1 + descLines) * s.stackingLineHeight + 4;
1368
+ const descLines = show.has("units") ? item.description ? wrapText(item.description, charsPerLine).length : 0 : Math.ceil((item.description.length || 1) / charsPerLine);
1369
+ const extraLines = (item.unit ? 1 : 0) + (item.aquifer ? 1 : 0);
1370
+ const estH = (1 + descLines + extraLines) * s.stackingLineHeight + 4;
1008
1371
  const yLabel = Math.max(item.baseY, currY + s.stackingGap);
1009
1372
  currY = yLabel + estH;
1010
1373
  labelPositions.push({ item, yLabel, estH });
@@ -1021,16 +1384,41 @@ function drawAnnotationLabels(ctx, data) {
1021
1384
  if (show.has("description")) {
1022
1385
  const lineH = s.stackingLineHeight;
1023
1386
  const bgFill = st.annotationBg ?? "white";
1387
+ const swatchFill = labelPositions.some((p) => p.item.swatch) ? getLithologyFill(data.lithology, ctx.svg, ctx.theme.lithologyTexture) : null;
1024
1388
  labelPositions.forEach(({ item, yLabel, estH }) => {
1025
1389
  const labelG = ctx.groups.lithologyLabelsGroup.append("g").attr("class", ctx.classes.labels.lithology.label).attr("transform", `translate(${labelX},${yLabel})`);
1026
1390
  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");
1027
- 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);
1391
+ if (item.rule) {
1392
+ labelG.append("line").attr("x1", 0).attr("x2", s.descriptionMaxWidth).attr("y1", 0).attr("y2", 0).attr("stroke", ctx.theme.labels.color).attr(
1393
+ "stroke-width",
1394
+ item.rule === "solid" ? 0.8 : ctx.theme.labels.dividerStrokeWidth
1395
+ ).attr(
1396
+ "stroke-dasharray",
1397
+ item.rule === "solid" ? null : ctx.theme.labels.dividerStrokeDasharray
1398
+ );
1399
+ }
1400
+ let headerX = 0;
1401
+ if (item.swatch && swatchFill) {
1402
+ const size = lineH * 0.75;
1403
+ 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);
1404
+ headerX = size + 3;
1405
+ }
1406
+ 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);
1407
+ let lineY = lineH * 0.85 + lineH;
1408
+ if (item.unit) {
1409
+ 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);
1410
+ lineY += lineH;
1411
+ }
1028
1412
  if (item.description) {
1029
1413
  const lines = wrapText(item.description, charsPerLine);
1030
- 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);
1414
+ 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);
1031
1415
  lines.forEach((line, i) => {
1032
1416
  bodyEl.append("tspan").attr("x", 0).attr("dy", i === 0 ? 0 : lineH).text(line);
1033
1417
  });
1418
+ lineY += lines.length * lineH;
1419
+ }
1420
+ if (item.aquifer) {
1421
+ 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);
1034
1422
  }
1035
1423
  });
1036
1424
  }
@@ -1051,7 +1439,6 @@ var makePointKey = (type) => (d, i) => {
1051
1439
 
1052
1440
  // src/renderers/caves.renderer.ts
1053
1441
  function drawCaves(ctx, data) {
1054
- if (!data.length) return;
1055
1442
  const {
1056
1443
  groups: { cavesGroup },
1057
1444
  geoXLeft,
@@ -1061,7 +1448,7 @@ function drawCaves(ctx, data) {
1061
1448
  yScale,
1062
1449
  renderConfig,
1063
1450
  theme,
1064
- textures: textures3,
1451
+ textures: textures4,
1065
1452
  classes,
1066
1453
  tooltips
1067
1454
  } = ctx;
@@ -1098,7 +1485,7 @@ function drawCaves(ctx, data) {
1098
1485
  const botPath = botCut ? `M ${geoXLeft.toFixed(1)},${yBot.toFixed(1)} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath(botPts);
1099
1486
  const botReversed = botCut ? `L ${geoXLeft.toFixed(1)},${yBot.toFixed(1)}` : ptsToSmoothPath([...botPts].reverse(), { omitMoveTo: true });
1100
1487
  const closedPath = `${topPath} L ${geoXRight.toFixed(1)},${yBot.toFixed(1)}${botReversed} L ${geoXLeft.toFixed(1)},${yTop.toFixed(1)} Z`;
1101
- const caveTexture = cave.water_intake ? textures3.cave_wet : textures3.cave_dry;
1488
+ const caveTexture = cave.water_intake ? textures4.cave_wet : textures4.cave_dry;
1102
1489
  const caveStroke = cave.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
1103
1490
  g.append("path").attr("class", classes.caves.fill).attr("d", closedPath).attr("fill", caveTexture.url()).attr("fill-opacity", theme.cave.fillOpacity).attr("stroke", "none");
1104
1491
  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");
@@ -1110,9 +1497,6 @@ function drawCaves(ctx, data) {
1110
1497
  // src/renderers/centralizers.renderer.ts
1111
1498
  import * as d32 from "d3";
1112
1499
  import { getCentralizerDepths, getProfileDiamValues } from "@welldot/utils";
1113
- function diameterAt(items, depth) {
1114
- return items.find((i) => depth >= i.from && depth <= i.to)?.diameter;
1115
- }
1116
1500
  function drawCentralizers(ctx, data) {
1117
1501
  const group = ctx.groups.centralizerGroup;
1118
1502
  const cls = ctx.classes.centralizer;
@@ -1149,8 +1533,108 @@ function drawCentralizers(ctx, data) {
1149
1533
  });
1150
1534
  }
1151
1535
 
1536
+ // src/utils/layout.utils.ts
1537
+ function getProfileTopDepth(profile) {
1538
+ return Math.min(
1539
+ 0,
1540
+ ...[
1541
+ ...profile.surface_case ?? [],
1542
+ ...profile.well_case ?? [],
1543
+ ...profile.well_screen ?? []
1544
+ ].map((i) => i.from).filter(Number.isFinite)
1545
+ );
1546
+ }
1547
+ function resolvePanelWindows(panels, totalDepth, topDepth = 0) {
1548
+ if (panels.length > 0 && panels.every((p) => p.depthRange)) {
1549
+ return panels.map((p) => [p.depthRange[0], p.depthRange[1]]);
1550
+ }
1551
+ if (panels.length === 1) return [[topDepth, totalDepth]];
1552
+ const totalHeight = panels.reduce((s, p) => s + p.height, 0) || 1;
1553
+ const metersPerPx = (totalDepth - topDepth) / totalHeight;
1554
+ let current = topDepth;
1555
+ return panels.map((p) => {
1556
+ const from = current;
1557
+ current = from + p.height * metersPerPx;
1558
+ return [from, current];
1559
+ });
1560
+ }
1561
+ function getColumnHeaderPositions(geometry) {
1562
+ const { marginLeft, geoXLeft, geoXRight, pocoCenterX, descriptionX } = geometry;
1563
+ return {
1564
+ depth: marginLeft - 2,
1565
+ lithology: marginLeft + (geoXLeft + geoXRight) / 2,
1566
+ construction: marginLeft + pocoCenterX,
1567
+ ...descriptionX != null ? { description: marginLeft + descriptionX } : {}
1568
+ };
1569
+ }
1570
+ function getLithologyMarkers(lithology, depthFrom, depthTo) {
1571
+ const markers = [];
1572
+ lithology.forEach((layer, i) => {
1573
+ const from = Math.max(layer.from, depthFrom);
1574
+ const to = Math.min(layer.to, depthTo);
1575
+ if (to > from) markers.push({ index: i + 1, from, to });
1576
+ });
1577
+ return markers;
1578
+ }
1579
+ function getFractureMarkers(fractures, depthFrom, depthTo) {
1580
+ const markers = [];
1581
+ fractures.forEach((f, i) => {
1582
+ if (f.depth >= depthFrom && f.depth < depthTo) {
1583
+ markers.push({ label: `F${i + 1}`, depth: f.depth });
1584
+ }
1585
+ });
1586
+ return markers;
1587
+ }
1588
+ function getOverviewGeometry(totalDepth, height, depthFrom, depthTo) {
1589
+ const ratio = totalDepth > 0 ? height / totalDepth : 0;
1590
+ const y = (depth) => Math.min(height, Math.max(0, depth * ratio));
1591
+ return {
1592
+ y,
1593
+ window: { y: y(depthFrom), height: Math.max(1, y(depthTo) - y(depthFrom)) }
1594
+ };
1595
+ }
1596
+ function formatOverlapLabel(template, range) {
1597
+ return template.replace("{range}", range);
1598
+ }
1599
+
1600
+ // src/renderers/column-headers.renderer.ts
1601
+ function hasDescriptionColumn(ctx) {
1602
+ const { active, lithology } = ctx.renderConfig.labels;
1603
+ if (active === false) return false;
1604
+ if (Array.isArray(active) && !active.includes("lithology")) return false;
1605
+ if (lithology === false) return false;
1606
+ return !Array.isArray(lithology) || lithology.includes("description");
1607
+ }
1608
+ function drawColumnHeaders(ctx) {
1609
+ const group = ctx.groups.columnHeadersGroup;
1610
+ group.selectAll("*").remove();
1611
+ const cfg = ctx.renderConfig.columnHeaders;
1612
+ if (!cfg.active) return;
1613
+ const theme = ctx.theme.columnHeaders;
1614
+ const { margins } = ctx.state;
1615
+ const positions = getColumnHeaderPositions({
1616
+ marginLeft: margins.left,
1617
+ geoXLeft: ctx.geoXLeft,
1618
+ geoXRight: ctx.geoXRight,
1619
+ pocoCenterX: ctx.pocoCenterX,
1620
+ descriptionX: hasDescriptionColumn(ctx) ? ctx.geoXRight + ctx.renderConfig.labels.descriptionXOffset : void 0
1621
+ });
1622
+ const ruleY = margins.top - 2 - ctx.renderConfig.construction.cementPad.height;
1623
+ const textY = ruleY - 4;
1624
+ 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);
1625
+ addText(positions.depth, cfg.labels.depth, "end");
1626
+ addText(positions.lithology, cfg.labels.lithology, "middle");
1627
+ addText(positions.construction, cfg.labels.construction, "middle");
1628
+ if (positions.description != null) {
1629
+ addText(positions.description, cfg.labels.description, "start");
1630
+ }
1631
+ const overview = ctx.renderConfig.overview;
1632
+ 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);
1633
+ }
1634
+
1152
1635
  // src/renderers/construction-labels.renderer.ts
1153
1636
  import * as d33 from "d3";
1637
+ import { CONSTRUCTION_MATERIALS as CONSTRUCTION_MATERIALS2, getVocabLabel as getVocabLabel2 } from "@welldot/core";
1154
1638
  import { getProfileDiamValues as getProfileDiamValues2 } from "@welldot/utils";
1155
1639
  function drawConstructionLabels(ctx, data) {
1156
1640
  if (!ctx.renderConfig.constructionLabels.active) return;
@@ -1159,16 +1643,18 @@ function drawConstructionLabels(ctx, data) {
1159
1643
  const clct = ctx.theme.constructionLabels;
1160
1644
  const fmtD = (mm) => formatDiameter(mm, ctx.units.diameter, ctx.locale);
1161
1645
  const dUnit = getDiameterUnit(ctx.units.diameter, ctx.locale);
1646
+ const material = (value) => getVocabLabel2(CONSTRUCTION_MATERIALS2, value, ctx.locale);
1162
1647
  const maxXValues = getProfileDiamValues2(ctx.constructionData);
1163
1648
  const xScale = d33.scaleLinear().domain([0, d33.max(maxXValues) || 0]).range([0, ctx.POCO_WIDTH]);
1164
1649
  const occupied = [];
1165
- const placeLabel = (d, text) => {
1650
+ const onRight = clc.side === "right";
1651
+ const continuation = ctx.renderConfig.labels.continuation;
1652
+ const placeLabel = (d, label) => {
1166
1653
  const clampedFrom = Math.max(d.from, ctx.depthFrom);
1167
1654
  const clampedTo = Math.min(d.to, ctx.depthTo);
1168
1655
  if (clampedFrom >= clampedTo) return;
1656
+ const text = continuation?.active && d.from < ctx.depthFrom ? `\u2191 ${label}` : label;
1169
1657
  const midY = ctx.yScale((clampedFrom + clampedTo) / 2);
1170
- const leftEdgeX = (ctx.POCO_CENTER - xScale(d.diameter)) / 2;
1171
- const labelRightX = leftEdgeX - clc.xOffset;
1172
1658
  const charW = clct.fontSize * 0.52;
1173
1659
  const lineH = clct.fontSize * 1.35;
1174
1660
  const vertPad = 6;
@@ -1185,16 +1671,17 @@ function drawConstructionLabels(ctx, data) {
1185
1671
  lines = [text];
1186
1672
  boxW = text.length * charW + 8;
1187
1673
  }
1674
+ const labelRightX = onRight ? (ctx.POCO_CENTER + ctx.POCO_WIDTH) / 2 + clc.xOffset + boxW : (ctx.POCO_CENTER - xScale(d.diameter)) / 2 - clc.xOffset;
1188
1675
  const labelH = lines.length * lineH + vertPad;
1189
- let labelY = midY - labelH / 2;
1676
+ let labelY = onRight ? ctx.yScale(clampedFrom) + 1 : midY - labelH / 2;
1190
1677
  for (const pos of occupied) {
1191
1678
  if (labelY < pos.bot && labelY + labelH > pos.top) labelY = pos.bot + 2;
1192
1679
  }
1193
1680
  occupied.push({ top: labelY, bot: labelY + labelH });
1194
1681
  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);
1195
- 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");
1682
+ 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");
1196
1683
  lines.forEach((line, i) => {
1197
- textEl.append("tspan").attr("x", labelRightX - 4).attr("dy", i === 0 ? 0 : lineH).text(line);
1684
+ textEl.append("tspan").attr("x", onRight ? labelRightX - boxW + 4 : labelRightX - 4).attr("dy", i === 0 ? 0 : lineH).text(line);
1198
1685
  });
1199
1686
  };
1200
1687
  let lastDiam = 0;
@@ -1207,7 +1694,7 @@ function drawConstructionLabels(ctx, data) {
1207
1694
  }).forEach(
1208
1695
  (d) => placeLabel(
1209
1696
  d,
1210
- `${clc.labels.wellCasePrefix} ${fmtD(d.diameter)}${dUnit} ${d.type}`
1697
+ `${clc.labels.wellCasePrefix} ${fmtD(d.diameter)}${dUnit} ${material(d.type)}`
1211
1698
  )
1212
1699
  );
1213
1700
  lastDiam = 0;
@@ -1220,7 +1707,7 @@ function drawConstructionLabels(ctx, data) {
1220
1707
  }).forEach(
1221
1708
  (d) => placeLabel(
1222
1709
  d,
1223
- `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${d.type} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot)}${dUnit}`
1710
+ `${clc.labels.wellScreenPrefix} ${fmtD(d.diameter)}${dUnit} ${material(d.type)} ${clc.labels.wellScreenSlotPrefix} ${fmtD(d.screen_slot)}${dUnit}`
1224
1711
  )
1225
1712
  );
1226
1713
  }
@@ -1228,110 +1715,307 @@ function drawConstructionLabels(ctx, data) {
1228
1715
  // src/renderers/construction.renderer.ts
1229
1716
  import * as d34 from "d3";
1230
1717
  import { getProfileDiamValues as getProfileDiamValues3 } from "@welldot/utils";
1718
+ var entering = /* @__PURE__ */ new WeakSet();
1719
+ function constructionXScale(ctx) {
1720
+ const max6 = d34.max(getProfileDiamValues3(ctx.constructionData)) || 0;
1721
+ return d34.scaleLinear().domain([0, max6]).range([0, ctx.POCO_WIDTH]);
1722
+ }
1723
+ function outlineScales(ctx) {
1724
+ const x = constructionXScale(ctx);
1725
+ return {
1726
+ y: ctx.yScale,
1727
+ left: (d) => (ctx.POCO_CENTER - x(d)) / 2,
1728
+ right: (d) => (ctx.POCO_CENTER + x(d)) / 2,
1729
+ depthFrom: ctx.depthFrom,
1730
+ depthTo: ctx.depthTo
1731
+ };
1732
+ }
1733
+ var surfaceCaseDiameter = (ctx, diameter) => diameter * (1 + ctx.renderConfig.construction.surfaceCase.diameterPaddingRatio);
1734
+ function joinIntervalLayer(ctx, group, cls, data, key, tooltip, { shoe = false, pathFill = false, ends = false } = {}) {
1735
+ const sel = group.selectAll(`g.${cls}`).data(data, makeIntervalKey(key));
1736
+ sel.exit().transition(ctx.transition).style("opacity", 0).remove();
1737
+ const top = (d) => ctx.yScale(Math.max(d.from, ctx.depthFrom));
1738
+ 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);
1739
+ if (pathFill)
1740
+ entered.append("path").attr("class", `${cls}-fill`).attr("stroke", "none").attr("fill-rule", "evenodd").attr("d", "");
1741
+ else
1742
+ entered.append("rect").attr("class", `${cls}-fill`).attr("stroke", "none").attr("y", top).attr("height", 0);
1743
+ entered.append("path").attr("class", `${cls}-outline`).attr("fill", "none").attr("d", "");
1744
+ if (shoe)
1745
+ entered.append("path").attr("class", `${cls}-shoe`).attr("stroke-linejoin", "round").attr("d", "");
1746
+ if (ends)
1747
+ entered.append("path").attr("class", `${cls}-ends`).attr("fill", "none").attr("d", "");
1748
+ return entered.merge(sel);
1749
+ }
1750
+ function drawCementPad(ctx, pad) {
1751
+ const cls = ctx.classes.cementPad.item;
1752
+ const visible = !!pad?.thickness && ctx.depthFrom <= 0 && ctx.depthTo > 0;
1753
+ const sel = ctx.groups.cementPadGroup.selectAll(`g.${cls}`).data(visible ? [pad] : [], () => "cement_pad");
1754
+ sel.exit().remove();
1755
+ if (!visible) return;
1756
+ const entered = sel.enter().append("g").attr("class", cls).on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1757
+ entered.append("path").attr("class", `${cls}-slab`);
1758
+ entered.append("path").attr("class", `${cls}-slab`);
1759
+ const x = constructionXScale(ctx);
1760
+ const { opening, outer } = getPadDiameters(
1761
+ ctx.constructionData,
1762
+ ctx.renderConfig.construction.surfaceCase.diameterPaddingRatio
1763
+ );
1764
+ const { slabs } = getCementPadGeometry(
1765
+ ctx.POCO_WIDTH,
1766
+ ctx.POCO_CENTER,
1767
+ ctx.yScale(0),
1768
+ ctx.renderConfig.construction.cementPad,
1769
+ x(opening),
1770
+ x(outer)
1771
+ );
1772
+ 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");
1773
+ }
1774
+ function layoutConstruction(ctx, animate = false) {
1775
+ const c = ctx.classes;
1776
+ const scales = outlineScales(ctx);
1777
+ const data = ctx.constructionData;
1778
+ const motion = (sel) => animate ? withTransition(sel, ctx.transition) : sel;
1779
+ const yTop = (d) => ctx.yScale(Math.max(d.from, ctx.depthFrom));
1780
+ const yBottom = (d) => ctx.yScale(Math.min(d.to, ctx.depthTo));
1781
+ const collapsed = (d) => ({
1782
+ ...scales,
1783
+ y: () => yTop(d)
1784
+ });
1785
+ const shapeParts = (items, part, attr, shape) => {
1786
+ const parts = items.select(part);
1787
+ if (animate)
1788
+ parts.filter(function() {
1789
+ return entering.has(this.parentNode);
1790
+ }).attr(attr, (d) => shape(d, collapsed(d)));
1791
+ motion(parts).attr(attr, (d) => shape(d, scales));
1792
+ };
1793
+ const layoutLayer = (group, cls, diameter, outline, {
1794
+ shoe: shoe2,
1795
+ ends,
1796
+ fill
1797
+ } = {}) => {
1798
+ const items = group.selectAll(`g.${cls}`);
1799
+ shapeParts(items, `.${cls}-outline`, "d", outline);
1800
+ if (shoe2) shapeParts(items, `.${cls}-shoe`, "d", shoe2);
1801
+ if (ends) shapeParts(items, `.${cls}-ends`, "d", ends);
1802
+ if (fill) {
1803
+ shapeParts(items, `.${cls}-fill`, "d", fill);
1804
+ return;
1805
+ }
1806
+ motion(
1807
+ items.select(`.${cls}-fill`).attr("x", (d) => scales.left(diameter(d))).attr(
1808
+ "width",
1809
+ (d) => scales.right(diameter(d)) - scales.left(diameter(d))
1810
+ )
1811
+ ).attr("y", yTop).attr("height", (d) => Math.max(0, yBottom(d) - yTop(d)));
1812
+ };
1813
+ const walls = (d, s, dia = d.diameter) => pipeWallsPath(
1814
+ s.left(dia),
1815
+ s.right(dia),
1816
+ s.y(Math.max(d.from, s.depthFrom)),
1817
+ s.y(Math.min(d.to, s.depthTo))
1818
+ );
1819
+ layoutLayer(
1820
+ ctx.groups.holeGroup,
1821
+ c.boreHole.rect,
1822
+ (d) => d.diameter,
1823
+ (d, s) => boreHoleOutlinePath(d, data.bore_hole, s)
1824
+ );
1825
+ const pipes = [...data.well_case, ...data.well_screen];
1826
+ const insideSurface = [...data.bore_hole, ...data.hole_fill, ...pipes];
1827
+ const scDiameter = (d) => surfaceCaseDiameter(ctx, d.diameter);
1828
+ layoutLayer(
1829
+ ctx.groups.surfaceCaseGroup,
1830
+ c.surfaceCase.rect,
1831
+ scDiameter,
1832
+ (d, s) => walls(d, s, scDiameter(d)),
1833
+ // Only the ring outside the hole: the texture never shows under the casing.
1834
+ {
1835
+ fill: (d, s) => annulusFillPath({ ...d, diameter: scDiameter(d) }, insideSurface, s)
1836
+ }
1837
+ );
1838
+ layoutLayer(
1839
+ ctx.groups.holeFillGroup,
1840
+ c.holeFill.rect,
1841
+ (d) => d.diameter,
1842
+ (d, s) => holeFillOutlinePath(d, pipes, s),
1843
+ { fill: (d, s) => annulusFillPath(d, pipes, s) }
1844
+ );
1845
+ const string = [...pipes, ...data.reduction ?? []];
1846
+ const shoeCfg = ctx.renderConfig.construction.casingShoe;
1847
+ const endsCfg = ctx.renderConfig.construction.screenEnds;
1848
+ const room = (d, depth, size) => {
1849
+ const hole = diameterAt(data.bore_hole, depth);
1850
+ const annulus = hole !== void 0 && hole > d.diameter ? scales.left(d.diameter) - scales.left(hole) - 1 : size;
1851
+ return Math.max(2, Math.min(size, annulus));
1852
+ };
1853
+ const shoe = (d, s) => {
1854
+ const ends = getIntervalEnds(d, string, ctx.depthFrom, ctx.depthTo);
1855
+ if (!shoeCfg.active || ends.cutBottom || ends.joinedBottom) return "";
1856
+ return shoePath(
1857
+ s.left(d.diameter),
1858
+ s.right(d.diameter),
1859
+ s.y(Math.min(d.to, s.depthTo)),
1860
+ room(d, d.to, shoeCfg.size)
1861
+ );
1862
+ };
1863
+ const screenEnds = (d, s) => {
1864
+ if (!endsCfg.active) return "";
1865
+ const ends = getIntervalEnds(d, string, ctx.depthFrom, ctx.depthTo);
1866
+ const mark = (depth) => screenEndPath(s.left(d.diameter), s.right(d.diameter), s.y(depth));
1867
+ return (ends.cutTop ? "" : mark(d.from)) + (ends.cutBottom ? "" : mark(d.to));
1868
+ };
1869
+ layoutLayer(
1870
+ ctx.groups.wellCaseGroup,
1871
+ c.wellCase.rect,
1872
+ (d) => d.diameter,
1873
+ walls,
1874
+ { shoe }
1875
+ );
1876
+ layoutLayer(
1877
+ ctx.groups.wellScreenGroup,
1878
+ c.wellScreen.rect,
1879
+ (d) => d.diameter,
1880
+ walls,
1881
+ { shoe, ends: screenEnds }
1882
+ );
1883
+ const reductions = ctx.groups.reductionGroup.selectAll(`g.${c.reduction.item}`);
1884
+ const corners = (d, s) => {
1885
+ const t = s.y(Math.max(d.from, s.depthFrom));
1886
+ const b = s.y(Math.min(d.to, s.depthTo));
1887
+ return {
1888
+ tl: `${s.left(d.diam_from)},${t}`,
1889
+ tr: `${s.right(d.diam_from)},${t}`,
1890
+ bl: `${s.left(d.diam_to)},${b}`,
1891
+ br: `${s.right(d.diam_to)},${b}`
1892
+ };
1893
+ };
1894
+ shapeParts(reductions, `.${c.reduction.item}-fill`, "points", (d, s) => {
1895
+ const p = corners(d, s);
1896
+ return `${p.tl} ${p.tr} ${p.br} ${p.bl}`;
1897
+ });
1898
+ shapeParts(reductions, `.${c.reduction.item}-outline`, "d", (d, s) => {
1899
+ const p = corners(d, s);
1900
+ return `M${p.tl}L${p.bl}M${p.tr}L${p.br}`;
1901
+ });
1902
+ if (animate) {
1903
+ for (const group of [
1904
+ ctx.groups.holeGroup,
1905
+ ctx.groups.surfaceCaseGroup,
1906
+ ctx.groups.holeFillGroup,
1907
+ ctx.groups.wellCaseGroup,
1908
+ ctx.groups.wellScreenGroup,
1909
+ ctx.groups.reductionGroup
1910
+ ])
1911
+ group.selectAll("g").each(function() {
1912
+ entering.delete(this);
1913
+ });
1914
+ }
1915
+ }
1231
1916
  function drawConstructive(ctx, data) {
1232
1917
  const { getHeight, getYPos } = getYAxisFunctions(ctx.yScale, {
1233
1918
  from: ctx.depthFrom,
1234
1919
  to: ctx.depthTo
1235
1920
  });
1236
- const maxXValues = getProfileDiamValues3(ctx.constructionData);
1237
- const maxXValueConstruction = d34.max(maxXValues) || 0;
1238
- const xScale = d34.scaleLinear().domain([0, maxXValueConstruction]).range([0, ctx.POCO_WIDTH]);
1239
- const rcc = ctx.renderConfig.construction;
1240
- const yPos = getYPos;
1241
- ctx.groups.constructionGroup.selectAll(`.${ctx.classes.cementPad.item}`).remove();
1242
- if (data.cement_pad && data.cement_pad.thickness && ctx.depthFrom === 0) {
1243
- const cementPad = ctx.groups.cementPadGroup.selectAll("rect").data([data.cement_pad], () => "cement_pad");
1244
- cementPad.exit().remove();
1245
- const newCementPad = cementPad.enter().append("rect").attr("class", ctx.classes.cementPad.item).attr(
1246
- "x",
1247
- (d) => (ctx.POCO_CENTER - xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)) / 2
1248
- ).attr("y", (d) => {
1249
- return ctx.yScale(0) - ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1250
- }).attr(
1251
- "width",
1252
- (d) => xScale(d.width * rcc.cementPad.widthMultiplier * 1e3 / 2)
1253
- ).attr("height", (d) => {
1254
- return ctx.yScale(d.thickness * rcc.cementPad.thicknessMultiplier);
1255
- }).attr("fill", ctx.textures.pad.url()).attr("stroke", ctx.theme.cementPad.stroke).attr("stroke-width", ctx.theme.cementPad.strokeWidth);
1256
- newCementPad.on("mouseover", ctx.tooltips.cementPad.show).on("mouseout", ctx.tooltips.cementPad.hide);
1257
- }
1258
- const hole = ctx.groups.holeGroup.selectAll(`.${ctx.classes.boreHole.rect}`).data(data.bore_hole, makeIntervalKey("bore_hole"));
1259
- hole.exit().transition(ctx.transition).attr("height", 0).remove();
1260
- 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);
1261
- const mergedHole = mergeEnter(newHole, hole).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1262
- withTransition(mergedHole, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1263
- const surfaceCaseGs = ctx.groups.surfaceCaseGroup.selectAll(`g.${ctx.classes.surfaceCase.rect}`).data(data.surface_case, makeIntervalKey("surface_case"));
1264
- surfaceCaseGs.exit().transition(ctx.transition).style("opacity", 0).remove();
1265
- const newSC = surfaceCaseGs.enter().append("g").attr("class", ctx.classes.surfaceCase.rect).on("mouseover", ctx.tooltips.surfaceCase.show).on("mouseout", ctx.tooltips.surfaceCase.hide);
1266
- newSC.append("rect").attr("class", "surface-case-fill").attr("stroke", "none").attr("y", yPos).attr("height", 0);
1267
- newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1268
- newSC.append("line").attr("class", "surface-case-side").attr("stroke", ctx.theme.surfaceCase.stroke).attr("stroke-width", ctx.theme.surfaceCase.strokeWidth);
1269
- const mergedSC = mergeEnter(newSC, surfaceCaseGs);
1270
- mergedSC.each((d, i, nodes) => {
1271
- const x = (ctx.POCO_CENTER - xScale(
1272
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1273
- )) / 2;
1274
- const w = xScale(
1275
- d.diameter + d.diameter * rcc.surfaceCase.diameterPaddingRatio
1921
+ const c = ctx.classes;
1922
+ const theme = ctx.theme;
1923
+ drawCementPad(ctx, data.cement_pad);
1924
+ joinIntervalLayer(
1925
+ ctx,
1926
+ ctx.groups.holeGroup,
1927
+ c.boreHole.rect,
1928
+ data.bore_hole,
1929
+ "bore_hole",
1930
+ ctx.tooltips.hole
1931
+ ).attr("opacity", theme.boreHole.opacity).call((g) => {
1932
+ g.select(`.${c.boreHole.rect}-fill`).attr("fill", theme.boreHole.fill);
1933
+ g.select(`.${c.boreHole.rect}-outline`).attr("stroke", theme.boreHole.stroke).attr("stroke-width", theme.boreHole.strokeWidth).attr("stroke-dasharray", theme.boreHole.strokeDasharray);
1934
+ });
1935
+ joinIntervalLayer(
1936
+ ctx,
1937
+ ctx.groups.surfaceCaseGroup,
1938
+ c.surfaceCase.rect,
1939
+ data.surface_case,
1940
+ "surface_case",
1941
+ ctx.tooltips.surfaceCase,
1942
+ { pathFill: true }
1943
+ ).call((g) => {
1944
+ g.select(`.${c.surfaceCase.rect}-fill`).attr(
1945
+ "fill",
1946
+ ctx.textures.surface_case.url()
1276
1947
  );
1277
- const g = d34.select(nodes[i]);
1278
- g.select(".surface-case-fill").attr("x", x).attr("width", w).attr("fill", ctx.textures.surface_case.url());
1279
- const sideNodes = g.selectAll(".surface-case-side").nodes();
1280
- d34.select(sideNodes[0]).attr("x1", x).attr("x2", x);
1281
- d34.select(sideNodes[1]).attr("x1", x + w).attr("x2", x + w);
1948
+ g.select(`.${c.surfaceCase.rect}-outline`).attr("stroke", theme.surfaceCase.stroke).attr("stroke-width", theme.surfaceCase.strokeWidth);
1282
1949
  });
1283
- withTransition(
1284
- mergedSC.select(".surface-case-fill"),
1285
- ctx.transition
1286
- ).attr("y", (d) => getYPos(d)).attr("height", (d) => getHeight(d));
1287
- mergedSC.selectAll(".surface-case-side").attr("y1", (d) => getYPos(d)).attr(
1288
- "y2",
1289
- (d) => getYPos(d) + getHeight(d)
1290
- );
1291
- const holeFill = ctx.groups.holeFillGroup.selectAll(`.${ctx.classes.holeFill.rect}`).data(data.hole_fill, makeIntervalKey("hole_fill"));
1292
- holeFill.exit().transition(ctx.transition).attr("height", 0).remove();
1293
- 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);
1294
- const mergedHoleFill = mergeEnter(newHoleFill, holeFill).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1295
- withTransition(mergedHoleFill, ctx.transition).attr("y", getYPos).attr("height", getHeight).attr("fill", (d) => ctx.textures[d.type].url());
1296
- const wellCase = ctx.groups.wellCaseGroup.selectAll(`.${ctx.classes.wellCase.rect}`).data(data.well_case, makeIntervalKey("well_case"));
1297
- wellCase.exit().transition(ctx.transition).attr("height", 0).remove();
1298
- 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);
1299
- const mergedWellCase = mergeEnter(newWellCase, wellCase).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1300
- withTransition(mergedWellCase, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1301
- const wellScreen = ctx.groups.wellScreenGroup.selectAll(`.${ctx.classes.wellScreen.rect}`).data(data.well_screen, makeIntervalKey("well_screen"));
1302
- wellScreen.exit().transition(ctx.transition).attr("height", 0).remove();
1303
- 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);
1304
- const mergedWellScreen = mergeEnter(newWellScreen, wellScreen).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1305
- withTransition(mergedWellScreen, ctx.transition).attr("y", getYPos).attr("height", getHeight);
1306
- const reductionPoints = (d, yTop, yBot) => {
1307
- const xTopLeft = (ctx.POCO_CENTER - xScale(d.diam_from)) / 2;
1308
- const xTopRight = (ctx.POCO_CENTER + xScale(d.diam_from)) / 2;
1309
- const xBotLeft = (ctx.POCO_CENTER - xScale(d.diam_to)) / 2;
1310
- const xBotRight = (ctx.POCO_CENTER + xScale(d.diam_to)) / 2;
1311
- return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
1312
- };
1313
- const reduction = ctx.groups.reductionGroup.selectAll(`.${ctx.classes.reduction.item}`).data(data.reduction, makeIntervalKey("reduction"));
1950
+ joinIntervalLayer(
1951
+ ctx,
1952
+ ctx.groups.holeFillGroup,
1953
+ c.holeFill.rect,
1954
+ data.hole_fill,
1955
+ "hole_fill",
1956
+ ctx.tooltips.holeFill,
1957
+ { pathFill: true }
1958
+ ).call((g) => {
1959
+ g.select(`.${c.holeFill.rect}-fill`).attr(
1960
+ "fill",
1961
+ (d) => ctx.textures[d.type].url()
1962
+ );
1963
+ g.select(`.${c.holeFill.rect}-outline`).attr("stroke", theme.holeFill.stroke).attr("stroke-width", theme.holeFill.strokeWidth);
1964
+ });
1965
+ for (const [group, cls, items, key, tooltip, fill, stroke] of [
1966
+ [
1967
+ ctx.groups.wellCaseGroup,
1968
+ c.wellCase.rect,
1969
+ data.well_case,
1970
+ "well_case",
1971
+ ctx.tooltips.wellCase,
1972
+ theme.wellCase.fill,
1973
+ theme.wellCase
1974
+ ],
1975
+ [
1976
+ ctx.groups.wellScreenGroup,
1977
+ c.wellScreen.rect,
1978
+ data.well_screen,
1979
+ "well_screen",
1980
+ ctx.tooltips.wellScreen,
1981
+ ctx.textures.well_screen.url(),
1982
+ theme.wellScreen
1983
+ ]
1984
+ ]) {
1985
+ joinIntervalLayer(ctx, group, cls, items, key, tooltip, {
1986
+ shoe: true,
1987
+ ends: cls === c.wellScreen.rect
1988
+ }).call((g) => {
1989
+ g.select(`.${cls}-fill`).attr("fill", fill);
1990
+ g.select(`.${cls}-outline`).attr("stroke", stroke.stroke).attr("stroke-width", stroke.strokeWidth);
1991
+ g.select(`.${cls}-shoe`).attr("fill", stroke.stroke).attr("stroke", stroke.stroke).attr("stroke-width", 0.5);
1992
+ g.select(`.${cls}-ends`).attr("stroke", stroke.stroke).attr("stroke-width", theme.wellScreen.endStrokeWidth).attr("stroke-dasharray", theme.wellScreen.endDasharray);
1993
+ });
1994
+ }
1995
+ const reduction = ctx.groups.reductionGroup.selectAll(`g.${c.reduction.item}`).data(data.reduction ?? [], makeIntervalKey("reduction"));
1314
1996
  reduction.exit().transition(ctx.transition).style("opacity", 0).remove();
1315
- const collapsedPoints = (d) => {
1316
- const r = d;
1317
- const y = getYPos(r);
1318
- return reductionPoints(r, y, y);
1319
- };
1320
- 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);
1321
- const mergedReduction = mergeEnter(newReduction, reduction);
1322
- withTransition(mergedReduction, ctx.transition).attr(
1323
- "points",
1324
- (d) => reductionPoints(d, getYPos(d), getYPos(d) + getHeight(d))
1997
+ 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);
1998
+ newReduction.append("polygon").attr("class", `${c.reduction.item}-fill`).attr("stroke", "none");
1999
+ newReduction.append("path").attr("class", `${c.reduction.item}-outline`).attr("fill", "none");
2000
+ newReduction.merge(reduction).call((g) => {
2001
+ g.select(`.${c.reduction.item}-fill`).attr("fill", theme.reduction.fill);
2002
+ g.select(`.${c.reduction.item}-outline`).attr("stroke", theme.reduction.stroke).attr("stroke-width", theme.reduction.strokeWidth);
2003
+ });
2004
+ const mergedConflicts = mergeConflicts(
2005
+ [
2006
+ ...getConflictAreas(data.well_case, data.well_screen),
2007
+ ...getConflictAreas(data.well_screen, data.well_case)
2008
+ ],
2009
+ 1
1325
2010
  );
1326
- const conflictAreas = [];
1327
- conflictAreas.push(...getConflictAreas(data.well_case, data.well_screen));
1328
- conflictAreas.push(...getConflictAreas(data.well_screen, data.well_case));
1329
- const mergedConflicts = mergeConflicts(conflictAreas, 1);
1330
- const conflict = ctx.groups.conflictGroup.selectAll(`.${ctx.classes.conflict.rect}`).data(mergedConflicts, makeIntervalKey("conflict"));
2011
+ const xScale = constructionXScale(ctx);
2012
+ const yPos = getYPos;
2013
+ const conflict = ctx.groups.conflictGroup.selectAll(`.${c.conflict.rect}`).data(mergedConflicts, makeIntervalKey("conflict"));
1331
2014
  conflict.exit().transition(ctx.transition).attr("height", 0).remove();
1332
- 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);
2015
+ 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);
1333
2016
  const mergedConflict = mergeEnter(newConflict, conflict).attr("x", (d) => (ctx.POCO_CENTER - xScale(d.diameter)) / 2).attr("width", (d) => xScale(d.diameter));
1334
- withTransition(mergedConflict, ctx.transition).attr("y", ctx.yScale(0)).attr("height", getHeight);
2017
+ withTransition(mergedConflict, ctx.transition).attr("y", getYPos).attr("height", getHeight);
2018
+ layoutConstruction(ctx, true);
1335
2019
  }
1336
2020
 
1337
2021
  // src/renderers/fractures.renderer.ts
@@ -1347,7 +2031,6 @@ function wavyLine(rng, steps, baseY, jitter, startInset, endInset) {
1347
2031
  return pts;
1348
2032
  }
1349
2033
  function drawFractures(ctx, data) {
1350
- if (!data.length) return;
1351
2034
  const {
1352
2035
  groups: { fracturesGroup },
1353
2036
  POCO_WIDTH,
@@ -1369,7 +2052,7 @@ function drawFractures(ctx, data) {
1369
2052
  const merged = mergeEnter(entered, groups);
1370
2053
  merged.attr(
1371
2054
  "transform",
1372
- (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fracture.dip},${pocoCenterX},0)`
2055
+ (fracture) => `translate(0,${yScale(fracture.depth)}) rotate(${fractureDip(fracture.dip)},${pocoCenterX},0)`
1373
2056
  );
1374
2057
  merged.each(function(fracture) {
1375
2058
  const g = d35.select(this);
@@ -1514,24 +2197,26 @@ function drawHighlights(ctx, hl) {
1514
2197
  const maxXVals = getProfileDiamValues4(ctx.constructionData);
1515
2198
  const xScaleConstr = d36.scaleLinear().domain([0, d36.max(maxXVals) || 0]).range([0, ctx.POCO_WIDTH]);
1516
2199
  const constructionConfig = ctx.renderConfig.construction;
1517
- if (hl.cement_pad && ctx.depthFrom === 0 && ctx.constructionData.cement_pad?.thickness) {
1518
- const cp = ctx.constructionData.cement_pad;
1519
- const cpPadWidth = cp.width * constructionConfig.cementPad.widthMultiplier * 1e3 / 2;
1520
- const cpX = (ctx.POCO_CENTER - xScaleConstr(cpPadWidth)) / 2;
1521
- const cpY = ctx.yScale(0) - ctx.yScale(
1522
- cp.thickness * constructionConfig.cementPad.thicknessMultiplier
2200
+ if (hl.cement_pad && ctx.depthFrom <= 0 && ctx.depthTo > 0 && ctx.constructionData.cement_pad?.thickness) {
2201
+ const { opening, outer } = getPadDiameters(
2202
+ ctx.constructionData,
2203
+ constructionConfig.surfaceCase.diameterPaddingRatio
1523
2204
  );
1524
- const cpW = xScaleConstr(cpPadWidth);
1525
- const cpH = ctx.yScale(
1526
- cp.thickness * constructionConfig.cementPad.thicknessMultiplier
2205
+ const pad = getCementPadRect(
2206
+ ctx.POCO_WIDTH,
2207
+ ctx.POCO_CENTER,
2208
+ ctx.yScale(0),
2209
+ constructionConfig.cementPad,
2210
+ xScaleConstr(opening),
2211
+ xScaleConstr(outer)
1527
2212
  );
1528
2213
  drawHighlightItem(
1529
2214
  ctx,
1530
2215
  ctx.groups.highlightsConstructionGroup,
1531
- cpX,
1532
- cpY,
1533
- cpW,
1534
- cpH,
2216
+ pad.x,
2217
+ pad.y,
2218
+ pad.width,
2219
+ pad.height,
1535
2220
  hl.cement_pad.label
1536
2221
  );
1537
2222
  }
@@ -1607,8 +2292,12 @@ function drawHighlights(ctx, hl) {
1607
2292
  // src/renderers/legend.renderer.ts
1608
2293
  import * as d3module from "d3";
1609
2294
  import { defu } from "defu";
2295
+ import { getCurrentPump } from "@welldot/utils";
1610
2296
  var d37 = { ...d3module };
1611
- function drawWellLegend(selector, profile, options = {}) {
2297
+ var SYMBOL_WIDTH = 24;
2298
+ var SYMBOL_HEIGHT = 12;
2299
+ var SYMBOL_MARGIN = 2;
2300
+ function getWellLegendItems(profile, labels = INTERACTIVE_RENDER_CONFIG.legend.labels) {
1612
2301
  const fractures = profile.fractures ?? [];
1613
2302
  const caves = profile.caves ?? [];
1614
2303
  const boreHoles = profile.bore_hole ?? [];
@@ -1629,6 +2318,7 @@ function drawWellLegend(selector, profile, options = {}) {
1629
2318
  const hasWellScreen = wellScreens.length > 0;
1630
2319
  const hasReduction = reductions.length > 0;
1631
2320
  const hasCentralizer = (profile.centralizers ?? []).length > 0;
2321
+ const hasPump = getCurrentPump(profile)?.intake_depth !== void 0;
1632
2322
  const hasCementPad = !!profile.cement_pad?.thickness;
1633
2323
  const hasConflict = mergeConflicts(
1634
2324
  [
@@ -1637,222 +2327,314 @@ function drawWellLegend(selector, profile, options = {}) {
1637
2327
  ],
1638
2328
  1
1639
2329
  ).length > 0;
1640
- const cfg = defu(
1641
- options.config ?? {},
1642
- INTERACTIVE_RENDER_CONFIG.legend
1643
- );
1644
- const theme = defu(options.theme ?? {}, DEFAULT_WELL_THEME);
1645
- const cls = options.classNames ? { ...DEFAULT_COMPONENTS_CLASS_NAMES.legend, ...options.classNames } : DEFAULT_COMPONENTS_CLASS_NAMES.legend;
1646
2330
  const items = [];
1647
2331
  if (hasSimple)
1648
2332
  items.push({
1649
2333
  kind: "fracture",
1650
- label: cfg.labels.fractureSingle,
2334
+ key: "fractureSingle",
2335
+ label: labels.fractureSingle,
1651
2336
  swarm: false,
1652
2337
  water_intake: false
1653
2338
  });
1654
2339
  if (hasSwarm)
1655
2340
  items.push({
1656
2341
  kind: "fracture",
1657
- label: cfg.labels.fractureSwarm,
2342
+ key: "fractureSwarm",
2343
+ label: labels.fractureSwarm,
1658
2344
  swarm: true,
1659
2345
  water_intake: false
1660
2346
  });
1661
2347
  if (hasWaterFx)
1662
2348
  items.push({
1663
2349
  kind: "fracture",
1664
- label: cfg.labels.fractureWater,
2350
+ key: "fractureWater",
2351
+ label: labels.fractureWater,
1665
2352
  swarm: false,
1666
2353
  water_intake: true
1667
2354
  });
1668
2355
  if (hasCaveDry)
1669
2356
  items.push({
1670
2357
  kind: "cave",
1671
- label: cfg.labels.caveDry,
2358
+ key: "caveDry",
2359
+ label: labels.caveDry,
1672
2360
  water_intake: false
1673
2361
  });
1674
2362
  if (hasCaveWet)
1675
- items.push({ kind: "cave", label: cfg.labels.caveWet, water_intake: true });
2363
+ items.push({
2364
+ kind: "cave",
2365
+ key: "caveWet",
2366
+ label: labels.caveWet,
2367
+ water_intake: true
2368
+ });
1676
2369
  if (hasCementPad)
1677
2370
  items.push({
1678
2371
  kind: "construction",
1679
- label: cfg.labels.cementPad,
2372
+ key: "cementPad",
2373
+ label: labels.cementPad,
1680
2374
  subKind: "cementPad"
1681
2375
  });
1682
2376
  if (hasBoreHole)
1683
2377
  items.push({
1684
2378
  kind: "construction",
1685
- label: cfg.labels.boreHole,
2379
+ key: "boreHole",
2380
+ label: labels.boreHole,
1686
2381
  subKind: "boreHole"
1687
2382
  });
1688
2383
  if (hasSurfaceCase)
1689
2384
  items.push({
1690
2385
  kind: "construction",
1691
- label: cfg.labels.surfaceCase,
2386
+ key: "surfaceCase",
2387
+ label: labels.surfaceCase,
1692
2388
  subKind: "surfaceCase"
1693
2389
  });
1694
2390
  if (hasHoleFillGravel)
1695
2391
  items.push({
1696
2392
  kind: "construction",
1697
- label: cfg.labels.holeFillGravel,
2393
+ key: "holeFillGravel",
2394
+ label: labels.holeFillGravel,
1698
2395
  subKind: "holeFillGravel"
1699
2396
  });
1700
2397
  if (hasHoleFillSeal)
1701
2398
  items.push({
1702
2399
  kind: "construction",
1703
- label: cfg.labels.holeFillSeal,
2400
+ key: "holeFillSeal",
2401
+ label: labels.holeFillSeal,
1704
2402
  subKind: "holeFillSeal"
1705
2403
  });
1706
2404
  if (hasWellCase)
1707
2405
  items.push({
1708
2406
  kind: "construction",
1709
- label: cfg.labels.wellCase,
2407
+ key: "wellCase",
2408
+ label: labels.wellCase,
1710
2409
  subKind: "wellCase"
1711
2410
  });
1712
2411
  if (hasWellScreen)
1713
2412
  items.push({
1714
2413
  kind: "construction",
1715
- label: cfg.labels.wellScreen,
2414
+ key: "wellScreen",
2415
+ label: labels.wellScreen,
1716
2416
  subKind: "wellScreen"
1717
2417
  });
1718
2418
  if (hasReduction)
1719
2419
  items.push({
1720
2420
  kind: "construction",
1721
- label: cfg.labels.reduction,
2421
+ key: "reduction",
2422
+ label: labels.reduction,
1722
2423
  subKind: "reduction"
1723
2424
  });
1724
2425
  if (hasCentralizer)
1725
2426
  items.push({
1726
2427
  kind: "construction",
1727
- label: cfg.labels.centralizer,
2428
+ key: "centralizer",
2429
+ label: labels.centralizer,
1728
2430
  subKind: "centralizer"
1729
2431
  });
2432
+ if (hasPump)
2433
+ items.push({
2434
+ kind: "construction",
2435
+ key: "pump",
2436
+ label: labels.pump,
2437
+ subKind: "pump"
2438
+ });
1730
2439
  if (hasConflict)
1731
2440
  items.push({
1732
2441
  kind: "construction",
1733
- label: cfg.labels.conflict,
2442
+ key: "conflict",
2443
+ label: labels.conflict,
1734
2444
  subKind: "conflict"
1735
2445
  });
2446
+ return items;
2447
+ }
2448
+ function drawLegendSymbol(symG, item, cx, rowSymY, { theme, cls, textures: legendTextures }) {
2449
+ const RC = "round";
2450
+ const rw = SYMBOL_WIDTH;
2451
+ const rh = SYMBOL_HEIGHT;
2452
+ if (item.kind === "fracture") {
2453
+ const color = item.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
2454
+ if (item.swarm) {
2455
+ [-4, 0, 4].forEach((offset) => {
2456
+ symG.append("polyline").attr("class", cls.fracturePoly).attr(
2457
+ "points",
2458
+ `${cx},${rowSymY + offset} ${cx + 6},${rowSymY + offset - 1} ${cx + 12},${rowSymY + offset + 1} ${cx + 18},${rowSymY + offset - 0.5} ${cx + 24},${rowSymY + offset}`
2459
+ ).attr("stroke", color).attr(
2460
+ "stroke-width",
2461
+ offset !== 0 ? theme.legend.fractureSideStrokeWidth : theme.legend.fractureStrokeWidth
2462
+ ).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2463
+ });
2464
+ } else {
2465
+ symG.append("polyline").attr("class", cls.fracturePoly).attr(
2466
+ "points",
2467
+ `${cx},${rowSymY} ${cx + 5},${rowSymY - 1.5} ${cx + 11},${rowSymY + 1} ${cx + 17},${rowSymY - 0.5} ${cx + 24},${rowSymY}`
2468
+ ).attr("stroke", color).attr("stroke-width", theme.legend.fractureStrokeWidth).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
2469
+ }
2470
+ } else if (item.kind === "cave") {
2471
+ const color = item.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
2472
+ const texture = item.water_intake ? legendTextures.cave_wet : legendTextures.cave_dry;
2473
+ 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);
2474
+ } else if (item.subKind === "surfaceCase") {
2475
+ 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");
2476
+ 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);
2477
+ 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);
2478
+ } else if (item.subKind === "centralizer") {
2479
+ const pipeW = rw * 0.4;
2480
+ const pipeL = cx + (rw - pipeW) / 2;
2481
+ const pipeR = pipeL + pipeW;
2482
+ const y1 = rowSymY - rh / 2;
2483
+ const y2 = rowSymY + rh / 2;
2484
+ const bowH = rh * 0.3;
2485
+ for (const x of [pipeL, pipeR]) {
2486
+ 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);
2487
+ }
2488
+ for (const [from, to] of [
2489
+ [pipeL, cx],
2490
+ [pipeR, cx + rw]
2491
+ ]) {
2492
+ symG.append("polyline").attr("class", cls.constructionRect).attr(
2493
+ "points",
2494
+ `${from},${rowSymY - bowH} ${to},${rowSymY} ${from},${rowSymY + bowH}`
2495
+ ).attr("fill", theme.centralizer.fill).attr("stroke", theme.centralizer.stroke).attr("stroke-width", theme.centralizer.strokeWidth);
2496
+ }
2497
+ } else if (item.subKind === "pump") {
2498
+ const y1 = rowSymY - rh / 2;
2499
+ const y2 = rowSymY + rh / 2;
2500
+ const midX = cx + rw / 2;
2501
+ const riserHalf = rw * 0.12;
2502
+ const bodyHalf = rw * 0.28;
2503
+ const bodyTop = y1 + rh * 0.4;
2504
+ 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");
2505
+ 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);
2506
+ symG.append("path").attr("class", cls.constructionRect).attr(
2507
+ "d",
2508
+ `M${midX - bodyHalf},${bodyTop}H${midX + bodyHalf}V${y2}H${midX - bodyHalf}Z`
2509
+ ).attr("fill", theme.pump.fill).attr("stroke", theme.pump.stroke).attr("stroke-width", theme.pump.strokeWidth);
2510
+ } else if (item.subKind === "reduction") {
2511
+ const topW = rw * 0.5;
2512
+ const y1 = rowSymY - rh / 2;
2513
+ const y2 = rowSymY + rh / 2;
2514
+ const midX = cx + rw / 2;
2515
+ const tl = `${midX - topW / 2},${y1}`;
2516
+ const tr = `${midX + topW / 2},${y1}`;
2517
+ symG.append("polygon").attr("class", cls.constructionRect).attr("points", `${tl} ${tr} ${cx + rw},${y2} ${cx},${y2}`).attr("fill", theme.reduction.fill).attr("stroke", "none");
2518
+ 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);
2519
+ } else if (item.subKind === "wellCase" || item.subKind === "wellScreen" || item.subKind === "boreHole") {
2520
+ const y1 = rowSymY - rh / 2;
2521
+ const y2 = rowSymY + rh / 2;
2522
+ const shoe = 3;
2523
+ const xL = cx + shoe;
2524
+ const xR = cx + rw - shoe;
2525
+ const style = {
2526
+ wellCase: { fill: theme.wellCase.fill, stroke: theme.wellCase.stroke },
2527
+ wellScreen: {
2528
+ fill: legendTextures.well_screen.url(),
2529
+ stroke: theme.wellScreen.stroke
2530
+ },
2531
+ boreHole: { fill: theme.boreHole.fill, stroke: theme.boreHole.stroke }
2532
+ }[item.subKind];
2533
+ 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");
2534
+ const walls = symG.append("path").attr("class", cls.constructionRect).attr(
2535
+ "d",
2536
+ pipeWallsPath(xL, xR, y1, y2) + (item.subKind === "boreHole" ? `M${xL},${y2}H${xR}` : "")
2537
+ ).attr("fill", "none").attr("stroke", style.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
2538
+ if (item.subKind === "boreHole")
2539
+ walls.attr("stroke-dasharray", theme.boreHole.strokeDasharray);
2540
+ if (item.subKind === "wellCase")
2541
+ symG.append("path").attr("class", cls.constructionRect).attr("d", shoePath(xL, xR, y2, shoe)).attr("fill", style.stroke).attr("stroke", "none");
2542
+ if (item.subKind === "wellScreen")
2543
+ symG.append("path").attr("class", cls.constructionRect).attr(
2544
+ "d",
2545
+ screenEndPath(xL, xR, y1 + 0.5) + screenEndPath(xL, xR, y2 - 0.5)
2546
+ ).attr("fill", "none").attr("stroke", style.stroke).attr("stroke-width", theme.wellScreen.endStrokeWidth).attr("stroke-dasharray", "2, 1.5");
2547
+ } else if (item.subKind === "cementPad") {
2548
+ const h = rh * 0.6;
2549
+ const { slabs } = getCementPadGeometry(
2550
+ rw,
2551
+ 2 * cx + rw,
2552
+ rowSymY + h / 2,
2553
+ { widthRatio: 1, height: h },
2554
+ rw * 0.3
2555
+ );
2556
+ for (const d of slabs)
2557
+ 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);
2558
+ } else {
2559
+ const symMap = {
2560
+ holeFillGravel: {
2561
+ fill: legendTextures.gravel_pack.url(),
2562
+ stroke: theme.holeFill.stroke
2563
+ },
2564
+ holeFillSeal: {
2565
+ fill: legendTextures.seal.url(),
2566
+ stroke: theme.holeFill.stroke
2567
+ },
2568
+ conflict: {
2569
+ fill: legendTextures.conflict.url(),
2570
+ stroke: theme.conflict.stroke
2571
+ }
2572
+ };
2573
+ const sym = symMap[item.subKind];
2574
+ 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);
2575
+ if (sym.dasharray) symRect.attr("stroke-dasharray", sym.dasharray);
2576
+ }
2577
+ }
2578
+ function resolveStyle(options) {
2579
+ return {
2580
+ theme: defu(options.theme ?? {}, DEFAULT_WELL_THEME),
2581
+ cls: options.classNames ? { ...DEFAULT_COMPONENTS_CLASS_NAMES.legend, ...options.classNames } : DEFAULT_COMPONENTS_CLASS_NAMES.legend,
2582
+ textures: createWellTextures(options.textures)
2583
+ };
2584
+ }
2585
+ function drawWellLegendSymbol(selector, item, options = {}) {
2586
+ const style = resolveStyle(options);
2587
+ const svgEl = d37.select(selector);
2588
+ svgEl.selectAll("*").remove();
2589
+ svgEl.attr("width", SYMBOL_WIDTH + SYMBOL_MARGIN * 2).attr("height", SYMBOL_HEIGHT + SYMBOL_MARGIN * 2);
2590
+ Object.values(style.textures).forEach((t) => svgEl.call(t));
2591
+ const symG = svgEl.append("g").attr("class", style.cls.item);
2592
+ drawLegendSymbol(
2593
+ symG,
2594
+ item,
2595
+ SYMBOL_MARGIN,
2596
+ SYMBOL_MARGIN + SYMBOL_HEIGHT / 2,
2597
+ style
2598
+ );
2599
+ }
2600
+ function drawWellLegend(selector, profile, options = {}) {
2601
+ const cfg = defu(
2602
+ options.config ?? {},
2603
+ INTERACTIVE_RENDER_CONFIG.legend
2604
+ );
2605
+ const items = getWellLegendItems(profile, cfg.labels);
1736
2606
  const svgEl = d37.select(selector);
1737
2607
  svgEl.selectAll("*").remove();
1738
2608
  if (items.length === 0) {
1739
2609
  svgEl.attr("width", 0).attr("height", 0);
1740
2610
  return;
1741
2611
  }
1742
- const legendTextures = createWellTextures(options.textures);
1743
- Object.values(legendTextures).forEach((t) => svgEl.call(t));
2612
+ const style = resolveStyle(options);
2613
+ const { theme, cls } = style;
2614
+ Object.values(style.textures).forEach((t) => svgEl.call(t));
1744
2615
  const itemsPerRow = cfg.maxWidth ? Math.max(1, Math.floor((cfg.maxWidth - cfg.padding * 2) / cfg.itemWidth)) : items.length;
1745
2616
  const numRows = Math.ceil(items.length / itemsPerRow);
1746
2617
  const rowWidth = Math.min(items.length, itemsPerRow) * cfg.itemWidth;
1747
2618
  const totalWidth = cfg.padding * 2 + rowWidth;
1748
2619
  const totalHeight = numRows * cfg.height;
1749
2620
  svgEl.attr("width", totalWidth).attr("height", totalHeight);
1750
- const RC = "round";
1751
2621
  const g = svgEl.append("g").attr("transform", `translate(${cfg.padding}, 2)`);
1752
2622
  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);
1753
2623
  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}:`);
1754
- const rw = 24;
1755
- const rh = 12;
1756
2624
  items.forEach((item, i) => {
1757
2625
  const col = i % itemsPerRow;
1758
2626
  const row = Math.floor(i / itemsPerRow);
1759
2627
  const rowSymY = row * cfg.height + (cfg.height - 4) / 2;
1760
2628
  const cx = col * cfg.itemWidth;
1761
2629
  const symG = g.append("g").attr("class", cls.item);
1762
- if (item.kind === "fracture") {
1763
- const color = item.water_intake ? theme.fracture.wetStroke : theme.fracture.dryStroke;
1764
- if (item.swarm) {
1765
- [-4, 0, 4].forEach((offset) => {
1766
- symG.append("polyline").attr("class", cls.fracturePoly).attr(
1767
- "points",
1768
- `${cx},${rowSymY + offset} ${cx + 6},${rowSymY + offset - 1} ${cx + 12},${rowSymY + offset + 1} ${cx + 18},${rowSymY + offset - 0.5} ${cx + 24},${rowSymY + offset}`
1769
- ).attr("stroke", color).attr(
1770
- "stroke-width",
1771
- offset !== 0 ? theme.legend.fractureSideStrokeWidth : theme.legend.fractureStrokeWidth
1772
- ).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
1773
- });
1774
- } else {
1775
- symG.append("polyline").attr("class", cls.fracturePoly).attr(
1776
- "points",
1777
- `${cx},${rowSymY} ${cx + 5},${rowSymY - 1.5} ${cx + 11},${rowSymY + 1} ${cx + 17},${rowSymY - 0.5} ${cx + 24},${rowSymY}`
1778
- ).attr("stroke", color).attr("stroke-width", theme.legend.fractureStrokeWidth).attr("fill", "none").attr("stroke-linecap", RC).attr("stroke-linejoin", RC);
1779
- }
1780
- } else if (item.kind === "cave") {
1781
- const color = item.water_intake ? theme.cave.wetStroke : theme.cave.dryStroke;
1782
- const texture = item.water_intake ? legendTextures.cave_wet : legendTextures.cave_dry;
1783
- 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);
1784
- } else if (item.subKind === "surfaceCase") {
1785
- 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");
1786
- 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);
1787
- 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);
1788
- } else if (item.subKind === "centralizer") {
1789
- const pipeW = rw * 0.4;
1790
- const pipeL = cx + (rw - pipeW) / 2;
1791
- const pipeR = pipeL + pipeW;
1792
- const y1 = rowSymY - rh / 2;
1793
- const y2 = rowSymY + rh / 2;
1794
- const bowH = rh * 0.3;
1795
- for (const x of [pipeL, pipeR]) {
1796
- 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);
1797
- }
1798
- for (const [from, to] of [
1799
- [pipeL, cx],
1800
- [pipeR, cx + rw]
1801
- ]) {
1802
- symG.append("polyline").attr("class", cls.constructionRect).attr(
1803
- "points",
1804
- `${from},${rowSymY - bowH} ${to},${rowSymY} ${from},${rowSymY + bowH}`
1805
- ).attr("fill", theme.centralizer.fill).attr("stroke", theme.centralizer.stroke).attr("stroke-width", theme.centralizer.strokeWidth);
1806
- }
1807
- } else if (item.subKind === "reduction") {
1808
- const topW = rw * 0.5;
1809
- const y1 = rowSymY - rh / 2;
1810
- const y2 = rowSymY + rh / 2;
1811
- const midX = cx + rw / 2;
1812
- symG.append("polygon").attr("class", cls.constructionRect).attr(
1813
- "points",
1814
- `${midX - topW / 2},${y1} ${midX + topW / 2},${y1} ${cx + rw},${y2} ${cx},${y2}`
1815
- ).attr("fill", theme.reduction.fill).attr("stroke", theme.reduction.stroke).attr("stroke-width", theme.legend.itemStrokeWidth);
1816
- } else {
1817
- const symMap = {
1818
- boreHole: {
1819
- fill: theme.boreHole.fill,
1820
- stroke: theme.boreHole.stroke,
1821
- dasharray: theme.boreHole.strokeDasharray
1822
- },
1823
- holeFillGravel: {
1824
- fill: legendTextures.gravel_pack.url(),
1825
- stroke: theme.holeFill.stroke
1826
- },
1827
- holeFillSeal: {
1828
- fill: legendTextures.seal.url(),
1829
- stroke: theme.holeFill.stroke
1830
- },
1831
- wellCase: { fill: theme.wellCase.fill, stroke: theme.wellCase.stroke },
1832
- wellScreen: {
1833
- fill: legendTextures.well_screen.url(),
1834
- stroke: theme.wellScreen.stroke
1835
- },
1836
- cementPad: {
1837
- fill: legendTextures.pad.url(),
1838
- stroke: theme.cementPad.stroke
1839
- },
1840
- conflict: {
1841
- fill: legendTextures.conflict.url(),
1842
- stroke: theme.conflict.stroke
1843
- }
1844
- };
1845
- const sym = symMap[item.subKind];
1846
- 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);
1847
- if (sym.dasharray) symRect.attr("stroke-dasharray", sym.dasharray);
1848
- }
2630
+ drawLegendSymbol(symG, item, cx, rowSymY, style);
1849
2631
  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);
1850
2632
  });
1851
2633
  }
1852
2634
 
1853
2635
  // src/renderers/lithology.renderer.ts
2636
+ import * as d38 from "d3";
1854
2637
  function drawLithology(ctx, data) {
1855
- if (!data.length) return;
1856
2638
  const {
1857
2639
  groups: { lithologyGroup },
1858
2640
  svg,
@@ -1876,10 +2658,219 @@ function drawLithology(ctx, data) {
1876
2658
  const merged = mergeEnter(newLayers, rects);
1877
2659
  withTransition(merged, transition).attr("y", getYPos).attr("height", getHeight).attr("fill", getLithologyFill(data, svg, ctx.theme.lithologyTexture));
1878
2660
  }
2661
+ function drawLithologySwatch(selector, layer, options) {
2662
+ const { size, theme } = options;
2663
+ const svg = d38.select(selector);
2664
+ svg.selectAll("*").remove();
2665
+ svg.attr("width", size).attr("height", size);
2666
+ const fill = getLithologyFill([layer], svg, {
2667
+ ...theme.lithologyTexture,
2668
+ // Denser pattern so a few strokes show inside the small square.
2669
+ size: Math.max(12, size * 3)
2670
+ });
2671
+ 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);
2672
+ }
2673
+
2674
+ // src/renderers/markers.renderer.ts
2675
+ function drawMarkers(ctx, data) {
2676
+ const group = ctx.groups.markersGroup;
2677
+ group.selectAll("*").remove();
2678
+ const cfg = ctx.renderConfig.markers;
2679
+ const theme = ctx.theme.markers;
2680
+ const r = cfg.radius;
2681
+ if (cfg.lithology) {
2682
+ const cx = (ctx.geoXLeft + ctx.geoXRight) / 2;
2683
+ getLithologyMarkers(data.lithology, ctx.depthFrom, ctx.depthTo).forEach(
2684
+ (marker) => {
2685
+ const top = ctx.yScale(marker.from);
2686
+ const bottom = ctx.yScale(marker.to);
2687
+ if (bottom - top < r) return;
2688
+ const cy = bottom - top >= 2 * r + 4 ? top + r + 2 : (top + bottom) / 2;
2689
+ const g = group.append("g").attr("class", ctx.classes.markers.lithology);
2690
+ 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);
2691
+ 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));
2692
+ }
2693
+ );
2694
+ }
2695
+ if (cfg.fractures) {
2696
+ const x = ctx.pocoCenterX + ctx.POCO_WIDTH * ctx.renderConfig.fractures.widthMultiplier / 2 + 3;
2697
+ getFractureMarkers(data.fractures, ctx.depthFrom, ctx.depthTo).forEach(
2698
+ (marker) => {
2699
+ 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);
2700
+ }
2701
+ );
2702
+ }
2703
+ }
2704
+
2705
+ // src/renderers/overlap.renderer.ts
2706
+ import textures3 from "textures";
2707
+ import { formatNumber as formatNumber3 } from "@welldot/utils";
2708
+ function drawOverlap(ctx) {
2709
+ const group = ctx.groups.overlapGroup;
2710
+ group.selectAll("*").remove();
2711
+ const { repeatedTo, depthFrom } = ctx;
2712
+ if (repeatedTo == null || repeatedTo <= depthFrom) return;
2713
+ const theme = ctx.theme.overlap;
2714
+ const { margins } = ctx.state;
2715
+ const overview = ctx.renderConfig.overview;
2716
+ const showsDescription = hasDescriptionColumn(ctx);
2717
+ const x0 = -margins.left + (overview.active ? overview.x + overview.width + 4 : 0);
2718
+ const x1 = showsDescription ? ctx.geoXRight + ctx.renderConfig.labels.descriptionXOffset - 4 : ctx.svgWidth - margins.left;
2719
+ const y0 = ctx.yScale(depthFrom);
2720
+ const y1 = ctx.yScale(repeatedTo);
2721
+ const hatch = textures3.lines().size(theme.size).strokeWidth(theme.strokeWidth).stroke(theme.stroke);
2722
+ ctx.svg.call(hatch);
2723
+ 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");
2724
+ 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");
2725
+ const whole = (m) => formatNumber3(ctx.units.length === "ft" ? m * 3.28084 : m, {
2726
+ maximumFractionDigits: 0,
2727
+ locale: ctx.locale
2728
+ });
2729
+ const unit = getLengthUnit(ctx.units.length);
2730
+ const range = `${whole(depthFrom)}\u2013${whole(repeatedTo)} ${unit}`;
2731
+ 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));
2732
+ }
2733
+
2734
+ // src/renderers/overview.renderer.ts
2735
+ function drawOverview(ctx, lithology) {
2736
+ const group = ctx.groups.overviewGroup;
2737
+ group.selectAll("*").remove();
2738
+ const cfg = ctx.renderConfig.overview;
2739
+ if (!cfg.active || lithology.length === 0 || ctx.totalDepth <= 0) return;
2740
+ const theme = ctx.theme.overview;
2741
+ const height = Math.min(cfg.height, ctx.state.height);
2742
+ const { y, window } = getOverviewGeometry(
2743
+ ctx.totalDepth,
2744
+ height,
2745
+ ctx.depthFrom,
2746
+ ctx.depthTo
2747
+ );
2748
+ lithology.forEach((layer) => {
2749
+ 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);
2750
+ });
2751
+ 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);
2752
+ 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);
2753
+ }
2754
+
2755
+ // src/renderers/pump.renderer.ts
2756
+ import * as d39 from "d3";
2757
+ import { getProfileDiamValues as getProfileDiamValues5 } from "@welldot/utils";
2758
+ function getPumpSpans(panelTop, panelBottom, bodyTop, intakeY) {
2759
+ const riserBottom = Math.min(Math.max(panelTop, bodyTop), panelBottom);
2760
+ const top = Math.max(bodyTop, panelTop);
2761
+ const bottom = Math.min(intakeY, panelBottom);
2762
+ return {
2763
+ riserBottom,
2764
+ body: bottom > top ? { top, bottom, cutTop: top > bodyTop, cutBottom: bottom < intakeY } : null
2765
+ };
2766
+ }
2767
+ var riserBreakAmplitude = (width) => Math.min(2, width / 3);
2768
+ function getRiserTop(xLeft, xRight, y, broken) {
2769
+ if (!broken) return { edge: `H${xRight}`, breakLine: "" };
2770
+ const a = riserBreakAmplitude(xRight - xLeft);
2771
+ const w = xRight - xLeft;
2772
+ const zigzag = `L${xLeft + w / 3},${y - a}L${xLeft + 2 * w / 3},${y + a}L${xRight},${y}`;
2773
+ return { edge: zigzag, breakLine: `M${xLeft},${y}${zigzag}` };
2774
+ }
2775
+ var BODY_MAX_FILL = 0.85;
2776
+ var RISER_MAX_FILL = 0.7;
2777
+ function getPumpDiameters(intakeD, narrowestD, riserDiameter, cfg) {
2778
+ const maxBody = intakeD * BODY_MAX_FILL;
2779
+ const riser = Math.min(
2780
+ riserDiameter ?? intakeD * cfg.riserWidthRatio,
2781
+ narrowestD * RISER_MAX_FILL,
2782
+ maxBody * RISER_MAX_FILL
2783
+ );
2784
+ const body = Math.min(
2785
+ Math.max(intakeD * cfg.bodyWidthRatio, riser * 1.4),
2786
+ maxBody
2787
+ );
2788
+ return { riser, body };
2789
+ }
2790
+ function drawPump(ctx, pump) {
2791
+ const group = ctx.groups.pumpGroup;
2792
+ const cls = ctx.classes.pump;
2793
+ const cfg = ctx.renderConfig.construction.pump;
2794
+ const theme = ctx.theme.pump;
2795
+ const intake = pump?.intake_depth;
2796
+ const visible = !!pump && cfg.active && intake !== void 0 && intake > 0 && // the riser crosses this panel when the panel starts above the intake
2797
+ ctx.depthFrom < intake;
2798
+ const data = visible ? [pump] : [];
2799
+ const selection = group.selectAll(`g.${cls.item}`).data(data, (d) => d.id);
2800
+ selection.exit().remove();
2801
+ if (!visible) return;
2802
+ const maxXValue = d39.max(getProfileDiamValues5(ctx.constructionData)) || 0;
2803
+ const xScale = d39.scaleLinear().domain([0, maxXValue]).range([0, ctx.POCO_WIDTH]);
2804
+ const pipes = [
2805
+ ...ctx.constructionData.well_case,
2806
+ ...ctx.constructionData.well_screen
2807
+ ];
2808
+ const casingD = diameterAt(pipes, intake) ?? diameterAt(ctx.constructionData.bore_hole, intake) ?? maxXValue;
2809
+ const narrowestD = Math.min(
2810
+ casingD,
2811
+ ...[...pipes, ...ctx.constructionData.bore_hole].filter((i) => i.from < intake && i.to > 0).map((i) => i.diameter)
2812
+ );
2813
+ const { riser: riserD, body: bodyD } = getPumpDiameters(
2814
+ casingD,
2815
+ narrowestD,
2816
+ pump.riser_diameter,
2817
+ cfg
2818
+ );
2819
+ const riserLeft = (ctx.POCO_CENTER - xScale(riserD)) / 2;
2820
+ const riserRight = (ctx.POCO_CENTER + xScale(riserD)) / 2;
2821
+ const bodyLeft = (ctx.POCO_CENTER - xScale(bodyD)) / 2;
2822
+ const bodyRight = (ctx.POCO_CENTER + xScale(bodyD)) / 2;
2823
+ const yIntake = ctx.yScale(intake);
2824
+ const yBodyTop = yIntake - cfg.bodyHeight;
2825
+ const headDepth = Math.min(
2826
+ 0,
2827
+ ...pipes.filter((p) => p.from < 0).map((p) => p.from)
2828
+ );
2829
+ const headInPanel = ctx.depthFrom <= headDepth;
2830
+ const padOnHead = headDepth === 0 && !!ctx.constructionData.cement_pad?.thickness;
2831
+ const yRiserTop = headInPanel ? ctx.yScale(headDepth) - (padOnHead ? ctx.renderConfig.construction.cementPad.height - riserBreakAmplitude(xScale(riserD)) : 0) : ctx.yScale(ctx.depthFrom);
2832
+ const { riserBottom: yRiserBottom, body } = getPumpSpans(
2833
+ yRiserTop,
2834
+ ctx.yScale(ctx.depthTo),
2835
+ yBodyTop,
2836
+ yIntake
2837
+ );
2838
+ const r = Math.min(3, (bodyRight - bodyLeft) / 4);
2839
+ const entered = selection.enter().append("g").attr("class", cls.item).on("mouseover", ctx.tooltips.pump.show).on("mouseout", ctx.tooltips.pump.hide);
2840
+ entered.append("path").attr("class", `${cls.item}-riser-fill`);
2841
+ entered.append("path").attr("class", `${cls.item}-riser`);
2842
+ entered.append("path").attr("class", `${cls.item}-body`);
2843
+ entered.append("path").attr("class", `${cls.item}-intake`);
2844
+ const merged = entered.merge(selection);
2845
+ const top = getRiserTop(riserLeft, riserRight, yRiserTop, headInPanel);
2846
+ merged.select(`.${cls.item}-riser-fill`).attr(
2847
+ "d",
2848
+ yRiserBottom > yRiserTop ? `M${riserLeft},${yRiserTop}${top.edge}V${yRiserBottom}H${riserLeft}Z` : ""
2849
+ ).attr("fill", theme.riserFill).attr("stroke", "none");
2850
+ merged.select(`.${cls.item}-riser`).attr(
2851
+ "d",
2852
+ yRiserBottom > yRiserTop ? `M${riserLeft},${yRiserTop}V${yRiserBottom}M${riserRight},${yRiserTop}V${yRiserBottom}${top.breakLine}` : ""
2853
+ ).attr("stroke-linejoin", "round").attr("fill", "none").attr("stroke", theme.riserStroke).attr("stroke-width", theme.riserStrokeWidth);
2854
+ let bodyPath = "";
2855
+ if (body) {
2856
+ const rt = body.cutTop ? 0 : Math.min(r, (body.bottom - body.top) / 2);
2857
+ const rb = body.cutBottom ? 0 : Math.min(r, (body.bottom - body.top) / 2);
2858
+ const { top: top2, bottom } = body;
2859
+ 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`;
2860
+ }
2861
+ merged.select(`.${cls.item}-body`).attr("d", bodyPath).attr("fill", theme.fill).attr("stroke", theme.stroke).attr("stroke-width", theme.strokeWidth);
2862
+ const hatchY = [
2863
+ yIntake - cfg.bodyHeight * 0.3,
2864
+ yIntake - cfg.bodyHeight * 0.15
2865
+ ].filter((y) => body && y > body.top && y < body.bottom);
2866
+ merged.select(`.${cls.item}-intake`).attr(
2867
+ "d",
2868
+ hatchY.map((y) => `M${bodyLeft + 1.5},${y}H${bodyRight - 1.5}`).join("")
2869
+ ).attr("fill", "none").attr("stroke", theme.stroke).attr("stroke-width", 0.8);
2870
+ }
1879
2871
 
1880
2872
  // src/renderers/unit-labels.renderer.ts
1881
2873
  function drawUnitLabels(ctx, data) {
1882
- if (!ctx.renderConfig.unitLabels.active || !data.length) return;
1883
2874
  const {
1884
2875
  groups: { unitLabelsGroup },
1885
2876
  depthFrom,
@@ -1890,6 +2881,7 @@ function drawUnitLabels(ctx, data) {
1890
2881
  classes
1891
2882
  } = ctx;
1892
2883
  unitLabelsGroup.selectAll("*").remove();
2884
+ if (!renderConfig.unitLabels.active || !data.length) return;
1893
2885
  const ru = renderConfig.unitLabels;
1894
2886
  const rut = theme.unitLabels;
1895
2887
  const sw = ru.stripWidth;
@@ -1970,7 +2962,7 @@ function buildSvgStyleBlock(theme) {
1970
2962
  }
1971
2963
 
1972
2964
  // src/Renderer.ts
1973
- var d38 = { ...d3module2, tip: d3tip2 };
2965
+ var d310 = { ...d3module2, tip: d3tip2 };
1974
2966
  var WellRenderer = class {
1975
2967
  /**
1976
2968
  * @param svgs - One entry per SVG panel; multi-entry for split-panel layouts.
@@ -2030,12 +3022,14 @@ var WellRenderer = class {
2030
3022
  const { selector, height, width, margins } = inst;
2031
3023
  const svgWidth = width + margins.left + margins.right;
2032
3024
  const svgHeight = height + margins.top + margins.bottom;
2033
- const svg = d38.select(selector);
3025
+ const svg = d310.select(selector);
2034
3026
  svg.selectAll("*").remove();
2035
3027
  svg.attr("height", svgHeight).attr("width", svgWidth);
2036
3028
  const defs = svg.append("defs");
2037
3029
  defs.append("style").text(buildSvgStyleBlock(this.theme));
2038
3030
  defs.append("clipPath").attr("id", clipId).append("rect").attr("id", clipRectId).attr("x", 0).attr("y", 0).attr("width", 1e5).attr("height", 1e4);
3031
+ svg.append("g").attr("class", this.classes.overview.group).attr("transform", `translate(0, ${margins.top})`);
3032
+ svg.append("g").attr("class", this.classes.columnHeaders.group);
2039
3033
  const poco = svg.append("g").attr("class", this.classes.wellGroup);
2040
3034
  const geologic = poco.append("g").attr("class", this.classes.geologicGroup).attr("transform", `translate(${margins.left}, ${margins.top})`);
2041
3035
  geologic.append("g").attr("class", this.classes.yAxis);
@@ -2050,6 +3044,7 @@ var WellRenderer = class {
2050
3044
  );
2051
3045
  const fracturesGroup = poco.append("g").attr("class", this.classes.fractures.group).attr("transform", `translate(${margins.left}, ${margins.top})`).attr("clip-path", `url(#${clipId})`);
2052
3046
  fracturesGroup.append("g").attr("class", this.classes.highlights.fracturesGroup);
3047
+ poco.append("g").attr("class", this.classes.markers.group).attr("transform", `translate(${margins.left}, ${margins.top})`);
2053
3048
  construction.append("g").attr("class", this.classes.cementPad.group);
2054
3049
  construction.append("g").attr("class", this.classes.boreHole.group);
2055
3050
  construction.append("g").attr("class", this.classes.surfaceCase.group);
@@ -2057,12 +3052,23 @@ var WellRenderer = class {
2057
3052
  construction.append("g").attr("class", this.classes.wellCase.group);
2058
3053
  construction.append("g").attr("class", this.classes.wellScreen.group);
2059
3054
  construction.append("g").attr("class", this.classes.reduction.group);
3055
+ construction.append("g").attr("class", this.classes.pump.group);
2060
3056
  construction.append("g").attr("class", this.classes.centralizer.group);
2061
3057
  construction.append("g").attr("class", this.classes.conflict.group);
2062
3058
  construction.append("g").attr("class", this.classes.constructionLabels.group);
2063
3059
  construction.append("g").attr("class", this.classes.highlights.constructionGroup);
3060
+ poco.append("g").attr("class", this.classes.overlap.group).attr("transform", `translate(${margins.left}, ${margins.top})`);
2064
3061
  Object.values(this.textures).forEach((texture) => svg.call(texture));
2065
- return { svg, height, width, margins, clipId, clipRectId };
3062
+ return {
3063
+ svg,
3064
+ height,
3065
+ width,
3066
+ margins,
3067
+ clipId,
3068
+ clipRectId,
3069
+ depthRange: inst.depthRange,
3070
+ repeatedTo: inst.repeatedTo
3071
+ };
2066
3072
  }
2067
3073
  /** Renders a standalone legend into the element matched by `selector`. */
2068
3074
  renderLegend(selector, profile) {
@@ -2073,6 +3079,30 @@ var WellRenderer = class {
2073
3079
  textures: this.renderConfig.textures
2074
3080
  });
2075
3081
  }
3082
+ /** Lists the legend entries `profile` needs, labeled from `renderConfig.legend.labels`. */
3083
+ getLegendItems(profile) {
3084
+ return getWellLegendItems(profile, this.renderConfig.legend.labels);
3085
+ }
3086
+ /**
3087
+ * Renders one legend entry's symbol (see {@link getLegendItems}) into its
3088
+ * own SVG matched by `selector`, with the textures it needs defined inside.
3089
+ */
3090
+ renderLegendSymbol(selector, item) {
3091
+ drawWellLegendSymbol(selector, item, {
3092
+ theme: this.theme,
3093
+ classNames: this.classes.legend,
3094
+ textures: this.renderConfig.textures
3095
+ });
3096
+ }
3097
+ /**
3098
+ * Renders one lithology layer's fill as a `size`×`size` square into its
3099
+ * own SVG matched by `selector`, with its texture defined inside (e.g. for
3100
+ * a swatch in a PDF table). Call after `prepareSvg()`, which loads the
3101
+ * FGDC textures.
3102
+ */
3103
+ renderLithologySwatch(selector, layer, size = 10) {
3104
+ drawLithologySwatch(selector, layer, { size, theme: this.theme });
3105
+ }
2076
3106
  /**
2077
3107
  * Multiplies the current zoom scale by `factor` (e.g. `1.25` to zoom in,
2078
3108
  * `1 / 1.25` to zoom out) on every panel. No-op if both `renderConfig.zoom`
@@ -2089,7 +3119,7 @@ var WellRenderer = class {
2089
3119
  * (defaults to `1`, i.e. the initial fit-to-container view).
2090
3120
  */
2091
3121
  resetZoom() {
2092
- const target = d38.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
3122
+ const target = d310.zoomIdentity.scale(this.renderConfig.zoomLevel ?? 1);
2093
3123
  for (const state of this.instanceStates) {
2094
3124
  if (!state.zoomNode) continue;
2095
3125
  asSvgElement(state.svg).transition().call(state.zoomNode.transform, target);
@@ -2101,7 +3131,46 @@ var WellRenderer = class {
2101
3131
  */
2102
3132
  getZoomScale() {
2103
3133
  const node = this.instanceStates[0]?.svg.node();
2104
- return node ? d38.zoomTransform(node).k : 1;
3134
+ return node ? d310.zoomTransform(node).k : 1;
3135
+ }
3136
+ /**
3137
+ * Removes everything drawn on every panel (layers, labels, axis,
3138
+ * highlights) and keeps the structure `prepareSvg()` built, so the next
3139
+ * `draw()` needs no new `prepareSvg()`. `draw()` calls it for an empty well.
3140
+ */
3141
+ clear() {
3142
+ const c = this.classes;
3143
+ const layerGroups = [
3144
+ c.overview.group,
3145
+ c.columnHeaders.group,
3146
+ c.yAxis,
3147
+ c.unitLabels.group,
3148
+ c.lithology.group,
3149
+ c.caves.group,
3150
+ c.labels.lithology.group,
3151
+ c.highlights.geologicGroup,
3152
+ c.highlights.constructionGroup,
3153
+ c.highlights.fracturesGroup,
3154
+ c.markers.group,
3155
+ c.cementPad.group,
3156
+ c.boreHole.group,
3157
+ c.surfaceCase.group,
3158
+ c.holeFill.group,
3159
+ c.wellCase.group,
3160
+ c.wellScreen.group,
3161
+ c.reduction.group,
3162
+ c.pump.group,
3163
+ c.centralizer.group,
3164
+ c.conflict.group,
3165
+ c.constructionLabels.group,
3166
+ c.overlap.group
3167
+ ];
3168
+ for (const { svg } of this.instanceStates) {
3169
+ for (const cls of layerGroups) {
3170
+ svg.select(`.${cls}`).selectAll("*").interrupt().remove();
3171
+ }
3172
+ svg.select(`.${c.fractures.group}`).selectAll(`g.${c.fractures.item}`).interrupt().remove();
3173
+ }
2105
3174
  }
2106
3175
  /**
2107
3176
  * Initialises the SVG DOM structure and preloads FGDC textures.
@@ -2114,14 +3183,19 @@ var WellRenderer = class {
2114
3183
  );
2115
3184
  }
2116
3185
  /**
2117
- * Renders the well profile into all configured SVG panels.
3186
+ * Renders the well profile into all configured SVG panels. Layers the
3187
+ * profile no longer has are removed; an empty well (`isWellEmpty`) clears
3188
+ * the panels (see {@link clear}).
2118
3189
  * @param profile - The well data to render.
2119
3190
  * @param options.units - Override the instance-level measurement units for this render.
2120
3191
  * @param options.locale - Override the instance-level locale for this render.
2121
3192
  * @param options.highlights - Highlight overlays to display on top of the profile.
2122
3193
  */
2123
3194
  draw(profile, options = {}) {
2124
- if (isWellEmpty(profile)) return;
3195
+ if (isWellEmpty(profile)) {
3196
+ this.clear();
3197
+ return;
3198
+ }
2125
3199
  if (profile.version !== 2) {
2126
3200
  console.warn(
2127
3201
  `[WellRenderer] Received a .well file with version ${profile.version ?? "unknown"}. Only v2 is supported. Normalize with deserializeWell() from @welldot/core before rendering.`
@@ -2133,35 +3207,18 @@ var WellRenderer = class {
2133
3207
  }
2134
3208
  const highlights = options.highlights ?? {};
2135
3209
  const maxValues = getProfileLastItemsDepths(profile);
2136
- const maxYValues = d38.max(maxValues) || 0;
2137
- if (this.instanceStates.length === 1) {
2138
- this.drawLogToInstance(
2139
- this.instanceStates[0],
2140
- profile,
2141
- 0,
2142
- maxYValues,
2143
- highlights
2144
- );
2145
- return;
2146
- }
2147
- const totalHeight = this.instanceStates.reduce((s, st) => s + st.height, 0) || 1;
2148
- const yScaleGlobal = d38.scaleLinear().domain([0, maxYValues]).range([0, totalHeight]);
2149
- let currentDepth = 0;
2150
- for (const state of this.instanceStates) {
2151
- const maxSvgDepth = yScaleGlobal.invert(
2152
- yScaleGlobal(currentDepth) + state.height
2153
- );
2154
- this.drawLogToInstance(
2155
- state,
2156
- profile,
2157
- currentDepth,
2158
- maxSvgDepth,
2159
- highlights
2160
- );
2161
- currentDepth = maxSvgDepth;
2162
- }
3210
+ const maxYValues = d310.max(maxValues) || 0;
3211
+ const windows = resolvePanelWindows(
3212
+ this.instanceStates,
3213
+ maxYValues,
3214
+ getProfileTopDepth(profile)
3215
+ );
3216
+ this.instanceStates.forEach((state, i) => {
3217
+ const [from, to] = windows[i];
3218
+ this.drawLogToInstance(state, profile, from, to, maxYValues, highlights);
3219
+ });
2163
3220
  }
2164
- drawLogToInstance(state, profile, depthFrom, depthTo, highlights = {}) {
3221
+ drawLogToInstance(state, profile, depthFrom, depthTo, totalDepth, highlights = {}) {
2165
3222
  const svg = state.svg;
2166
3223
  const pocoGroup = svg.select(`.${this.classes.wellGroup}`);
2167
3224
  const lithologyGroup = svg.select(`.${this.classes.lithology.group}`);
@@ -2179,6 +3236,7 @@ var WellRenderer = class {
2179
3236
  const wellScreenGroup = svg.select(`.${this.classes.wellScreen.group}`);
2180
3237
  const reductionGroup = svg.select(`.${this.classes.reduction.group}`);
2181
3238
  const centralizerGroup = svg.select(`.${this.classes.centralizer.group}`);
3239
+ const pumpGroup = svg.select(`.${this.classes.pump.group}`);
2182
3240
  const conflictGroup = svg.select(`.${this.classes.conflict.group}`);
2183
3241
  const constructionLabelsGroup = svg.select(
2184
3242
  `.${this.classes.constructionLabels.group}`
@@ -2193,13 +3251,19 @@ var WellRenderer = class {
2193
3251
  `.${this.classes.highlights.fracturesGroup}`
2194
3252
  );
2195
3253
  const unitLabelsGroup = svg.select(`.${this.classes.unitLabels.group}`);
3254
+ const overviewGroup = svg.select(`.${this.classes.overview.group}`);
3255
+ const columnHeadersGroup = svg.select(
3256
+ `.${this.classes.columnHeaders.group}`
3257
+ );
3258
+ const markersGroup = svg.select(`.${this.classes.markers.group}`);
3259
+ const overlapGroup = svg.select(`.${this.classes.overlap.group}`);
2196
3260
  const svgWidth = state.width + state.margins.left + state.margins.right;
2197
3261
  const svgHeight = state.height + state.margins.top + state.margins.bottom;
2198
3262
  const { pocoWidthRatio, pocoCenterRatio } = this.renderConfig.layout;
2199
3263
  const POCO_WIDTH = svgWidth * pocoWidthRatio;
2200
3264
  const POCO_CENTER = svgWidth * pocoCenterRatio;
2201
3265
  const { duration, ease } = this.renderConfig.animation;
2202
- const transition = d38.transition().duration(duration).ease(ease);
3266
+ const transition = d310.transition().duration(duration).ease(ease);
2203
3267
  const tooltips = populateTooltips(
2204
3268
  svg,
2205
3269
  this.classes,
@@ -2228,11 +3292,11 @@ var WellRenderer = class {
2228
3292
  fractures: profile.fractures
2229
3293
  };
2230
3294
  const contentHeight = svgHeight - state.margins.top - state.margins.bottom;
2231
- const yScaleLocal = d38.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
3295
+ const yScaleLocal = d310.scaleLinear().domain([depthFrom, depthTo]).range([0, contentHeight]);
2232
3296
  const depthFactor = this.units.length === "ft" ? 3.28084 : 1;
2233
- const yScaleAxis = d38.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
2234
- const yAxis = d38.axisLeft(yScaleAxis).tickFormat(
2235
- (d) => `${formatNumber3(Number(d), {
3297
+ const yScaleAxis = d310.scaleLinear().domain([depthFrom * depthFactor, depthTo * depthFactor]).range([0, contentHeight]);
3298
+ const yAxis = d310.axisLeft(yScaleAxis).tickFormat(
3299
+ (d) => `${formatNumber4(Number(d), {
2236
3300
  maximumFractionDigits: this.units.length === "ft" ? 1 : 2,
2237
3301
  locale: this.locale
2238
3302
  })}${getLengthUnit(this.units.length)}`
@@ -2274,10 +3338,15 @@ var WellRenderer = class {
2274
3338
  wellScreenGroup,
2275
3339
  reductionGroup,
2276
3340
  centralizerGroup,
3341
+ pumpGroup,
2277
3342
  conflictGroup,
2278
3343
  highlightsGeologicGroup,
2279
3344
  highlightsConstructionGroup,
2280
- highlightsFracturesGroup
3345
+ highlightsFracturesGroup,
3346
+ overviewGroup,
3347
+ columnHeadersGroup,
3348
+ markersGroup,
3349
+ overlapGroup
2281
3350
  };
2282
3351
  const ctx = {
2283
3352
  svg,
@@ -2291,6 +3360,8 @@ var WellRenderer = class {
2291
3360
  geoXRight,
2292
3361
  depthFrom,
2293
3362
  depthTo,
3363
+ totalDepth,
3364
+ repeatedTo: state.repeatedTo,
2294
3365
  yScale: yScaleLocal,
2295
3366
  transition,
2296
3367
  tooltips,
@@ -2304,6 +3375,7 @@ var WellRenderer = class {
2304
3375
  groups
2305
3376
  };
2306
3377
  const inDepth = filterByDepth(depthFrom, depthTo);
3378
+ const currentPump = getCurrentPump2(profile);
2307
3379
  const filteredConstruction = {
2308
3380
  ...constructionData,
2309
3381
  bore_hole: constructionData.bore_hole.filter(inDepth),
@@ -2328,33 +3400,14 @@ var WellRenderer = class {
2328
3400
  if (!d) return null;
2329
3401
  return transform.k * spanH(d);
2330
3402
  });
2331
- surfaceCaseGroup.selectAll(`g.${this.classes.surfaceCase.rect}`).each((d, i, nodes) => {
2332
- if (!d) return;
2333
- const sc = d;
2334
- const g = d38.select(nodes[i]);
2335
- const y = transform.applyY(spanY(sc));
2336
- const h = transform.k * spanH(sc);
2337
- g.select(".surface-case-fill").attr("y", y).attr("height", h);
2338
- g.selectAll(".surface-case-side").attr("y1", y).attr("y2", y + h);
2339
- });
2340
- const maxXValsReduction = getProfileDiamValues5(constructionData);
2341
- const xScaleReduction = d38.scaleLinear().domain([0, d38.max(maxXValsReduction) || 0]).range([0, POCO_WIDTH]);
2342
- reductionGroup.selectAll(`polygon.${this.classes.reduction.item}`).attr("points", (d) => {
2343
- const r = d;
2344
- const yTop = transform.applyY(spanY(r));
2345
- const yBot = yTop + transform.k * spanH(r);
2346
- const xTopLeft = (POCO_CENTER - xScaleReduction(r.diam_from)) / 2;
2347
- const xTopRight = (POCO_CENTER + xScaleReduction(r.diam_from)) / 2;
2348
- const xBotLeft = (POCO_CENTER - xScaleReduction(r.diam_to)) / 2;
2349
- const xBotRight = (POCO_CENTER + xScaleReduction(r.diam_to)) / 2;
2350
- return `${xTopLeft},${yTop} ${xTopRight},${yTop} ${xBotRight},${yBot} ${xBotLeft},${yBot}`;
2351
- });
3403
+ layoutConstruction(zoomedCtx);
3404
+ drawCementPad(zoomedCtx, constructionData.cement_pad);
2352
3405
  fracturesGroup.selectAll(`g.${this.classes.fractures.item}`).attr("transform", (d) => {
2353
3406
  if (!d) return null;
2354
3407
  const f = d;
2355
3408
  const cx = pocoCenterX;
2356
3409
  const cy = transform.applyY(yScaleLocal(f.depth));
2357
- return `translate(0,${cy}) rotate(${f.dip},${cx},0)`;
3410
+ return `translate(0,${cy}) rotate(${fractureDip(f.dip)},${cx},0)`;
2358
3411
  });
2359
3412
  drawCaves(zoomedCtx, geologicData.caves?.filter(inDepth) ?? []);
2360
3413
  drawUnitLabels(zoomedCtx, profile.lithology?.filter(inDepth) ?? []);
@@ -2365,11 +3418,17 @@ var WellRenderer = class {
2365
3418
  ) ?? [],
2366
3419
  caves: profile.caves?.filter(inDepth) ?? []
2367
3420
  });
3421
+ drawPump(zoomedCtx, currentPump);
2368
3422
  drawCentralizers(zoomedCtx, filteredConstruction);
2369
3423
  drawConstructionLabels(zoomedCtx, {
2370
3424
  well_case: constructionData.well_case.filter(inDepth),
2371
3425
  well_screen: constructionData.well_screen.filter(inDepth)
2372
3426
  });
3427
+ drawMarkers(zoomedCtx, {
3428
+ lithology: profile.lithology ?? [],
3429
+ fractures: profile.fractures ?? []
3430
+ });
3431
+ drawOverlap(zoomedCtx);
2373
3432
  drawHighlights(zoomedCtx, highlights);
2374
3433
  this.onZoom?.(transform.k);
2375
3434
  };
@@ -2392,11 +3451,19 @@ var WellRenderer = class {
2392
3451
  ) ?? []
2393
3452
  );
2394
3453
  drawConstructive(ctx, filteredConstruction);
3454
+ drawPump(ctx, currentPump);
2395
3455
  drawCentralizers(ctx, filteredConstruction);
2396
3456
  drawConstructionLabels(ctx, {
2397
3457
  well_case: filteredConstruction.well_case,
2398
3458
  well_screen: filteredConstruction.well_screen
2399
3459
  });
3460
+ drawMarkers(ctx, {
3461
+ lithology: profile.lithology ?? [],
3462
+ fractures: profile.fractures ?? []
3463
+ });
3464
+ drawOverview(ctx, profile.lithology ?? []);
3465
+ drawColumnHeaders(ctx);
3466
+ drawOverlap(ctx);
2400
3467
  svg.select(`#${state.clipRectId}`).attr("y", 0).attr("height", svgHeight);
2401
3468
  drawHighlights(ctx, highlights);
2402
3469
  };
@@ -2412,7 +3479,7 @@ var WellRenderer = class {
2412
3479
  }
2413
3480
  const { zoom: zoomEnabled, pan: panEnabled } = this.renderConfig;
2414
3481
  if (zoomEnabled || panEnabled) {
2415
- const zoomNode = d38.zoom();
3482
+ const zoomNode = d310.zoom();
2416
3483
  if (zoomEnabled || panEnabled) zoomNode.on("zoom", zooming);
2417
3484
  zoomNode.scaleExtent(
2418
3485
  zoomEnabled ? [
@@ -2428,7 +3495,7 @@ var WellRenderer = class {
2428
3495
  if (initialZoom !== 1) {
2429
3496
  asSvgElement(svg).call(
2430
3497
  zoomNode.transform,
2431
- d38.zoomIdentity.scale(initialZoom)
3498
+ d310.zoomIdentity.scale(initialZoom)
2432
3499
  );
2433
3500
  }
2434
3501
  }
@@ -2442,10 +3509,13 @@ export {
2442
3509
  WellRenderer,
2443
3510
  applyRenderLocale,
2444
3511
  drawWellLegend,
3512
+ drawWellLegendSymbol,
2445
3513
  formatDiameter,
2446
3514
  formatLength,
2447
3515
  getDiameterUnit,
2448
3516
  getLengthUnit,
3517
+ getProfileTopDepth,
3518
+ getWellLegendItems,
2449
3519
  importFgdcTextures,
2450
3520
  resolveRenderLabel
2451
3521
  };