well-log-toolkit 0.1.131__py3-none-any.whl → 0.1.132__py3-none-any.whl
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.
- well_log_toolkit/property.py +10 -3
- well_log_toolkit/visualization.py +291 -49
- {well_log_toolkit-0.1.131.dist-info → well_log_toolkit-0.1.132.dist-info}/METADATA +1 -1
- {well_log_toolkit-0.1.131.dist-info → well_log_toolkit-0.1.132.dist-info}/RECORD +6 -6
- {well_log_toolkit-0.1.131.dist-info → well_log_toolkit-0.1.132.dist-info}/WHEEL +0 -0
- {well_log_toolkit-0.1.131.dist-info → well_log_toolkit-0.1.132.dist-info}/top_level.txt +0 -0
well_log_toolkit/property.py
CHANGED
|
@@ -642,7 +642,7 @@ class Property(PropertyOperationsMixin):
|
|
|
642
642
|
|
|
643
643
|
def resample(self, target_depth: Union[np.ndarray, 'Property']) -> 'Property':
|
|
644
644
|
"""
|
|
645
|
-
Resample property to a new depth grid using
|
|
645
|
+
Resample property to a new depth grid using appropriate interpolation.
|
|
646
646
|
|
|
647
647
|
This method creates a new Property object with values interpolated to match
|
|
648
648
|
the target depth grid. This is required when combining properties with
|
|
@@ -663,7 +663,10 @@ class Property(PropertyOperationsMixin):
|
|
|
663
663
|
Notes
|
|
664
664
|
-----
|
|
665
665
|
- Uses linear interpolation for continuous data
|
|
666
|
-
- Uses
|
|
666
|
+
- Uses forward-fill (previous) for discrete data - geological zones extend
|
|
667
|
+
from their top/boundary until the next boundary is encountered. For example,
|
|
668
|
+
"Cerisa West top" at 2929.93m remains active until "Cerisa West SST 1 top"
|
|
669
|
+
at 2955.10m is intercepted.
|
|
667
670
|
- Values outside the original depth range are set to NaN
|
|
668
671
|
- NaN values in original data are excluded from interpolation
|
|
669
672
|
|
|
@@ -728,7 +731,11 @@ class Property(PropertyOperationsMixin):
|
|
|
728
731
|
|
|
729
732
|
# Choose interpolation method based on type
|
|
730
733
|
if self.type == 'discrete':
|
|
731
|
-
|
|
734
|
+
# Use 'previous' (forward-fill) for discrete properties
|
|
735
|
+
# This ensures geological zones extend from their top/boundary
|
|
736
|
+
# until the next top is encountered (e.g., "Cerisa West top" at 2929.93
|
|
737
|
+
# remains active until "Cerisa West SST 1 top" at 2955.10)
|
|
738
|
+
kind = 'previous'
|
|
732
739
|
else:
|
|
733
740
|
kind = 'linear'
|
|
734
741
|
|
|
@@ -3072,6 +3072,10 @@ class Crossplot:
|
|
|
3072
3072
|
# Data cache
|
|
3073
3073
|
self._data = None
|
|
3074
3074
|
|
|
3075
|
+
# Discrete property labels storage
|
|
3076
|
+
# Maps property role ('shape', 'color', 'size') to labels dict {0: 'label0', 1: 'label1', ...}
|
|
3077
|
+
self._discrete_labels = {}
|
|
3078
|
+
|
|
3075
3079
|
def add_layer(self, x: str, y: str, label: str):
|
|
3076
3080
|
"""
|
|
3077
3081
|
Add a new data layer to the crossplot.
|
|
@@ -3136,6 +3140,32 @@ class Crossplot:
|
|
|
3136
3140
|
# Only do expensive allclose if needed
|
|
3137
3141
|
return not np.allclose(prop_depth, ref_depth)
|
|
3138
3142
|
|
|
3143
|
+
# Helper function to align property values to target depth grid
|
|
3144
|
+
def align_property(prop, target_depth):
|
|
3145
|
+
"""
|
|
3146
|
+
Align property values to target depth grid.
|
|
3147
|
+
|
|
3148
|
+
Uses appropriate interpolation based on property type:
|
|
3149
|
+
- Discrete properties: forward-fill/previous (geological zones extend from
|
|
3150
|
+
their top/boundary until the next boundary is encountered)
|
|
3151
|
+
- Continuous properties: linear interpolation
|
|
3152
|
+
|
|
3153
|
+
Args:
|
|
3154
|
+
prop: Property object to align
|
|
3155
|
+
target_depth: Target depth array
|
|
3156
|
+
|
|
3157
|
+
Returns:
|
|
3158
|
+
Aligned values array
|
|
3159
|
+
"""
|
|
3160
|
+
if prop.type == 'discrete':
|
|
3161
|
+
# Use Property's resample method which handles discrete properties correctly
|
|
3162
|
+
# (forward-fill to preserve integer codes and geological zone logic)
|
|
3163
|
+
resampled = prop.resample(target_depth)
|
|
3164
|
+
return resampled.values
|
|
3165
|
+
else:
|
|
3166
|
+
# For continuous properties, use linear interpolation
|
|
3167
|
+
return np.interp(target_depth, prop.depth, prop.values, left=np.nan, right=np.nan)
|
|
3168
|
+
|
|
3139
3169
|
# Loop through each layer
|
|
3140
3170
|
for layer in self._layers:
|
|
3141
3171
|
layer_x = layer['x']
|
|
@@ -3153,9 +3183,9 @@ class Crossplot:
|
|
|
3153
3183
|
x_values = x_prop.values
|
|
3154
3184
|
y_values = y_prop.values
|
|
3155
3185
|
|
|
3156
|
-
# Align y values to x depth grid if needed
|
|
3186
|
+
# Align y values to x depth grid if needed using appropriate method
|
|
3157
3187
|
if needs_alignment(y_prop.depth, depths):
|
|
3158
|
-
y_values =
|
|
3188
|
+
y_values = align_property(y_prop, depths)
|
|
3159
3189
|
|
|
3160
3190
|
# Create dataframe for this well and layer
|
|
3161
3191
|
df = pd.DataFrame({
|
|
@@ -3176,10 +3206,14 @@ class Crossplot:
|
|
|
3176
3206
|
elif self.color and self.color != "depth":
|
|
3177
3207
|
try:
|
|
3178
3208
|
color_prop = well.get_property(self.color)
|
|
3179
|
-
|
|
3180
|
-
|
|
3209
|
+
# Store labels if discrete property (only once)
|
|
3210
|
+
if 'color' not in self._discrete_labels:
|
|
3211
|
+
self._store_discrete_labels(color_prop, 'color')
|
|
3212
|
+
# Align to x depth grid using appropriate method for property type
|
|
3181
3213
|
if needs_alignment(color_prop.depth, depths):
|
|
3182
|
-
color_values =
|
|
3214
|
+
color_values = align_property(color_prop, depths)
|
|
3215
|
+
else:
|
|
3216
|
+
color_values = color_prop.values
|
|
3183
3217
|
df['color_val'] = color_values
|
|
3184
3218
|
except (AttributeError, KeyError, PropertyNotFoundError):
|
|
3185
3219
|
# Silently use depth as fallback
|
|
@@ -3194,10 +3228,14 @@ class Crossplot:
|
|
|
3194
3228
|
elif self.size:
|
|
3195
3229
|
try:
|
|
3196
3230
|
size_prop = well.get_property(self.size)
|
|
3197
|
-
|
|
3198
|
-
|
|
3231
|
+
# Store labels if discrete property (only once)
|
|
3232
|
+
if 'size' not in self._discrete_labels:
|
|
3233
|
+
self._store_discrete_labels(size_prop, 'size')
|
|
3234
|
+
# Align to x depth grid using appropriate method for property type
|
|
3199
3235
|
if needs_alignment(size_prop.depth, depths):
|
|
3200
|
-
size_values =
|
|
3236
|
+
size_values = align_property(size_prop, depths)
|
|
3237
|
+
else:
|
|
3238
|
+
size_values = size_prop.values
|
|
3201
3239
|
df['size_val'] = size_values
|
|
3202
3240
|
except (AttributeError, KeyError, PropertyNotFoundError):
|
|
3203
3241
|
# Silently skip if size property not found
|
|
@@ -3210,10 +3248,14 @@ class Crossplot:
|
|
|
3210
3248
|
elif self.shape and self.shape != "well":
|
|
3211
3249
|
try:
|
|
3212
3250
|
shape_prop = well.get_property(self.shape)
|
|
3213
|
-
|
|
3214
|
-
|
|
3251
|
+
# Store labels if discrete property (only once)
|
|
3252
|
+
if 'shape' not in self._discrete_labels:
|
|
3253
|
+
self._store_discrete_labels(shape_prop, 'shape')
|
|
3254
|
+
# Align to x depth grid using appropriate method for property type
|
|
3215
3255
|
if needs_alignment(shape_prop.depth, depths):
|
|
3216
|
-
shape_values =
|
|
3256
|
+
shape_values = align_property(shape_prop, depths)
|
|
3257
|
+
else:
|
|
3258
|
+
shape_values = shape_prop.values
|
|
3217
3259
|
df['shape_val'] = shape_values
|
|
3218
3260
|
except (AttributeError, KeyError, PropertyNotFoundError):
|
|
3219
3261
|
# Silently skip if shape property not found
|
|
@@ -3377,6 +3419,201 @@ class Crossplot:
|
|
|
3377
3419
|
|
|
3378
3420
|
return best_pos, second_best_pos
|
|
3379
3421
|
|
|
3422
|
+
def _store_discrete_labels(self, prop, role: str) -> None:
|
|
3423
|
+
"""
|
|
3424
|
+
Store labels from a discrete property for later use in legends.
|
|
3425
|
+
|
|
3426
|
+
Args:
|
|
3427
|
+
prop: Property object (must have type and labels attributes)
|
|
3428
|
+
role: Property role - 'shape', 'color', or 'size'
|
|
3429
|
+
"""
|
|
3430
|
+
if hasattr(prop, 'type') and prop.type == 'discrete' and hasattr(prop, 'labels') and prop.labels:
|
|
3431
|
+
self._discrete_labels[role] = prop.labels.copy()
|
|
3432
|
+
|
|
3433
|
+
def _get_display_label(self, value, role: str) -> str:
|
|
3434
|
+
"""
|
|
3435
|
+
Get display label for a value, using stored labels for discrete properties.
|
|
3436
|
+
|
|
3437
|
+
For discrete properties with labels, converts integer codes to readable names.
|
|
3438
|
+
For continuous properties or discrete without labels, returns string value.
|
|
3439
|
+
|
|
3440
|
+
Args:
|
|
3441
|
+
value: The value to get label for (could be int, float, or string)
|
|
3442
|
+
role: Property role - 'shape', 'color', or 'size'
|
|
3443
|
+
|
|
3444
|
+
Returns:
|
|
3445
|
+
Display label string
|
|
3446
|
+
|
|
3447
|
+
Examples:
|
|
3448
|
+
>>> # For discrete property with labels {0: 'Agat top', 1: 'Cerisa Main top'}
|
|
3449
|
+
>>> self._get_display_label(0.0, 'shape')
|
|
3450
|
+
'Agat top'
|
|
3451
|
+
|
|
3452
|
+
>>> # For continuous property or no labels
|
|
3453
|
+
>>> self._get_display_label(2.5, 'color')
|
|
3454
|
+
'2.5'
|
|
3455
|
+
"""
|
|
3456
|
+
if role in self._discrete_labels:
|
|
3457
|
+
# Try to convert to integer and look up label
|
|
3458
|
+
try:
|
|
3459
|
+
int_val = int(np.round(float(value)))
|
|
3460
|
+
return self._discrete_labels[role].get(int_val, str(value))
|
|
3461
|
+
except (ValueError, TypeError):
|
|
3462
|
+
return str(value)
|
|
3463
|
+
return str(value)
|
|
3464
|
+
|
|
3465
|
+
def _is_edge_location(self, location: str) -> bool:
|
|
3466
|
+
"""Check if a legend location is on the left or right edge.
|
|
3467
|
+
|
|
3468
|
+
Args:
|
|
3469
|
+
location: Matplotlib location string
|
|
3470
|
+
|
|
3471
|
+
Returns:
|
|
3472
|
+
True if on left or right edge (for vertical stacking)
|
|
3473
|
+
"""
|
|
3474
|
+
edge_locations = ['upper left', 'center left', 'lower left',
|
|
3475
|
+
'upper right', 'center right', 'lower right']
|
|
3476
|
+
return location in edge_locations
|
|
3477
|
+
|
|
3478
|
+
def _create_grouped_legends(self,
|
|
3479
|
+
shape_handles, shape_title: str,
|
|
3480
|
+
color_handles, color_title: str,
|
|
3481
|
+
location: str) -> None:
|
|
3482
|
+
"""Create grouped legends in the same region, stacked or side-by-side.
|
|
3483
|
+
|
|
3484
|
+
When both shape and color legends are needed, this groups them in the same
|
|
3485
|
+
1/9th section without overlap. Stacks vertically on edges, side-by-side elsewhere.
|
|
3486
|
+
|
|
3487
|
+
Args:
|
|
3488
|
+
shape_handles: List of handles for shape legend
|
|
3489
|
+
shape_title: Title for shape legend
|
|
3490
|
+
color_handles: List of handles for color legend
|
|
3491
|
+
color_title: Title for color legend
|
|
3492
|
+
location: Matplotlib location string for positioning
|
|
3493
|
+
"""
|
|
3494
|
+
is_edge = self._is_edge_location(location)
|
|
3495
|
+
|
|
3496
|
+
# Determine base anchor point from location string
|
|
3497
|
+
# Map location to (x, y) coordinates in figure space
|
|
3498
|
+
anchor_map = {
|
|
3499
|
+
'upper left': (0.02, 0.98),
|
|
3500
|
+
'upper center': (0.5, 0.98),
|
|
3501
|
+
'upper right': (0.98, 0.98),
|
|
3502
|
+
'center left': (0.02, 0.5),
|
|
3503
|
+
'center': (0.5, 0.5),
|
|
3504
|
+
'center right': (0.98, 0.5),
|
|
3505
|
+
'lower left': (0.02, 0.02),
|
|
3506
|
+
'lower center': (0.5, 0.02),
|
|
3507
|
+
'lower right': (0.98, 0.02),
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
base_x, base_y = anchor_map.get(location, (0.98, 0.98))
|
|
3511
|
+
|
|
3512
|
+
if is_edge:
|
|
3513
|
+
# Stack vertically on edges
|
|
3514
|
+
# Position shape legend at the top
|
|
3515
|
+
shape_legend = self.ax.legend(
|
|
3516
|
+
handles=shape_handles,
|
|
3517
|
+
title=shape_title,
|
|
3518
|
+
loc=location,
|
|
3519
|
+
frameon=True,
|
|
3520
|
+
framealpha=0.9,
|
|
3521
|
+
edgecolor='black',
|
|
3522
|
+
bbox_to_anchor=(base_x, base_y),
|
|
3523
|
+
bbox_transform=self.fig.transFigure
|
|
3524
|
+
)
|
|
3525
|
+
shape_legend.get_title().set_fontweight('bold')
|
|
3526
|
+
self.ax.add_artist(shape_legend)
|
|
3527
|
+
|
|
3528
|
+
# Calculate offset for color legend below shape legend
|
|
3529
|
+
# Estimate shape legend height and add spacing
|
|
3530
|
+
shape_height = len(shape_handles) * 0.025 + 0.05 # Rough estimate
|
|
3531
|
+
|
|
3532
|
+
# Adjust y position for color legend
|
|
3533
|
+
if 'upper' in location:
|
|
3534
|
+
color_y = base_y - shape_height - 0.02 # Stack below
|
|
3535
|
+
elif 'lower' in location:
|
|
3536
|
+
color_y = base_y + shape_height + 0.02 # Stack above
|
|
3537
|
+
else: # center
|
|
3538
|
+
color_y = base_y - shape_height / 2 - 0.01 # Stack below
|
|
3539
|
+
|
|
3540
|
+
color_legend = self.ax.legend(
|
|
3541
|
+
handles=color_handles,
|
|
3542
|
+
title=color_title,
|
|
3543
|
+
loc=location,
|
|
3544
|
+
frameon=True,
|
|
3545
|
+
framealpha=0.9,
|
|
3546
|
+
edgecolor='black',
|
|
3547
|
+
bbox_to_anchor=(base_x, color_y),
|
|
3548
|
+
bbox_transform=self.fig.transFigure
|
|
3549
|
+
)
|
|
3550
|
+
color_legend.get_title().set_fontweight('bold')
|
|
3551
|
+
else:
|
|
3552
|
+
# Place side by side for non-edge locations (top, bottom, center)
|
|
3553
|
+
# Estimate width of each legend
|
|
3554
|
+
legend_width = 0.15
|
|
3555
|
+
|
|
3556
|
+
if 'center' in location and location != 'center left' and location != 'center right':
|
|
3557
|
+
# For center positions, place them side by side
|
|
3558
|
+
shape_x = base_x - legend_width / 2 - 0.01
|
|
3559
|
+
color_x = base_x + legend_width / 2 + 0.01
|
|
3560
|
+
|
|
3561
|
+
shape_legend = self.ax.legend(
|
|
3562
|
+
handles=shape_handles,
|
|
3563
|
+
title=shape_title,
|
|
3564
|
+
loc='center',
|
|
3565
|
+
frameon=True,
|
|
3566
|
+
framealpha=0.9,
|
|
3567
|
+
edgecolor='black',
|
|
3568
|
+
bbox_to_anchor=(shape_x, base_y),
|
|
3569
|
+
bbox_transform=self.fig.transFigure
|
|
3570
|
+
)
|
|
3571
|
+
shape_legend.get_title().set_fontweight('bold')
|
|
3572
|
+
self.ax.add_artist(shape_legend)
|
|
3573
|
+
|
|
3574
|
+
color_legend = self.ax.legend(
|
|
3575
|
+
handles=color_handles,
|
|
3576
|
+
title=color_title,
|
|
3577
|
+
loc='center',
|
|
3578
|
+
frameon=True,
|
|
3579
|
+
framealpha=0.9,
|
|
3580
|
+
edgecolor='black',
|
|
3581
|
+
bbox_to_anchor=(color_x, base_y),
|
|
3582
|
+
bbox_transform=self.fig.transFigure
|
|
3583
|
+
)
|
|
3584
|
+
color_legend.get_title().set_fontweight('bold')
|
|
3585
|
+
else:
|
|
3586
|
+
# For other positions, fall back to stacking
|
|
3587
|
+
shape_legend = self.ax.legend(
|
|
3588
|
+
handles=shape_handles,
|
|
3589
|
+
title=shape_title,
|
|
3590
|
+
loc=location,
|
|
3591
|
+
frameon=True,
|
|
3592
|
+
framealpha=0.9,
|
|
3593
|
+
edgecolor='black'
|
|
3594
|
+
)
|
|
3595
|
+
shape_legend.get_title().set_fontweight('bold')
|
|
3596
|
+
self.ax.add_artist(shape_legend)
|
|
3597
|
+
|
|
3598
|
+
# Estimate offset
|
|
3599
|
+
shape_height = len(shape_handles) * 0.025 + 0.05
|
|
3600
|
+
if 'upper' in location:
|
|
3601
|
+
color_y = base_y - shape_height - 0.02
|
|
3602
|
+
else:
|
|
3603
|
+
color_y = base_y + shape_height + 0.02
|
|
3604
|
+
|
|
3605
|
+
color_legend = self.ax.legend(
|
|
3606
|
+
handles=color_handles,
|
|
3607
|
+
title=color_title,
|
|
3608
|
+
loc=location,
|
|
3609
|
+
frameon=True,
|
|
3610
|
+
framealpha=0.9,
|
|
3611
|
+
edgecolor='black',
|
|
3612
|
+
bbox_to_anchor=(base_x, color_y),
|
|
3613
|
+
bbox_transform=self.fig.transFigure
|
|
3614
|
+
)
|
|
3615
|
+
color_legend.get_title().set_fontweight('bold')
|
|
3616
|
+
|
|
3380
3617
|
def _format_regression_label(self, name: str, reg, include_equation: bool = None, include_r2: bool = None) -> str:
|
|
3381
3618
|
"""Format a modern, compact regression label.
|
|
3382
3619
|
|
|
@@ -3898,7 +4135,7 @@ class Crossplot:
|
|
|
3898
4135
|
# Create custom legend handles
|
|
3899
4136
|
legend_elements = [Patch(facecolor=category_colors[cat],
|
|
3900
4137
|
edgecolor=self.edge_color,
|
|
3901
|
-
label=
|
|
4138
|
+
label=self._get_display_label(cat, 'color'))
|
|
3902
4139
|
for cat in unique_categories]
|
|
3903
4140
|
|
|
3904
4141
|
colorbar_label = self.color if self.color != "depth" and self.color != "label" else "Category"
|
|
@@ -4008,20 +4245,53 @@ class Crossplot:
|
|
|
4008
4245
|
edgecolors=self.edge_color,
|
|
4009
4246
|
linewidths=self.edge_width,
|
|
4010
4247
|
marker=marker,
|
|
4011
|
-
label=
|
|
4248
|
+
label=self._get_display_label(group_name, 'shape')
|
|
4012
4249
|
)
|
|
4013
4250
|
|
|
4014
4251
|
if first_scatter is None and self.color and not is_categorical:
|
|
4015
4252
|
first_scatter = scatter
|
|
4016
4253
|
|
|
4017
|
-
#
|
|
4018
|
-
|
|
4254
|
+
# Check if we need both shape and color legends (grouped layout)
|
|
4255
|
+
need_shape_legend = self.show_legend
|
|
4256
|
+
need_color_legend = self.color and is_categorical and self.show_legend
|
|
4257
|
+
|
|
4258
|
+
if need_shape_legend and need_color_legend:
|
|
4259
|
+
# Create grouped legends in the same region
|
|
4019
4260
|
# Get best location based on data density
|
|
4020
4261
|
if self._data is not None:
|
|
4021
4262
|
primary_loc, _ = self._find_best_legend_locations(self._data)
|
|
4022
4263
|
else:
|
|
4023
4264
|
primary_loc = 'best'
|
|
4024
4265
|
|
|
4266
|
+
# Prepare shape legend handles (from scatter plots)
|
|
4267
|
+
shape_handles, _ = self.ax.get_legend_handles_labels()
|
|
4268
|
+
|
|
4269
|
+
# Prepare color legend handles
|
|
4270
|
+
c_vals_all = data['color_val'].values
|
|
4271
|
+
unique_categories = pd.Series(c_vals_all).dropna().unique()
|
|
4272
|
+
color_handles = [Patch(facecolor=category_colors[cat],
|
|
4273
|
+
edgecolor=self.edge_color,
|
|
4274
|
+
label=self._get_display_label(cat, 'color'))
|
|
4275
|
+
for cat in unique_categories]
|
|
4276
|
+
|
|
4277
|
+
colorbar_label = self.color if self.color != "depth" and self.color != "label" else "Category"
|
|
4278
|
+
|
|
4279
|
+
# Create grouped legends
|
|
4280
|
+
self._create_grouped_legends(
|
|
4281
|
+
shape_handles=shape_handles,
|
|
4282
|
+
shape_title=group_label,
|
|
4283
|
+
color_handles=color_handles,
|
|
4284
|
+
color_title=colorbar_label,
|
|
4285
|
+
location=primary_loc
|
|
4286
|
+
)
|
|
4287
|
+
|
|
4288
|
+
elif need_shape_legend:
|
|
4289
|
+
# Only shape legend needed
|
|
4290
|
+
if self._data is not None:
|
|
4291
|
+
primary_loc, _ = self._find_best_legend_locations(self._data)
|
|
4292
|
+
else:
|
|
4293
|
+
primary_loc = 'best'
|
|
4294
|
+
|
|
4025
4295
|
legend = self.ax.legend(
|
|
4026
4296
|
title=group_label,
|
|
4027
4297
|
loc=primary_loc,
|
|
@@ -4030,43 +4300,15 @@ class Crossplot:
|
|
|
4030
4300
|
edgecolor='black'
|
|
4031
4301
|
)
|
|
4032
4302
|
legend.get_title().set_fontweight('bold')
|
|
4033
|
-
|
|
4034
4303
|
# Store the primary legend so it persists when regression legend is added
|
|
4035
4304
|
self.ax.add_artist(legend)
|
|
4036
4305
|
|
|
4037
|
-
# Add colorbar
|
|
4038
|
-
if self.color:
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
# Create custom legend handles
|
|
4045
|
-
legend_elements = [Patch(facecolor=category_colors[cat],
|
|
4046
|
-
edgecolor=self.edge_color,
|
|
4047
|
-
label=str(cat))
|
|
4048
|
-
for cat in unique_categories]
|
|
4049
|
-
|
|
4050
|
-
colorbar_label = self.color if self.color != "depth" and self.color != "label" else "Category"
|
|
4051
|
-
|
|
4052
|
-
# Find a good location for the color legend (opposite corner from shape legend)
|
|
4053
|
-
if self._data is not None:
|
|
4054
|
-
_, secondary_loc = self._find_best_legend_locations(self._data)
|
|
4055
|
-
else:
|
|
4056
|
-
secondary_loc = 'upper right'
|
|
4057
|
-
|
|
4058
|
-
color_legend = self.ax.legend(handles=legend_elements,
|
|
4059
|
-
title=colorbar_label,
|
|
4060
|
-
loc=secondary_loc,
|
|
4061
|
-
frameon=True,
|
|
4062
|
-
framealpha=0.9,
|
|
4063
|
-
edgecolor='black')
|
|
4064
|
-
color_legend.get_title().set_fontweight('bold')
|
|
4065
|
-
elif not is_categorical and self.show_colorbar and first_scatter:
|
|
4066
|
-
# Add continuous colorbar
|
|
4067
|
-
self.colorbar = self.fig.colorbar(first_scatter, ax=self.ax)
|
|
4068
|
-
colorbar_label = self.color if self.color != "depth" else "Depth"
|
|
4069
|
-
self.colorbar.set_label(colorbar_label, fontsize=11, fontweight='bold')
|
|
4306
|
+
# Add colorbar for continuous color mapping
|
|
4307
|
+
if self.color and not is_categorical and self.show_colorbar and first_scatter:
|
|
4308
|
+
# Add continuous colorbar
|
|
4309
|
+
self.colorbar = self.fig.colorbar(first_scatter, ax=self.ax)
|
|
4310
|
+
colorbar_label = self.color if self.color != "depth" else "Depth"
|
|
4311
|
+
self.colorbar.set_label(colorbar_label, fontsize=11, fontweight='bold')
|
|
4070
4312
|
|
|
4071
4313
|
def add_regression(
|
|
4072
4314
|
self,
|
|
@@ -3,13 +3,13 @@ well_log_toolkit/exceptions.py,sha256=X_fzC7d4yaBFO9Vx74dEIB6xmI9Agi6_bTU3MPxn6k
|
|
|
3
3
|
well_log_toolkit/las_file.py,sha256=Tj0mRfX1aX2s6uug7BBlY1m_mu3G50EGxHGzD0eEedE,53876
|
|
4
4
|
well_log_toolkit/manager.py,sha256=PuHF8rqypirNIN77STHcvg8WneExikpq6ZvkcRbcQpg,109776
|
|
5
5
|
well_log_toolkit/operations.py,sha256=z8j8fGBOwoJGUQFy-Vawjq9nm3OD_dUt0oaNh8yuG7o,18515
|
|
6
|
-
well_log_toolkit/property.py,sha256=
|
|
6
|
+
well_log_toolkit/property.py,sha256=B-3mXNJmvIqjjMdsu1kgVSwMgEwbJ36wn_n_oppdJFw,76769
|
|
7
7
|
well_log_toolkit/regression.py,sha256=7D3oI-1XVlFb-mOoHTxTTtUHERFyvQSBAzJzAGVoZnk,25192
|
|
8
8
|
well_log_toolkit/statistics.py,sha256=_huPMbv2H3o9ezunjEM94mJknX5wPK8V4nDv2lIZZRw,16814
|
|
9
9
|
well_log_toolkit/utils.py,sha256=O2KPq4htIoUlL74V2zKftdqqTjRfezU9M-568zPLme0,6866
|
|
10
|
-
well_log_toolkit/visualization.py,sha256=
|
|
10
|
+
well_log_toolkit/visualization.py,sha256=204l_LFgcSfnpHti1Xc1iGF-KUVEyxkJNS88gkUDeXA,179959
|
|
11
11
|
well_log_toolkit/well.py,sha256=Aav5Y-rui8YsJdvk7BFndNPUu1O9mcjwDApAGyqV9kw,104535
|
|
12
|
-
well_log_toolkit-0.1.
|
|
13
|
-
well_log_toolkit-0.1.
|
|
14
|
-
well_log_toolkit-0.1.
|
|
15
|
-
well_log_toolkit-0.1.
|
|
12
|
+
well_log_toolkit-0.1.132.dist-info/METADATA,sha256=5IVBXhf2t9MQW4Gswct9F1IHl5JJnKOWDZxK4g2vUq4,59810
|
|
13
|
+
well_log_toolkit-0.1.132.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
well_log_toolkit-0.1.132.dist-info/top_level.txt,sha256=BMOo7OKLcZEnjo0wOLMclwzwTbYKYh31I8RGDOGSBdE,17
|
|
15
|
+
well_log_toolkit-0.1.132.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|