well-log-toolkit 0.1.116__tar.gz → 0.1.117__tar.gz

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.
Files changed (20) hide show
  1. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/PKG-INFO +1 -1
  2. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/pyproject.toml +1 -1
  3. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/manager.py +12 -0
  4. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/visualization.py +151 -25
  5. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit.egg-info/PKG-INFO +1 -1
  6. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/README.md +0 -0
  7. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/setup.cfg +0 -0
  8. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/__init__.py +0 -0
  9. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/exceptions.py +0 -0
  10. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/las_file.py +0 -0
  11. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/operations.py +0 -0
  12. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/property.py +0 -0
  13. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/regression.py +0 -0
  14. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/statistics.py +0 -0
  15. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/utils.py +0 -0
  16. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit/well.py +0 -0
  17. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit.egg-info/SOURCES.txt +0 -0
  18. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit.egg-info/dependency_links.txt +0 -0
  19. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit.egg-info/requires.txt +0 -0
  20. {well_log_toolkit-0.1.116 → well_log_toolkit-0.1.117}/well_log_toolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.116
3
+ Version: 0.1.117
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "well-log-toolkit"
7
- version = "0.1.116"
7
+ version = "0.1.117"
8
8
  description = "Fast LAS file processing with lazy loading and filtering for well log analysis"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -2466,6 +2466,9 @@ class WellDataManager:
2466
2466
  depth_range: Optional[tuple[float, float]] = None,
2467
2467
  show_colorbar: bool = True,
2468
2468
  show_legend: bool = True,
2469
+ show_regression_legend: bool = True,
2470
+ show_regression_equation: bool = True,
2471
+ show_regression_r2: bool = True,
2469
2472
  regression: Optional[Union[str, dict]] = None,
2470
2473
  regression_by_color: Optional[Union[str, dict]] = None,
2471
2474
  regression_by_group: Optional[Union[str, dict]] = None,
@@ -2534,6 +2537,12 @@ class WellDataManager:
2534
2537
  Show colorbar when using color mapping. Default: True
2535
2538
  show_legend : bool, optional
2536
2539
  Show legend. Default: True
2540
+ show_regression_legend : bool, optional
2541
+ Show separate legend for regression lines in lower right corner. Default: True
2542
+ show_regression_equation : bool, optional
2543
+ Include regression equation in regression legend labels. Default: True
2544
+ show_regression_r2 : bool, optional
2545
+ Include R² value in regression legend labels. Default: True
2537
2546
  regression : str or dict, optional
2538
2547
  Regression type to apply to all data points. Can be a string (e.g., "linear") or
2539
2548
  dict with keys: type, line_color, line_width, line_style, line_alpha, x_range.
@@ -2624,6 +2633,9 @@ class WellDataManager:
2624
2633
  depth_range=depth_range,
2625
2634
  show_colorbar=show_colorbar,
2626
2635
  show_legend=show_legend,
2636
+ show_regression_legend=show_regression_legend,
2637
+ show_regression_equation=show_regression_equation,
2638
+ show_regression_r2=show_regression_r2,
2627
2639
  regression=regression,
2628
2640
  regression_by_color=regression_by_color,
2629
2641
  regression_by_group=regression_by_group,
@@ -2822,6 +2822,12 @@ class Crossplot:
2822
2822
  Show colorbar when using color mapping. Default: True
2823
2823
  show_legend : bool, optional
2824
2824
  Show legend when using shape/well mapping. Default: True
2825
+ show_regression_legend : bool, optional
2826
+ Show separate legend for regression lines in lower right. Default: True
2827
+ show_regression_equation : bool, optional
2828
+ Show equations in regression legend. Default: True
2829
+ show_regression_r2 : bool, optional
2830
+ Show R² values in regression legend. Default: True
2825
2831
  regression : str or dict, optional
2826
2832
  Regression type to apply to all data points. Can be a string (e.g., "linear") or
2827
2833
  dict with keys: type, line_color, line_width, line_style, line_alpha, x_range.
@@ -2912,9 +2918,12 @@ class Crossplot:
2912
2918
  depth_range: Optional[tuple[float, float]] = None,
2913
2919
  show_colorbar: bool = True,
2914
2920
  show_legend: bool = True,
2915
- regression: Optional[str] = None,
2916
- regression_by_color: Optional[str] = None,
2917
- regression_by_group: Optional[str] = None,
2921
+ show_regression_legend: bool = True,
2922
+ show_regression_equation: bool = True,
2923
+ show_regression_r2: bool = True,
2924
+ regression: Optional[Union[str, dict]] = None,
2925
+ regression_by_color: Optional[Union[str, dict]] = None,
2926
+ regression_by_group: Optional[Union[str, dict]] = None,
2918
2927
  ):
2919
2928
  # Store wells as list
2920
2929
  if not isinstance(wells, list):
@@ -2948,6 +2957,9 @@ class Crossplot:
2948
2957
  self.depth_range = depth_range
2949
2958
  self.show_colorbar = show_colorbar
2950
2959
  self.show_legend = show_legend
2960
+ self.show_regression_legend = show_regression_legend
2961
+ self.show_regression_equation = show_regression_equation
2962
+ self.show_regression_r2 = show_regression_r2
2951
2963
  self.regression = regression
2952
2964
  self.regression_by_color = regression_by_color
2953
2965
  self.regression_by_group = regression_by_group
@@ -2961,6 +2973,7 @@ class Crossplot:
2961
2973
  # Regression storage - nested structure: {type: {identifier: regression_obj}}
2962
2974
  self._regressions = {}
2963
2975
  self.regression_lines = {}
2976
+ self.regression_legend = None # Separate legend for regressions
2964
2977
 
2965
2978
  # Pending regressions (added before plot() is called)
2966
2979
  self._pending_regressions = []
@@ -3126,6 +3139,121 @@ class Crossplot:
3126
3139
  self._regressions[reg_type] = {}
3127
3140
  self._regressions[reg_type][identifier] = regression_obj
3128
3141
 
3142
+ def _format_regression_label(self, name: str, reg, include_equation: bool = None, include_r2: bool = None) -> str:
3143
+ """Format a modern, compact regression label.
3144
+
3145
+ Args:
3146
+ name: Name of the regression
3147
+ reg: Regression object
3148
+ include_equation: Whether to include equation (uses self.show_regression_equation if None)
3149
+ include_r2: Whether to include R² (uses self.show_regression_r2 if None)
3150
+
3151
+ Returns:
3152
+ Formatted label string
3153
+ """
3154
+ if include_equation is None:
3155
+ include_equation = self.show_regression_equation
3156
+ if include_r2 is None:
3157
+ include_r2 = self.show_regression_r2
3158
+
3159
+ # Start with name
3160
+ parts = [name]
3161
+
3162
+ # Add equation and R² on same line if both shown, more compact
3163
+ metrics = []
3164
+ if include_equation:
3165
+ eq = reg.equation()
3166
+ # Shorten equation format for compactness
3167
+ eq = eq.replace(' ', '') # Remove spaces
3168
+ metrics.append(eq)
3169
+
3170
+ if include_r2:
3171
+ # Use superscript 2 for R²
3172
+ metrics.append(f"R²={reg.r_squared:.3f}")
3173
+
3174
+ if metrics:
3175
+ # Join equation and R² with pipe separator for clarity
3176
+ parts.append(" | ".join(metrics))
3177
+
3178
+ return "\n".join(parts)
3179
+
3180
+ def _update_regression_legend(self) -> None:
3181
+ """Create or update the separate regression legend with smart placement."""
3182
+ if not self.show_regression_legend or not self.regression_lines:
3183
+ return
3184
+
3185
+ if self.ax is None:
3186
+ return
3187
+
3188
+ # Remove old regression legend if it exists
3189
+ if self.regression_legend is not None:
3190
+ self.regression_legend.remove()
3191
+ self.regression_legend = None
3192
+
3193
+ # Create new regression legend with only regression lines
3194
+ regression_handles = []
3195
+ regression_labels = []
3196
+
3197
+ for line in self.regression_lines.values():
3198
+ regression_handles.append(line)
3199
+ regression_labels.append(line.get_label())
3200
+
3201
+ if regression_handles:
3202
+ # Smart placement: try these locations in priority order
3203
+ # Prefer corners away from main legend and colorbar
3204
+ locations = [
3205
+ 'lower right', # Primary choice
3206
+ 'upper right', # If lower right conflicts with data
3207
+ 'lower left', # If right side has colorbar/main legend
3208
+ 'center right', # Fallback
3209
+ ]
3210
+
3211
+ # If main legend is shown, it's likely in upper left
3212
+ # If colorbar is shown, it's on the right side
3213
+ # Adjust preferences based on what's visible
3214
+ if self.show_legend and self.show_colorbar:
3215
+ # Both legend and colorbar present - lower left might be better
3216
+ locations = ['lower left', 'lower right', 'upper right', 'center left']
3217
+ elif self.show_colorbar:
3218
+ # Colorbar on right - prefer left side
3219
+ locations = ['lower left', 'upper left', 'lower right', 'center left']
3220
+
3221
+ # Try to create legend with best location
3222
+ # Use 'best' as fallback - matplotlib will find optimal position
3223
+ try:
3224
+ self.regression_legend = self.ax.legend(
3225
+ regression_handles,
3226
+ regression_labels,
3227
+ loc=locations[0], # Try first preference
3228
+ frameon=True,
3229
+ framealpha=0.95,
3230
+ edgecolor='#cccccc',
3231
+ fancybox=False,
3232
+ shadow=False,
3233
+ fontsize=9,
3234
+ title='Regressions',
3235
+ title_fontsize=10
3236
+ )
3237
+ except Exception:
3238
+ # Fallback to 'best' if specific location fails
3239
+ self.regression_legend = self.ax.legend(
3240
+ regression_handles,
3241
+ regression_labels,
3242
+ loc='best',
3243
+ frameon=True,
3244
+ framealpha=0.95,
3245
+ edgecolor='#cccccc',
3246
+ fancybox=False,
3247
+ shadow=False,
3248
+ fontsize=9,
3249
+ title='Regressions',
3250
+ title_fontsize=10
3251
+ )
3252
+
3253
+ # Modern styling
3254
+ self.regression_legend.get_frame().set_linewidth(0.8)
3255
+ self.regression_legend.get_title().set_fontweight('600')
3256
+
3129
3257
  def _add_automatic_regressions(self, data: pd.DataFrame) -> None:
3130
3258
  """Add automatic regressions based on initialization parameters."""
3131
3259
  if not any([self.regression, self.regression_by_color, self.regression_by_group]):
@@ -3313,8 +3441,8 @@ class Crossplot:
3313
3441
  warnings.warn(f"Could not generate plot data for {name} regression: {e}")
3314
3442
  return
3315
3443
 
3316
- # Create label with equation and R²
3317
- label = f"{name}\n{reg.equation()}\nR² = {reg.r_squared:.4f}"
3444
+ # Create label using formatter
3445
+ label = self._format_regression_label(name, reg)
3318
3446
 
3319
3447
  # Plot line with config parameters
3320
3448
  line = self.ax.plot(
@@ -3328,9 +3456,9 @@ class Crossplot:
3328
3456
 
3329
3457
  self.regression_lines[name] = line
3330
3458
 
3331
- # Update legend if requested (skipped during batch operations for performance)
3459
+ # Update regression legend if requested (skipped during batch operations for performance)
3332
3460
  if update_legend and self.ax is not None:
3333
- self.ax.legend(loc='best', frameon=True, framealpha=0.9, edgecolor='black')
3461
+ self._update_regression_legend()
3334
3462
 
3335
3463
  def plot(self) -> 'Crossplot':
3336
3464
  """Generate the crossplot figure."""
@@ -3394,13 +3522,12 @@ class Crossplot:
3394
3522
  warnings.warn(f"Could not generate plot data for {reg_type} regression: {e}")
3395
3523
  continue
3396
3524
 
3397
- # Create label
3398
- label_parts = [reg_name]
3399
- if pending['show_equation']:
3400
- label_parts.append(reg.equation())
3401
- if pending['show_r2']:
3402
- label_parts.append(f"R² = {reg.r_squared:.4f}")
3403
- label = "\n".join(label_parts)
3525
+ # Create label using formatter
3526
+ label = self._format_regression_label(
3527
+ reg_name, reg,
3528
+ include_equation=pending['show_equation'],
3529
+ include_r2=pending['show_r2']
3530
+ )
3404
3531
 
3405
3532
  # Plot line
3406
3533
  line = self.ax.plot(
@@ -3414,9 +3541,9 @@ class Crossplot:
3414
3541
 
3415
3542
  self.regression_lines[reg_name] = line
3416
3543
 
3417
- # Update legend once after all pending regressions
3544
+ # Update regression legend once after all pending regressions
3418
3545
  if self.ax is not None:
3419
- self.ax.legend(loc='best', frameon=True, framealpha=0.9, edgecolor='black')
3546
+ self._update_regression_legend()
3420
3547
 
3421
3548
  # Clear pending list
3422
3549
  self._pending_regressions = []
@@ -3650,13 +3777,12 @@ class Crossplot:
3650
3777
  warnings.warn(f"Could not generate plot data for {regression_type} regression: {e}")
3651
3778
  return self
3652
3779
 
3653
- # Create label
3654
- label_parts = [reg_name]
3655
- if show_equation:
3656
- label_parts.append(reg.equation())
3657
- if show_r2:
3658
- label_parts.append(f"R² = {reg.r_squared:.4f}")
3659
- label = "\n".join(label_parts)
3780
+ # Create label using formatter
3781
+ label = self._format_regression_label(
3782
+ reg_name, reg,
3783
+ include_equation=show_equation,
3784
+ include_r2=show_r2
3785
+ )
3660
3786
 
3661
3787
  # Plot line
3662
3788
  line = self.ax.plot(
@@ -3670,8 +3796,8 @@ class Crossplot:
3670
3796
 
3671
3797
  self.regression_lines[reg_name] = line
3672
3798
 
3673
- # Update legend
3674
- self.ax.legend(loc='best', frameon=True, framealpha=0.9, edgecolor='black')
3799
+ # Update regression legend
3800
+ self._update_regression_legend()
3675
3801
  else:
3676
3802
  # Store for later when plot() is called
3677
3803
  self._pending_regressions.append({
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.116
3
+ Version: 0.1.117
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT