well-log-toolkit 0.1.134__py3-none-any.whl → 0.1.135__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/visualization.py +102 -48
- {well_log_toolkit-0.1.134.dist-info → well_log_toolkit-0.1.135.dist-info}/METADATA +1 -1
- {well_log_toolkit-0.1.134.dist-info → well_log_toolkit-0.1.135.dist-info}/RECORD +5 -5
- {well_log_toolkit-0.1.134.dist-info → well_log_toolkit-0.1.135.dist-info}/WHEEL +0 -0
- {well_log_toolkit-0.1.134.dist-info → well_log_toolkit-0.1.135.dist-info}/top_level.txt +0 -0
|
@@ -3475,22 +3475,48 @@ class Crossplot:
|
|
|
3475
3475
|
Returns:
|
|
3476
3476
|
Tuple of (segment_number, matplotlib_location_string)
|
|
3477
3477
|
"""
|
|
3478
|
-
# Get x and y
|
|
3478
|
+
# Get x and y data values
|
|
3479
3479
|
x_vals = data['x'].values
|
|
3480
3480
|
y_vals = data['y'].values
|
|
3481
3481
|
|
|
3482
|
-
#
|
|
3482
|
+
# Get axes limits to convert data coordinates to axes-normalized coordinates (0-1)
|
|
3483
|
+
# This ensures we're dividing the GRAPH AREA, not the data space
|
|
3484
|
+
if self.ax is not None:
|
|
3485
|
+
x_lim = self.ax.get_xlim()
|
|
3486
|
+
y_lim = self.ax.get_ylim()
|
|
3487
|
+
else:
|
|
3488
|
+
# Fallback if ax not available yet
|
|
3489
|
+
x_lim = (np.nanmin(x_vals), np.nanmax(x_vals))
|
|
3490
|
+
y_lim = (np.nanmin(y_vals), np.nanmax(y_vals))
|
|
3491
|
+
|
|
3492
|
+
# Handle logarithmic axes - transform to log space for proper visual segment calculation
|
|
3493
|
+
# On log axes, equal visual spacing corresponds to equal ratios, not equal differences
|
|
3483
3494
|
if self.x_log:
|
|
3484
|
-
|
|
3495
|
+
# Filter out non-positive values before log transform
|
|
3496
|
+
x_valid = x_vals > 0
|
|
3497
|
+
x_vals_transformed = np.where(x_valid, np.log10(x_vals), np.nan)
|
|
3498
|
+
x_lim_transformed = (np.log10(max(x_lim[0], 1e-10)), np.log10(max(x_lim[1], 1e-10)))
|
|
3499
|
+
else:
|
|
3500
|
+
x_vals_transformed = x_vals
|
|
3501
|
+
x_lim_transformed = x_lim
|
|
3502
|
+
|
|
3485
3503
|
if self.y_log:
|
|
3486
|
-
|
|
3504
|
+
# Filter out non-positive values before log transform
|
|
3505
|
+
y_valid = y_vals > 0
|
|
3506
|
+
y_vals_transformed = np.where(y_valid, np.log10(y_vals), np.nan)
|
|
3507
|
+
y_lim_transformed = (np.log10(max(y_lim[0], 1e-10)), np.log10(max(y_lim[1], 1e-10)))
|
|
3508
|
+
else:
|
|
3509
|
+
y_vals_transformed = y_vals
|
|
3510
|
+
y_lim_transformed = y_lim
|
|
3487
3511
|
|
|
3488
|
-
|
|
3489
|
-
|
|
3512
|
+
# Normalize transformed coordinates to axes coordinates (0-1)
|
|
3513
|
+
# This divides the visible graph area properly, accounting for log scales
|
|
3514
|
+
x_norm = (x_vals_transformed - x_lim_transformed[0]) / (x_lim_transformed[1] - x_lim_transformed[0])
|
|
3515
|
+
y_norm = (y_vals_transformed - y_lim_transformed[0]) / (y_lim_transformed[1] - y_lim_transformed[0])
|
|
3490
3516
|
|
|
3491
|
-
# Create 3x3 grid
|
|
3492
|
-
x_bins = np.linspace(
|
|
3493
|
-
y_bins = np.linspace(
|
|
3517
|
+
# Create 3x3 grid in axes-normalized space (0-1)
|
|
3518
|
+
x_bins = np.linspace(0, 1, 4)
|
|
3519
|
+
y_bins = np.linspace(0, 1, 4)
|
|
3494
3520
|
|
|
3495
3521
|
# Map segments 1-9 to grid positions (i, j)
|
|
3496
3522
|
# Segment numbering:
|
|
@@ -3522,12 +3548,12 @@ class Crossplot:
|
|
|
3522
3548
|
9: 'lower right',
|
|
3523
3549
|
}
|
|
3524
3550
|
|
|
3525
|
-
# Count points in each segment
|
|
3526
|
-
total_points = len(
|
|
3551
|
+
# Count points in each segment using normalized coordinates
|
|
3552
|
+
total_points = len(x_norm)
|
|
3527
3553
|
segment_counts = {}
|
|
3528
3554
|
for segment, (i, j) in segment_to_grid.items():
|
|
3529
|
-
x_mask = (
|
|
3530
|
-
y_mask = (
|
|
3555
|
+
x_mask = (x_norm >= x_bins[i]) & (x_norm < x_bins[i+1])
|
|
3556
|
+
y_mask = (y_norm >= y_bins[j]) & (y_norm < y_bins[j+1])
|
|
3531
3557
|
count = np.sum(x_mask & y_mask)
|
|
3532
3558
|
segment_counts[segment] = count
|
|
3533
3559
|
|
|
@@ -3638,20 +3664,24 @@ class Crossplot:
|
|
|
3638
3664
|
is_edge = self._is_edge_location(location)
|
|
3639
3665
|
|
|
3640
3666
|
# Determine base anchor point from location string
|
|
3641
|
-
# Map location to (x, y) coordinates in
|
|
3667
|
+
# Map location to (x, y) coordinates in AXES space (0-1 within the graph area)
|
|
3668
|
+
# These match the segment corners:
|
|
3669
|
+
# Segment 1=upper left (0,1), 2=upper center (0.5,1), 3=upper right (1,1)
|
|
3670
|
+
# Segment 4=center left (0,0.5), 5=center (0.5,0.5), 6=center right (1,0.5)
|
|
3671
|
+
# Segment 7=lower left (0,0), 8=lower center (0.5,0), 9=lower right (1,0)
|
|
3642
3672
|
anchor_map = {
|
|
3643
|
-
'upper left': (0
|
|
3644
|
-
'upper center': (0.5,
|
|
3645
|
-
'upper right': (
|
|
3646
|
-
'center left': (0
|
|
3673
|
+
'upper left': (0, 1),
|
|
3674
|
+
'upper center': (0.5, 1),
|
|
3675
|
+
'upper right': (1, 1),
|
|
3676
|
+
'center left': (0, 0.5),
|
|
3647
3677
|
'center': (0.5, 0.5),
|
|
3648
|
-
'center right': (
|
|
3649
|
-
'lower left': (0
|
|
3650
|
-
'lower center': (0.5, 0
|
|
3651
|
-
'lower right': (
|
|
3678
|
+
'center right': (1, 0.5),
|
|
3679
|
+
'lower left': (0, 0),
|
|
3680
|
+
'lower center': (0.5, 0),
|
|
3681
|
+
'lower right': (1, 0),
|
|
3652
3682
|
}
|
|
3653
3683
|
|
|
3654
|
-
base_x, base_y = anchor_map.get(location, (
|
|
3684
|
+
base_x, base_y = anchor_map.get(location, (1, 1))
|
|
3655
3685
|
|
|
3656
3686
|
if is_edge:
|
|
3657
3687
|
# Stack vertically on edges
|
|
@@ -3664,23 +3694,23 @@ class Crossplot:
|
|
|
3664
3694
|
framealpha=0.9,
|
|
3665
3695
|
edgecolor='black',
|
|
3666
3696
|
bbox_to_anchor=(base_x, base_y),
|
|
3667
|
-
bbox_transform=self.
|
|
3697
|
+
bbox_transform=self.ax.transAxes
|
|
3668
3698
|
)
|
|
3669
3699
|
shape_legend.get_title().set_fontweight('bold')
|
|
3670
3700
|
shape_legend.set_clip_on(False) # Prevent clipping outside axes
|
|
3671
3701
|
self.ax.add_artist(shape_legend)
|
|
3672
3702
|
|
|
3673
3703
|
# Calculate offset for color legend below shape legend
|
|
3674
|
-
# Estimate shape legend height
|
|
3675
|
-
shape_height = len(shape_handles) * 0.
|
|
3704
|
+
# Estimate shape legend height in axes coordinates
|
|
3705
|
+
shape_height = len(shape_handles) * 0.05 + 0.08 # Adjusted for axes space
|
|
3676
3706
|
|
|
3677
3707
|
# Adjust y position for color legend
|
|
3678
3708
|
if 'upper' in location:
|
|
3679
|
-
color_y = base_y - shape_height
|
|
3709
|
+
color_y = base_y - shape_height # Stack below
|
|
3680
3710
|
elif 'lower' in location:
|
|
3681
|
-
color_y = base_y + shape_height
|
|
3711
|
+
color_y = base_y + shape_height # Stack above
|
|
3682
3712
|
else: # center
|
|
3683
|
-
color_y = base_y - shape_height / 2
|
|
3713
|
+
color_y = base_y - shape_height / 2 # Stack below
|
|
3684
3714
|
|
|
3685
3715
|
color_legend = self.ax.legend(
|
|
3686
3716
|
handles=color_handles,
|
|
@@ -3690,19 +3720,19 @@ class Crossplot:
|
|
|
3690
3720
|
framealpha=0.9,
|
|
3691
3721
|
edgecolor='black',
|
|
3692
3722
|
bbox_to_anchor=(base_x, color_y),
|
|
3693
|
-
bbox_transform=self.
|
|
3723
|
+
bbox_transform=self.ax.transAxes
|
|
3694
3724
|
)
|
|
3695
3725
|
color_legend.get_title().set_fontweight('bold')
|
|
3696
3726
|
color_legend.set_clip_on(False) # Prevent clipping outside axes
|
|
3697
3727
|
else:
|
|
3698
3728
|
# Place side by side for non-edge locations (top, bottom, center)
|
|
3699
|
-
# Estimate width of each legend
|
|
3700
|
-
legend_width = 0.
|
|
3729
|
+
# Estimate width of each legend in axes coordinates
|
|
3730
|
+
legend_width = 0.20
|
|
3701
3731
|
|
|
3702
3732
|
if 'center' in location and location != 'center left' and location != 'center right':
|
|
3703
3733
|
# For center positions, place them side by side
|
|
3704
|
-
shape_x = base_x - legend_width / 2
|
|
3705
|
-
color_x = base_x + legend_width / 2
|
|
3734
|
+
shape_x = base_x - legend_width / 2
|
|
3735
|
+
color_x = base_x + legend_width / 2
|
|
3706
3736
|
|
|
3707
3737
|
shape_legend = self.ax.legend(
|
|
3708
3738
|
handles=shape_handles,
|
|
@@ -3712,7 +3742,7 @@ class Crossplot:
|
|
|
3712
3742
|
framealpha=0.9,
|
|
3713
3743
|
edgecolor='black',
|
|
3714
3744
|
bbox_to_anchor=(shape_x, base_y),
|
|
3715
|
-
bbox_transform=self.
|
|
3745
|
+
bbox_transform=self.ax.transAxes
|
|
3716
3746
|
)
|
|
3717
3747
|
shape_legend.get_title().set_fontweight('bold')
|
|
3718
3748
|
shape_legend.set_clip_on(False) # Prevent clipping outside axes
|
|
@@ -3726,7 +3756,7 @@ class Crossplot:
|
|
|
3726
3756
|
framealpha=0.9,
|
|
3727
3757
|
edgecolor='black',
|
|
3728
3758
|
bbox_to_anchor=(color_x, base_y),
|
|
3729
|
-
bbox_transform=self.
|
|
3759
|
+
bbox_transform=self.ax.transAxes
|
|
3730
3760
|
)
|
|
3731
3761
|
color_legend.get_title().set_fontweight('bold')
|
|
3732
3762
|
color_legend.set_clip_on(False) # Prevent clipping outside axes
|
|
@@ -3738,18 +3768,20 @@ class Crossplot:
|
|
|
3738
3768
|
loc=location,
|
|
3739
3769
|
frameon=True,
|
|
3740
3770
|
framealpha=0.9,
|
|
3741
|
-
edgecolor='black'
|
|
3771
|
+
edgecolor='black',
|
|
3772
|
+
bbox_to_anchor=(base_x, base_y),
|
|
3773
|
+
bbox_transform=self.ax.transAxes
|
|
3742
3774
|
)
|
|
3743
3775
|
shape_legend.get_title().set_fontweight('bold')
|
|
3744
3776
|
shape_legend.set_clip_on(False) # Prevent clipping outside axes
|
|
3745
3777
|
self.ax.add_artist(shape_legend)
|
|
3746
3778
|
|
|
3747
|
-
# Estimate offset
|
|
3748
|
-
shape_height = len(shape_handles) * 0.
|
|
3779
|
+
# Estimate offset in axes coordinates
|
|
3780
|
+
shape_height = len(shape_handles) * 0.05 + 0.08
|
|
3749
3781
|
if 'upper' in location:
|
|
3750
|
-
color_y = base_y - shape_height
|
|
3782
|
+
color_y = base_y - shape_height
|
|
3751
3783
|
else:
|
|
3752
|
-
color_y = base_y + shape_height
|
|
3784
|
+
color_y = base_y + shape_height
|
|
3753
3785
|
|
|
3754
3786
|
color_legend = self.ax.legend(
|
|
3755
3787
|
handles=color_handles,
|
|
@@ -3759,7 +3791,7 @@ class Crossplot:
|
|
|
3759
3791
|
framealpha=0.9,
|
|
3760
3792
|
edgecolor='black',
|
|
3761
3793
|
bbox_to_anchor=(base_x, color_y),
|
|
3762
|
-
bbox_transform=self.
|
|
3794
|
+
bbox_transform=self.ax.transAxes
|
|
3763
3795
|
)
|
|
3764
3796
|
color_legend.get_title().set_fontweight('bold')
|
|
3765
3797
|
color_legend.set_clip_on(False) # Prevent clipping outside axes
|
|
@@ -3837,14 +3869,32 @@ class Crossplot:
|
|
|
3837
3869
|
secondary_loc = 'lower right'
|
|
3838
3870
|
|
|
3839
3871
|
# Determine descriptive title based on regression type
|
|
3872
|
+
# Extract the regression type and add it to the title
|
|
3873
|
+
reg_type_str = None
|
|
3840
3874
|
if self.regression_by_color_and_shape:
|
|
3841
|
-
|
|
3875
|
+
base_title = 'Regressions by color and shape'
|
|
3876
|
+
config = self._parse_regression_config(self.regression_by_color_and_shape)
|
|
3877
|
+
reg_type_str = config.get('type', None)
|
|
3842
3878
|
elif self.regression_by_color:
|
|
3843
|
-
|
|
3879
|
+
base_title = 'Regressions by color'
|
|
3880
|
+
config = self._parse_regression_config(self.regression_by_color)
|
|
3881
|
+
reg_type_str = config.get('type', None)
|
|
3844
3882
|
elif self.regression_by_group:
|
|
3845
|
-
|
|
3883
|
+
base_title = 'Regressions by group'
|
|
3884
|
+
config = self._parse_regression_config(self.regression_by_group)
|
|
3885
|
+
reg_type_str = config.get('type', None)
|
|
3846
3886
|
else:
|
|
3847
|
-
|
|
3887
|
+
base_title = 'Regressions'
|
|
3888
|
+
if self.regression:
|
|
3889
|
+
config = self._parse_regression_config(self.regression)
|
|
3890
|
+
reg_type_str = config.get('type', None)
|
|
3891
|
+
|
|
3892
|
+
# Add regression type to title (e.g., "Regressions by color - Power")
|
|
3893
|
+
if reg_type_str:
|
|
3894
|
+
reg_type_display = reg_type_str.capitalize()
|
|
3895
|
+
regression_title = f"{base_title} - {reg_type_display}"
|
|
3896
|
+
else:
|
|
3897
|
+
regression_title = base_title
|
|
3848
3898
|
|
|
3849
3899
|
# Import legend from matplotlib
|
|
3850
3900
|
from matplotlib.legend import Legend
|
|
@@ -4284,11 +4334,15 @@ class Crossplot:
|
|
|
4284
4334
|
if self.y_log:
|
|
4285
4335
|
self.ax.set_yscale('log')
|
|
4286
4336
|
|
|
4287
|
-
# Disable scientific notation on axes
|
|
4337
|
+
# Disable scientific notation on linear axes only
|
|
4338
|
+
# (log axes use matplotlib's default log formatter for proper log scale labels)
|
|
4288
4339
|
from matplotlib.ticker import ScalarFormatter
|
|
4289
4340
|
formatter = ScalarFormatter(useOffset=False)
|
|
4290
4341
|
formatter.set_scientific(False)
|
|
4291
|
-
|
|
4342
|
+
|
|
4343
|
+
# Only apply to linear axes - log axes need their default formatter
|
|
4344
|
+
if not self.y_log:
|
|
4345
|
+
self.ax.yaxis.set_major_formatter(formatter)
|
|
4292
4346
|
if not self.x_log:
|
|
4293
4347
|
self.ax.xaxis.set_major_formatter(formatter)
|
|
4294
4348
|
|
|
@@ -7,9 +7,9 @@ well_log_toolkit/property.py,sha256=B-3mXNJmvIqjjMdsu1kgVSwMgEwbJ36wn_n_oppdJFw,
|
|
|
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=vnO8QjSNvvnQHGKXpe7BsLaQ0CMdLr0ruBt7poD-8Mc,199727
|
|
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.135.dist-info/METADATA,sha256=q39tCQUCWVP2XaMzNgtega97YSMCukEnIgSnodrCxfI,59810
|
|
13
|
+
well_log_toolkit-0.1.135.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
well_log_toolkit-0.1.135.dist-info/top_level.txt,sha256=BMOo7OKLcZEnjo0wOLMclwzwTbYKYh31I8RGDOGSBdE,17
|
|
15
|
+
well_log_toolkit-0.1.135.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|