@welldot/render 0.3.0 → 0.4.1

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