Visualization

Template

class logsuite.Template(name='default', tracks=None)[source]

Bases: object

Template for well log display configuration.

A template defines the layout and styling of tracks in a well log display. Each track can contain multiple logs, fills, and tops markers.

Parameters:
  • name (str, optional) – Template name for identification.

  • tracks (list[dict], optional) – List of track definitions. If None, creates empty template.

name

Template name.

Type:

str

tracks

List of track configurations.

Type:

list[dict]

Examples

>>> # Create empty template
>>> template = Template("reservoir")
>>>
>>> # Add a GR track
>>> template.add_track(
...     track_type="continuous",
...     logs=[{"name": "GR", "x_range": [0, 150], "color": "green"}]
... )
>>>
>>> # Add a porosity/saturation track with fill
>>> template.add_track(
...     track_type="continuous",
...     logs=[
...         {"name": "PHIE", "x_range": [0.45, 0], "color": "blue"},
...         {"name": "SW", "x_range": [0, 1], "color": "red"}
...     ],
...     fill={
...         "left": {"curve": "PHIE"},
...         "right": {"value": 0},
...         "color": "lightblue",
...         "alpha": 0.5
...     }
... )
>>>
>>> # Add depth track
>>> template.add_track(track_type="depth")
>>>
>>> # Save to file
>>> template.save("reservoir_template.json")
>>>
>>> # Load from file
>>> template2 = Template.load("reservoir_template.json")
add_track(track_type='continuous', logs=None, fill=None, tops=None, width=1.0, title=None, log_scale=False)[source]

Add a track to the template.

Parameters:
  • track_type ({"continuous", "discrete", "depth"}, default "continuous") – Type of track: - “continuous”: Continuous log curves (GR, RHOB, etc.) - “discrete”: Discrete/categorical logs (facies, zones) - “depth”: Depth axis track

  • logs (list[dict], optional) –

    List of log configurations. Each dict can contain:

    • name (str): Property name

    • x_range (list[float, float]): Min and max x-axis values [left, right]

    • scale (str): Optional override for this log’s scale (“log” or “linear”). If not specified, uses the track’s log_scale setting.

    • color (str): Line color

    • style (str): Line style - supports both matplotlib codes and friendly names. Matplotlib: “-” (solid), “–” (dashed), “-.” (dashdot), “:” (dotted), “none” (no line). Friendly: “solid”, “dashed”, “dashdot”, “dotted”, “none”. Use “none” to show only markers without a connecting line.

    • thickness (float): Line width

    • alpha (float): Transparency (0-1)

    • marker (str): Marker style for data points (disabled by default). Supports Matplotlib codes: “o”, “s”, “D”, “^”, “v”, “<”, “>”, “+”, “x”, “*”, “p”, “h”, “.”, “,”, “|”, “_”. Friendly names: “circle”, “square”, “diamond”, “triangle_up”, “triangle_down”, “triangle_left”, “triangle_right”, “plus”, “cross”, “star”, “pentagon”, “hexagon”, “point”, “pixel”, “vline”, “hline”.

    • marker_size (float): Size of markers (default: 6)

    • marker_outline_color (str): Marker edge color (defaults to ‘color’)

    • marker_fill (str): Marker fill color (optional). If not specified, markers are unfilled.

    • marker_interval (int): Show every nth marker (default: 1, shows all markers)

  • fill (Union[dict, list[dict]], optional) –

    Fill configuration or list of fill configurations. Each fill dict can contain:

    • left: Left boundary (string/number or dict). Simple: “track_edge”, “CurveName”, or numeric value. Dict: {"curve": name}, {"value": float}, or {"track_edge": "left"}.

    • right: Right boundary (same format as left)

    • color (str): Fill color name (for solid fills)

    • colormap (str): Matplotlib colormap name (e.g., “viridis”, “inferno”). Creates horizontal bands where depth intervals are colored based on curve values.

    • colormap_curve (str): Curve name to use for colormap values (defaults to left boundary curve)

    • color_range (list): [min, max] values for colormap normalization

    • alpha (float): Transparency (0-1)

    Multiple fills are drawn in order (first fill is drawn first, then subsequent fills on top).

  • tops (dict, optional) –

    Formation tops configuration with keys:

    • name (str): Property name containing tops

    • line_style (str): Line style for markers

    • line_width (float): Line thickness

    • dotted (bool): Use dotted lines

    • title_size (int): Font size for labels

    • title_weight (str): Font weight (“normal”, “bold”)

    • title_orientation (str): Text alignment (“left”, “center”, “right”)

    • line_offset (float): Horizontal offset for line position

  • width (float, default 1.0) – Relative width of track (used for layout proportions)

  • title (str, optional) – Track title to display at top

  • log_scale (bool, default False) – Use logarithmic scale for the entire track. Individual logs can override this with the “scale” parameter (“log” or “linear”)

Returns:

Self for method chaining

Return type:

Template

Examples

>>> template = Template("my_template")
>>>
>>> # Add GR track
>>> template.add_track(
...     track_type="continuous",
...     logs=[{
...         "name": "GR",
...         "x_range": [0, 150],
...         "color": "green",
...         "style": "solid",  # or "-", both work
...         "thickness": 1.0
...     }],
...     title="Gamma Ray"
... )
>>>
>>> # Add resistivity track with log scale
>>> template.add_track(
...     track_type="continuous",
...     logs=[{
...         "name": "RES",
...         "x_range": [0.2, 2000],
...         "color": "red"
...     }],
...     title="Resistivity",
...     log_scale=True  # Apply log scale to entire track
... )
>>>
>>> # Add track with mixed scales (one log overrides track setting)
>>> template.add_track(
...     track_type="continuous",
...     logs=[
...         {"name": "ILD", "x_range": [0.2, 2000], "color": "red"},  # Uses track log_scale
...         {"name": "GR", "x_range": [0, 150], "scale": "linear", "color": "green"}  # Override to linear
...     ],
...     log_scale=True  # Default for track is log scale
... )
>>>
>>> # Add track with different line styles
>>> template.add_track(
...     track_type="continuous",
...     logs=[
...         {"name": "RHOB", "x_range": [1.95, 2.95], "color": "red", "style": "solid", "thickness": 1.5},
...         {"name": "NPHI", "x_range": [0.45, -0.15], "color": "blue", "style": "dashed", "thickness": 2.0}
...     ],
...     title="Density & Neutron"
... )
>>>
>>> # Add track with markers (line + markers)
>>> template.add_track(
...     track_type="continuous",
...     logs=[{
...         "name": "PERM",
...         "x_range": [0.1, 1000],
...         "color": "green",
...         "style": "solid",
...         "marker": "circle",
...         "marker_size": 4,
...         "marker_fill": "lightgreen"
...     }],
...     title="Permeability",
...     log_scale=True
... )
>>>
>>> # Add track with markers only (no line)
>>> template.add_track(
...     track_type="continuous",
...     logs=[{
...         "name": "SAMPLE_POINTS",
...         "x_range": [0, 100],
...         "color": "red",
...         "style": "none",
...         "marker": "diamond",
...         "marker_size": 8,
...         "marker_outline_color": "darkred",
...         "marker_fill": "yellow"
...     }],
...     title="Sample Locations"
... )
>>>
>>> # Add porosity track with fill (simplified API)
>>> template.add_track(
...     track_type="continuous",
...     logs=[{
...         "name": "PHIE",
...         "x_range": [0.45, 0],
...         "color": "blue"
...     }],
...     fill={
...         "left": "PHIE",      # Simple: curve name
...         "right": 0,          # Simple: numeric value
...         "color": "lightblue",
...         "alpha": 0.5
...     },
...     title="Porosity"
... )
>>>
>>> # Add GR track with colormap fill (horizontal bands colored by GR value)
>>> template.add_track(
...     track_type="continuous",
...     logs=[{"name": "GR", "x_range": [0, 150], "color": "black"}],
...     fill={
...         "left": "track_edge",  # Simple: track edge
...         "right": "GR",         # Simple: curve name
...         "colormap": "viridis",
...         "color_range": [20, 150],
...         "alpha": 0.7
...     },
...     title="Gamma Ray"
... )
>>>
>>> # Add porosity/saturation track with multiple fills
>>> template.add_track(
...     track_type="continuous",
...     logs=[
...         {"name": "PHIE", "x_range": [0.45, 0], "color": "blue"},
...         {"name": "SW", "x_range": [0, 1], "color": "red"}
...     ],
...     fill=[
...         # Fill 1: PHIE to zero with light blue
...         {
...             "left": "PHIE",
...             "right": 0,
...             "color": "lightblue",
...             "alpha": 0.3
...         },
...         # Fill 2: SW to one with light red
...         {
...             "left": "SW",
...             "right": 1,
...             "color": "lightcoral",
...             "alpha": 0.3
...         }
...     ],
...     title="PHIE & SW"
... )
>>>
>>> # Add discrete facies track
>>> template.add_track(
...     track_type="discrete",
...     logs=[{"name": "Facies"}],
...     title="Facies"
... )
>>>
>>> # Add depth track
>>> template.add_track(track_type="depth", width=0.3)
remove_track(index)[source]

Remove track at specified index.

Parameters:

index (int) – Track index to remove

Returns:

Self for method chaining

Return type:

Template

Examples

>>> template.remove_track(0)  # Remove first track
add_tops(property_name=None, tops_dict=None, colors=None, styles=None, thicknesses=None)[source]

Add well tops configuration to the template.

Tops added to the template will be displayed in all WellViews created from this template. They span across all tracks (except depth track).

Parameters:
  • property_name (str, optional) – Name of discrete property in well containing tops data. The property name will be resolved when the template is used with a well.

  • tops_dict (dict[float, str], optional) – Dictionary mapping depth values to formation names. Example: {2850.0: ‘Formation A’, 2920.5: ‘Formation B’}

  • colors (dict[float, str], optional) – Optional color mapping for each depth or discrete value. - For tops_dict: keys are depths matching tops_dict keys - For property_name: keys are discrete values (integers) If not provided and using a discrete property with color mapping, those colors will be used.

  • styles (dict[float, str], optional) – Optional line style mapping for each depth or discrete value. Valid styles: ‘solid’, ‘dashed’, ‘dotted’, ‘dashdot’ - For tops_dict: keys are depths matching tops_dict keys - For property_name: keys are discrete values (integers) If not provided and using a discrete property with style mapping, those styles will be used.

  • thicknesses (dict[float, float], optional) – Optional line thickness mapping for each depth or discrete value. - For tops_dict: keys are depths matching tops_dict keys - For property_name: keys are discrete values (integers) If not provided and using a discrete property with thickness mapping, those thicknesses will be used.

Returns:

Self for method chaining

Return type:

Template

Examples

>>> # Add tops from discrete property (resolved when used with well)
>>> template = Template("my_template")
>>> template.add_track(...)
>>> template.add_tops(property_name="Formations")
>>>
>>> # Add manual tops with colors
>>> template.add_tops(
...     tops_dict={2850.0: 'Reservoir', 2920.5: 'Seal'},
...     colors={2850.0: 'yellow', 2920.5: 'gray'}
... )
>>>
>>> # Add tops from discrete property with color overrides
>>> template.add_tops(
...     property_name='Zone',
...     colors={0: 'red', 1: 'green', 2: 'blue'}  # Map discrete values
... )

Notes

Tops are drawn as horizontal lines spanning all tracks (except depth track). Formation names are displayed at the right end of each line, floating above it.

edit_track(index, **kwargs)[source]

Edit track at specified index.

Parameters:
  • index (int) – Track index to edit

  • **kwargs – Track parameters to update (same as add_track)

Returns:

Self for method chaining

Return type:

Template

Examples

>>> # Change track title
>>> template.edit_track(0, title="New Title")
>>>
>>> # Update log styling
>>> template.edit_track(1, logs=[{"name": "PHIE", "color": "red"}])
get_track(index)[source]

Get track configuration at specified index.

Parameters:

index (int) – Track index

Returns:

Track configuration

Return type:

dict

Examples

>>> track_config = template.get_track(0)
>>> print(track_config["type"])
'continuous'
list_tracks()[source]

List all tracks with summary information.

Returns:

DataFrame with columns: Index, Type, Logs, Title, Width

Return type:

pd.DataFrame

Examples

>>> template.list_tracks()
   Index       Type           Logs      Title  Width
0      0 continuous          [GR]  Gamma Ray    1.0
1      1 continuous  [PHIE, SW]   Porosity    1.0
2      2      depth            []      Depth    0.3
save(filepath)[source]

Save template to JSON file.

Parameters:

filepath (Union[str, Path]) – Path to save the JSON file.

Return type:

None

Examples

>>> template.save("reservoir_template.json")
classmethod load(filepath)[source]

Load template from JSON file.

Parameters:

filepath (Union[str, Path]) – Path to JSON file

Returns:

Loaded template

Return type:

Template

Examples

>>> template = Template.load("reservoir_template.json")
to_dict()[source]

Export template as dictionary.

Returns:

Template configuration

Return type:

dict

Examples

>>> config = template.to_dict()
>>> print(config.keys())
dict_keys(['name', 'tracks', 'tops'])
classmethod from_dict(data)[source]

Create template from dictionary.

Parameters:

data (dict) – Template configuration dictionary

Returns:

New template instance

Return type:

Template

Examples

>>> config = {"name": "test", "tracks": [...], "tops": [...]}
>>> template = Template.from_dict(config)

WellView

class logsuite.WellView(well, depth_range=None, tops=None, template=None, figsize=None, dpi=100, header_config=None)[source]

Bases: object

Interactive well log display for Jupyter Lab.

Creates matplotlib-based well log plots with multiple tracks showing continuous logs, discrete properties, fills, and formation tops.

Parameters:
  • well (Well) – Well object containing log data

  • depth_range (tuple[float, float], optional) – Depth interval to display [start_depth, end_depth]. If None, shows full depth range.

  • template (Union[Template, dict, str], optional) – Display template. Can be: - Template object - Dictionary with template configuration - String name of template stored in well’s parent manager If None, creates a simple default view.

  • figsize (tuple[float, float], optional) – Figure size (width, height) in inches. If None, calculated from number of tracks.

  • dpi (int, default 100) – Figure resolution

  • Attributes (Class)

  • ----------------

  • HEADER_BOX_TOP (float) – Fixed top position of header boxes for alignment across tracks

  • HEADER_TITLE_SPACING (float) – Vertical space between log name and its scale line in continuous tracks

  • HEADER_LOG_SPACING (float) – Vertical space allocated per log in continuous tracks

  • HEADER_TOP_PADDING (float) – Padding inside header box above content

  • HEADER_BOTTOM_PADDING (float) – Padding inside header box below content

  • tops (list[str] | None)

  • header_config (dict | None)

well

Source well object

Type:

Well

depth_range

Displayed depth range

Type:

tuple[float, float]

template

Display template configuration

Type:

Template

fig

Matplotlib figure object

Type:

matplotlib.figure.Figure

axes

List of axes for each track

Type:

list[matplotlib.axes.Axes]

Examples

>>> from logsuite import WellDataManager
>>> from logsuite.visualization import WellView, Template
>>>
>>> # Load data
>>> manager = WellDataManager()
>>> manager.load_las("well.las")
>>> well = manager.well_36_7_5_A
>>>
>>> # Create template
>>> template = Template("basic")
>>> template.add_track(
...     track_type="continuous",
...     logs=[{"name": "GR", "x_range": [0, 150], "color": "green"}],
...     title="Gamma Ray"
... )
>>> template.add_track(track_type="depth")
>>>
>>> # Display well log with depth range
>>> view = WellView(well, depth_range=[2800, 3000], template=template)
>>> view.show()
>>>
>>> # Or auto-calculate from formation tops
>>> template.add_tops(property_name='Zone')
>>> view2 = WellView(well, tops=['Top_Brent', 'Top_Statfjord'], template=template)
>>> view2.show()
>>>
>>> # Or use template from manager
>>> manager.set_template("reservoir", template)
>>> view3 = WellView(well, depth_range=[3000, 3200], template="reservoir")
>>> view3.show()
>>>
>>> # Save figure
>>> view.save("well_log.png", dpi=300)
HEADER_BOX_TOP = 1.1
HEADER_TITLE_SPACING = 0.0015
HEADER_LOG_SPACING = 0.025
HEADER_TOP_PADDING = 0.01
HEADER_BOTTOM_PADDING = 0.01
add_track(track_type='continuous', logs=None, fill=None, width=1.0, title=None, log_scale=False)[source]

Add a temporary track to this view (not saved to template).

This allows adding tracks to a specific view without modifying the underlying template. Temporary tracks are appended after template tracks.

Parameters:
  • track_type ({"continuous", "discrete", "depth"}, default "continuous") – Type of track

  • logs (list[dict], optional) – List of log configurations (same format as Template.add_track)

  • fill (Union[dict, list[dict]], optional) – Fill configuration or list of fills

  • width (float, default 1.0) – Relative width of track

  • title (str, optional) – Track title

  • log_scale (bool, default False) – Use logarithmic scale for the track

Returns:

Self for method chaining

Return type:

WellView

Examples

>>> # Create view with template, then add temporary track
>>> view = WellView(well, template=template)
>>> view.add_track(
...     track_type="continuous",
...     logs=[{"name": "TEMP_LOG", "x_range": [0, 100], "color": "orange"}],
...     title="Temporary"
... )
>>> view.show()

Notes

Temporary tracks are not saved to the template and only exist for this view. If you want to reuse tracks across multiple views, add them to the template instead.

add_tops(property_name=None, tops_dict=None, colors=None, styles=None, thicknesses=None, source=None)[source]

Add temporary well tops to this view (not saved to template).

Tops can be specified either from a discrete property in the well or as a dictionary mapping depths to formation names.

Parameters:
  • property_name (str, optional) – Name of discrete property in well containing tops data. The property should have depth values where formations start.

  • tops_dict (dict[float, str], optional) – Dictionary mapping depth values to formation names. Example: {2850.0: ‘Formation A’, 2920.5: ‘Formation B’}

  • colors (dict[float, str], optional) – Optional color mapping for each depth. If provided, must have same keys as tops_dict or match values in the discrete property. Colors can be matplotlib color names, hex codes, or RGB tuples. If not provided and using a discrete property with color mapping, those colors will be used.

  • styles (dict[float, str], optional) – Optional line style mapping for each depth or discrete value. Valid styles: ‘solid’, ‘dashed’, ‘dotted’, ‘dashdot’ If not provided and using a discrete property with style mapping, those styles will be used.

  • thicknesses (dict[float, float], optional) – Optional line thickness mapping for each depth or discrete value. If not provided and using a discrete property with thickness mapping, those thicknesses will be used.

  • source (str, optional) – Source name to get property from (if property_name is specified). Only needed if property exists in multiple sources.

Returns:

Self for method chaining

Return type:

WellView

Examples

>>> # Add tops from discrete property
>>> view = WellView(well, template=template)
>>> view.add_tops(property_name='Zone')
>>> view.show()
>>>
>>> # Add tops manually with custom colors
>>> view.add_tops(
...     tops_dict={2850.0: 'Reservoir', 2920.5: 'Seal'},
...     colors={2850.0: 'yellow', 2920.5: 'gray'}
... )
>>>
>>> # Add tops from discrete property, overriding colors
>>> view.add_tops(
...     property_name='Formation',
...     colors={0: 'red', 1: 'green', 2: 'blue'}  # Map discrete values to colors
... )

Notes

Tops are drawn as horizontal lines spanning all tracks (except depth track). Formation names are displayed at the right end of each line, floating above it. Temporary tops are not saved to the template.

plot()[source]

Create the well log plot.

This method generates the matplotlib figure with all configured tracks. Call show() or save() after this to display or save the figure.

Examples

>>> view = WellView(well, template=template)
>>> view.plot()
>>> view.show()
Return type:

None

show()[source]

Display the well log plot in Jupyter notebook.

This will render the plot inline in Jupyter Lab/Notebook.

Examples

>>> view = WellView(well, template=template)
>>> view.show()
Return type:

None

save(filepath, dpi=None, bbox_inches='tight')[source]

Save the well log plot to file.

Parameters:
  • filepath (Union[str, Path]) – Output file path (format determined by extension: .png, .pdf, .svg, etc.).

  • dpi (int, optional) – Resolution for raster formats. If None, uses the figure’s dpi.

  • bbox_inches (str, default 'tight') – Bounding box specification for the saved figure.

Return type:

None

Examples

>>> view = WellView(well, template=template)
>>> view.save("well_log.png", dpi=300)
>>> view.save("well_log.pdf")
close()[source]

Close the matplotlib figure and free memory.

Examples

>>> view = WellView(well, template=template)
>>> view.show()
>>> view.close()
Return type:

None

Crossplot

class logsuite.Crossplot(wells, x=None, y=None, layers=None, shape=None, color=None, size=None, colortemplate='viridis', color_range=None, size_range=(20, 200), title='Cross Plot', xlabel=None, ylabel=None, figsize=(10, 8), dpi=100, marker='o', marker_size=50, marker_alpha=0.7, edge_color='black', edge_width=0.5, x_log=False, y_log=False, grid=True, grid_alpha=0.7, depth_range=None, show_colorbar=True, show_legend=True, show_regression_legend=True, show_regression_equation=True, show_regression_r2=True, equation_format='natural', decimals=4, regression=None, regression_by_color=None, regression_by_group=None, regression_by_color_and_shape=None, regression_by_shape_and_color=None)[source]

Bases: object

Create beautiful, modern crossplots for well log analysis.

Supports single and multi-well crossplots with extensive customization options including color mapping, size mapping, shape mapping, regression analysis, and multi-layer plotting for combining different data types (e.g., Core vs Sidewall).

Parameters:
  • wells (Well or list of Well) – Single well or list of wells to plot

  • x (str, optional) – Name of property for x-axis. Required if layers is not provided.

  • y (str, optional) – Name of property for y-axis. Required if layers is not provided.

  • layers (dict[str, list[str]], optional) – Dictionary mapping layer labels to [x_property, y_property] lists. Use this to combine multiple property pairs in a single plot. Example: {“Core”: [“CorePor”, “CorePerm”], “Sidewall”: [“SWPor”, “SWPerm”]} When using layers, shape defaults to “label” and color defaults to “well” for easy visualization of both layer types and wells. Default: None

  • shape (str, optional) – Property name for shape mapping. Use “well” to map shapes by well name, or “label” (when using layers) to map shapes by layer type. Default: “well” for multi-well plots, “label” when layers provided, None otherwise

  • color (str, optional) – Property name for color mapping. Use “depth” to color by depth, “well” to color by well, or “label” (when using layers) to color by layer type. Default: “well” when layers provided, None otherwise

  • size (str, optional) – Property name for size mapping, or “label” (when using layers) to size by layer type. Default: None (constant size)

  • colortemplate (str, optional) – Matplotlib colormap name (e.g., “viridis”, “plasma”, “coolwarm”) Default: “viridis”

  • color_range (tuple[float, float], optional) – Min and max values for color mapping. If None, uses data range. Default: None

  • size_range (tuple[float, float], optional) – Min and max marker sizes for size mapping. Default: (20, 200)

  • title (str, optional) – Plot title. Default: “Cross Plot”

  • xlabel (str, optional) – X-axis label. If None, uses property name.

  • ylabel (str, optional) – Y-axis label. If None, uses property name.

  • figsize (tuple[float, float], optional) – Figure size (width, height) in inches. Default: (10, 8)

  • dpi (int, optional) – Figure resolution. Default: 100

  • marker (str, optional) – Marker style. Default: “o”

  • marker_size (float, optional) – Base marker size. Default: 50

  • marker_alpha (float, optional) – Marker transparency (0-1). Default: 0.7

  • edge_color (str, optional) – Marker edge color. Default: “black”

  • edge_width (float, optional) – Marker edge width. Default: 0.5

  • x_log (bool, optional) – Use logarithmic scale for x-axis. Default: False

  • y_log (bool, optional) – Use logarithmic scale for y-axis. Default: False

  • grid (bool, optional) – Show grid. Default: True

  • grid_alpha (float, optional) – Grid transparency. Default: 0.3

  • depth_range (tuple[float, float], optional) – Depth range to filter data. Default: None (all depths)

  • show_colorbar (bool, optional) – Show colorbar when using color mapping. Default: True

  • show_legend (bool, optional) – Show legend when using shape/well mapping. Default: True

  • show_regression_legend (bool, optional) – Show separate legend for regression lines in lower right. Default: True

  • show_regression_equation (bool, optional) – Show equations in regression legend. Default: True

  • show_regression_r2 (bool, optional) – Show R-squared values in regression legend. Default: True

  • regression (str or dict, optional) – Regression type to apply to all data points. Can be a string (e.g., “linear”) or dict with keys: type, line_color, line_width, line_style, line_alpha, x_range. Default: None

  • regression_by_color (str or dict, optional) – Regression type to apply separately for each color group in the plot. Creates separate regression lines based on what determines colors in the visualization: explicit color mapping if specified, otherwise shape groups (e.g., wells when shape=’well’). Accepts string or dict format. Default: None

  • regression_by_group (str or dict, optional) – Regression type to apply separately for each group (well or shape). Creates separate regression lines for each well or shape category. Accepts string or dict. Default: None

  • regression_by_color_and_shape (str or dict, optional) – Regression type to apply separately for each combination of color AND shape groups. Creates separate regression lines for each (color, shape) combination. This is useful for analyzing how the relationship changes across both dimensions simultaneously (e.g., each well in each formation, each layer in each zone). Accepts string or dict. Default: None

  • regression_by_shape_and_color (str or dict, optional) – Alias for regression_by_color_and_shape. Provided for convenience — both parameters do exactly the same thing. Use whichever order feels more natural. Defaults to None.

  • equation_format (str)

  • decimals (int)

Examples

Basic crossplot from a single well:

>>> plot = well.Crossplot(x="RHOB", y="NPHI")
>>> plot.show()

Multi-well crossplot with color and size mapping:

>>> plot = manager.Crossplot(
...     x="PHIE",
...     y="SW",
...     color="depth",
...     size="PERM",
...     shape="well",
...     colortemplate="viridis"
... )
>>> plot.show()

With regression analysis (string format):

>>> plot = well.Crossplot(x="RHOB", y="NPHI", regression="linear")
>>> plot.show()

With regression analysis (dict format for custom styling):

>>> plot = well.Crossplot(
...     x="RHOB", y="NPHI",
...     regression={"type": "linear", "line_color": "red", "line_width": 3}
... )
>>> plot.show()

Multi-well with group-specific regressions:

>>> plot = manager.Crossplot(
...     x="PHIE", y="SW",
...     shape="well",
...     regression_by_group={"type": "linear", "line_style": "--"}
... )
>>> plot.show()

Combining multiple data types with layers (Core + Sidewall):

>>> plot = manager.Crossplot(
...     layers={
...         "Core": ["CorePor_obds", "CorePerm_obds"],
...         "Sidewall": ["SidewallPor_ob", "SidewallPerm_ob"]
...     },
...     y_log=True
...     # shape defaults to "label" - different shapes for Core vs Sidewall
...     # color defaults to "well" - different colors for each well
... )
>>> plot.show()

Using add_layer method with method chaining:

>>> manager.Crossplot(y_log=True) \
...     .add_layer("CorePor_obds", "CorePerm_obds", label="Core") \
...     .add_layer("SidewallPor_ob", "SidewallPerm_ob", label="Sidewall") \
...     .show()
...     # Automatically uses shape="label" and color="well"

Layers with regression by color (single trend per well):

>>> plot = manager.Crossplot(
...     layers={
...         "Core": ["CorePor_obds", "CorePerm_obds"],
...         "Sidewall": ["SidewallPor_ob", "SidewallPerm_ob"]
...     },
...     regression_by_color="linear"  # One trend per well (combining both data types)
...     # Defaults: shape="label" (different shapes), color="well" (different colors)
... )
>>> plot.show()

Access regression objects:

>>> linear_regs = plot.regression("linear")
>>> for name, reg in linear_regs.items():
...     print(f"{name}: {reg.equation()}, R²={reg.r_squared:.3f}")
add_layer(x, y, label)[source]

Add a new data layer to the crossplot.

This allows combining multiple property pairs in a single plot, useful for comparing different data types (e.g., Core vs Sidewall data).

Parameters:
  • x (str) – Name of property for x-axis for this layer

  • y (str) – Name of property for y-axis for this layer

  • label (str) – Label for this layer (used in legend and available as “label” property for color/shape mapping)

Returns:

Returns self to allow method chaining

Return type:

self

Examples

>>> plot = manager.Crossplot(y_log=True)
>>> plot.add_layer('CorePor_obds', 'CorePerm_obds', label='Core')
>>> plot.add_layer('SidewallPor_ob', 'SidewallPerm_ob', label='Sidewall')
>>> plot.show()

With method chaining: >>> manager.Crossplot(y_log=True) … .add_layer(‘CorePor_obds’, ‘CorePerm_obds’, label=’Core’) … .add_layer(‘SidewallPor_ob’, ‘SidewallPerm_ob’, label=’Sidewall’) … .show()

regression(regression_type=None)[source]

Access regression objects.

Args:

regression_type: Optional regression type to filter by (e.g., “linear”, “polynomial”)

Returns:
If regression_type is None: Returns all regressions organized by type:

{“linear”: {“red”: RegObj, …}, “polynomial”: {“blue”: RegObj, …}}

If regression_type specified: Returns regressions of that type:

{“red”: RegObj, …}

Examples:
>>> plot.regression()  # Get all regressions
>>> plot.regression("linear")  # Get only linear regressions
Parameters:

regression_type (str | None)

Return type:

dict

add_regression_per(group_property, regression_type, min_samples=5, **kwargs)[source]

Fit one regression per unique value of a group property.

Convenience wrapper around add_regression() that enumerates the unique values of group_property (from the prepared crossplot data) and calls add_regression once per value with the appropriate where= and name= set automatically. Each resulting line picks up its color from Property.colors via _resolve_line_color_from_where() when the palette is set, otherwise defaults apply.

Parameters:
  • group_property (str) – Public column name (e.g. the property bound to color or shape on this Crossplot). Must resolve to a column on the prepared data.

  • regression_type (str) – Forwarded to add_regression() ("linear", "exponential", …).

  • min_samples (int, default 5) – Forwarded to add_regression(). Subsets smaller than this are skipped with a warning.

  • **kwargs – Other arguments forwarded to add_regression(). where= and name= are filled in by this method; passing them explicitly raises TypeError.

Returns:

Self for method chaining.

Return type:

Crossplot

Examples

>>> xplot = Crossplot(manager, x="PHIE", y="PERM", color="Facies")
>>> xplot.add_regression_per("Facies", "exponential", equation_format="petrel")
# Three regressions added, one per facies, each colored from
# manager.Facies.colors when set.
column_for(public_name)[source]

Return the prepared-data column name for a public property reference.

Useful when writing a callable for add_regression(where=callable) that needs to inspect the prepared crossplot DataFrame: instead of hardcoding "color_val", you can write df[xplot.column_for("Facies")] == 2.0 and stay independent of which property is bound to which role.

Parameters:

public_name (str) – A property name bound to x, y, color, shape, or size on this Crossplot, or one of the literal internal column names ("x", "y", "color_val", "shape_val", "size_val", "well").

Returns:

The internal column name, or None if public_name does not match any binding.

Return type:

str or None

Examples

>>> xplot = Crossplot(manager, x="PHIE", y="PERM", color="Facies")
>>> xplot.column_for("Facies")
'color_val'
>>> xplot.column_for("PHIE")
'x'
plot()[source]

Generate the crossplot figure.

Return type:

Crossplot

add_regression(regression_type, name=None, line_color=None, line_width=2, line_style='-', line_alpha=0.8, show_equation=True, show_r2=True, x_range=None, where=None, min_samples=5, decimals=None, legend_decimals=None, equation_format=None, legend_loc=None, **kwargs)[source]

Add a regression line to the crossplot.

Parameters:
  • regression_type (str) – Type of regression: “linear”, “logarithmic”, “exponential”, “polynomial”, or “power”

  • name (str, optional) – Name for this regression. If None, uses regression_type.

  • line_color (str, optional) – Color of regression line. Default: ‘red’

  • line_width (float, optional) – Width of regression line. Default: 2

  • line_style (str, optional) – Style of regression line. Default: ‘-’

  • line_alpha (float, optional) – Transparency of regression line. Default: 0.8

  • show_equation (bool, optional) – Show equation in legend. Default: True

  • show_r2 (bool, optional) – Show R-squared value in legend. Default: True

  • x_range (tuple[float, float], optional) – Custom x-axis range for plotting the regression line. If None, uses the data range from fitting.

  • where (dict or callable, optional) –

    Restrict the fit to a subset of points. Two forms:

    • dict: {column: allowed_value(s)}. Keys may be public property names (the same as x, y, color, shape, size on the Crossplot) or the literal column name "well". Values are scalars or lists. Example: where={"Facies": [5, 6]}.

    • callable: a function f(df) -> boolean mask over the prepared crossplot data. Example: where=lambda df: df["color_val"].isin([5, 6]).

  • min_samples (int, default 5) – Minimum number of points required after filtering. If the filtered subset has fewer rows, a warning is emitted and the regression is skipped (no exception).

  • decimals (int, optional) – Decimal precision for the equation in the legend. None falls back to the Crossplot constructor’s decimals= (4 by default).

  • legend_decimals (int, optional) – Alias for decimals. If both are supplied, legend_decimals wins. Provided for API symmetry with legend_loc.

  • equation_format ({"natural", "log10", "petrel"}, optional) – Equation form rendered in the legend. None (default) falls back to the Crossplot constructor’s equation_format=. log10 and petrel are meaningful for exponential fits — Petrel form yields pow(10, c1*x + c0), suitable for direct paste into Petrel calculators. Other models fall back to natural.

  • legend_loc (str or tuple, optional) – Matplotlib loc for the regression legend (e.g. "upper left", (0.65, 0.05)). When supplied, overrides the auto-placement algorithm. The latest legend_loc= value across all add_regression calls wins, since the regression legend is rebuilt as one block.

  • **kwargs – Additional arguments for regression (e.g., degree for polynomial)

Returns:

Self for method chaining

Return type:

Crossplot

Examples

>>> plot = well.Crossplot(x="RHOB", y="NPHI")
>>> plot.add_regression("linear")
>>> plot.add_regression("polynomial", degree=2, line_color="blue")
>>> plot.add_regression("linear", x_range=(0, 10))  # Custom range
>>> plot.show()
remove_regression(name, regression_type=None)[source]

Remove a regression from the plot.

Parameters:
  • name (str) – Name of regression to remove

  • regression_type (str, optional) – Type of regression. If None, searches all types for the name.

Returns:

Self for method chaining

Return type:

Crossplot

add(artifact)[source]

Add an Artifact to the crossplot.

The artifact’s _render_in_crossplot(ax) method is called with this crossplot’s matplotlib axis. Artifacts that do not support Crossplot rendering raise TypeError. The plot is rendered first if it has not been already.

Parameters:

artifact (Artifact) – Any object implementing _render_in_crossplot(ax).

Returns:

Self for method chaining.

Return type:

Crossplot

Examples

>>> from logsuite import ExponentialRegression, RegressionFit
>>> reg = ExponentialRegression().fit(df["PHIE"], df["PERM"])
>>> fit = RegressionFit(reg, name="all wells", equation_format="petrel")
>>> xplot = Crossplot(...).add(fit)
add_table_panel(df, position='bottom', title=None, formatters=None, table_fraction=0.3)[source]

Attach a DataFrame as a rendered table panel to this Crossplot.

Grows the figure along the panel’s axis so the scatter is not squished, then renders the DataFrame using logsuite.visualization.table_panel.render_table_panel(). After this call, self.save("file.svg") produces the combined scatter+table figure as a single deliverable.

Parameters:
  • df (pandas.DataFrame) – Rows × columns of values to render. NaN values become "N/A"; MultiIndex columns flatten via " | "; MultiIndex rows visually merge repeated outer levels.

  • position ({"bottom", "right"}, default "bottom") – Where the panel sits relative to the scatter axes.

  • title (str, optional) – Heading rendered above the table.

  • formatters (dict, optional) – Per-column formatter spec. Each value may be a callable f(value) -> str or a Python format spec like ".4f". Example: {"PHIE": ".4f", "PERM": ".2f"}.

  • table_fraction (float, default 0.30) – Fraction of the figure dimension reserved for the panel.

Returns:

Self for method chaining.

Return type:

Crossplot

Examples

>>> stats = manager.PHIE.filter("Facies").stats(
...     return_df=True, flat_columns=True
... )
>>> xplot = Crossplot(manager, x="PHIE", y="PERM", color="Facies")
>>> xplot.add_regression_per("Facies", "exponential")
>>> xplot.add_table_panel(stats, title="Per-facies summary",
...                        formatters={"mean": ".4f", "p50": ".4f"})
>>> xplot.save("deliverable.svg")
show()[source]

Display the crossplot in Jupyter or interactive environment.

Return type:

None

save(filepath, dpi=None, bbox_inches='tight')[source]

Save the crossplot to a file.

Parameters:
  • filepath (str) – Output file path

  • dpi (int, optional) – Resolution. If None, uses figure’s dpi.

  • bbox_inches (str, optional) – Bounding box mode. Default: ‘tight’

Return type:

None

close()[source]

Close the matplotlib figure and free memory.

Return type:

None