Skip to content

1D Time Series Plots

Overview

The 1D visualization module creates line plots, bar charts, scatter plots, and tornado diagrams from series data. These are the standard tools for analyzing production histories, pressure decline curves, recovery factors, and sensitivity results. The module lives in bores.visualization.plotly1d and centers on the DataVisualizer class.

Every plot method returns a Plotly Figure object. You can display it interactively with .show(), save it as HTML with .write_html(), or export it as a static image with .write_image(). The figures are fully interactive: you can zoom, pan, hover for data values, and toggle individual series in the legend.

The 1D visualizer does not take ModelState objects directly. Instead, you extract the data you want from your states as numpy arrays and pass those arrays to the visualizer. This gives you full control over what to plot and how to prepare the data (averaging, unit conversion, filtering by well or region) before visualization.


Data Formats

The visualizer accepts three data formats through the SeriesData type:

Single series as a (n, 2) array:

import numpy as np

# Each row is (x, y)
time_pressure = np.column_stack([
    [s.time_in_days for s in states],
    [s.model.fluid_properties.pressure_grid.mean() for s in states],
])

Multiple series as a list of arrays:

# Each array is (n, 2)
oil_series = np.column_stack([time_days, avg_oil_sat])
water_series = np.column_stack([time_days, avg_water_sat])
gas_series = np.column_stack([time_days, avg_gas_sat])

data = [oil_series, water_series, gas_series]

Named series as a dictionary:

data = {
    "Oil Saturation": oil_series,
    "Water Saturation": water_series,
    "Gas Saturation": gas_series,
}

When you pass a dictionary, the keys become the legend labels. When you pass a list, names default to "Series 1", "Series 2", etc., unless you provide series_names in the render call.


Creating Plots

DataVisualizer

The DataVisualizer class is the main entry point for 1D plotting. Create one with optional configuration:

from bores.visualization.plotly1d import DataVisualizer, PlotConfig

# Default configuration
viz = DataVisualizer()

# Custom configuration
viz = DataVisualizer(config=PlotConfig(
    width=1000,
    height=500,
    line_width=3.0,
    font_size=14,
    show_legend=True,
    legend_position="top",
))

make_plot

The make_plot() method creates a single plot:

fig = viz.make_plot(
    data,
    plot_type="line",
    x_label="Time (days)",
    y_label="Average Pressure (psi)",
    title="Reservoir Pressure Decline",
)
fig.show()

The plot_type parameter accepts either a PlotType enum value or a string. Available types are "line", "bar", "scatter", and "tornado".

make_plots (Subplots)

The make_plots() method creates a grid of subplots from multiple datasets:

fig = viz.make_plots(
    data_list=[time_pressure, time_oil_sat, time_water_sat],
    plot_types="line",
    rows=3,
    cols=1,
    subplot_titles=["Pressure", "Oil Saturation", "Water Saturation"],
    shared_xaxes=True,
)
fig.show()

You can mix plot types across subplots by passing a list of types:

fig = viz.make_plots(
    data_list=[production_data, sensitivity_data],
    plot_types=["line", "tornado"],
    rows=1,
    cols=2,
)

make_series_plot (Convenience Function)

For quick one-off line plots, the module provides a standalone function:

from bores.visualization.plotly1d import make_series_plot

fig = make_series_plot(
    data,
    title="Oil Production Rate",
    x_label="Time (days)",
    y_label="Rate (STB/day)",
)

This creates a LineRenderer with default configuration and renders the data. It is equivalent to creating a DataVisualizer and calling make_plot() with plot_type="line".


Plot Types

Line Plots

Line plots are the default and most common type. They connect data points with continuous lines, making them ideal for time series data.

import numpy as np

time_days = np.array([s.time_in_days for s in states])
avg_pressure = np.array([s.model.fluid_properties.pressure_grid.mean() for s in states])

data = {
    "Average Pressure": np.column_stack([time_days, avg_pressure]),
}
fig = viz.make_plot(data, plot_type="line", x_label="Time (days)", y_label="Pressure (psi)")

You can plot multiple series on the same axes by including them all in the data dictionary or list.

Bar Charts

Bar charts display discrete or categorical data. They are useful for comparing values across categories, such as production by well or recovery factor by scenario.

# Recovery factors by scenario
scenarios = np.array([1, 2, 3, 4])
recovery = np.array([0.25, 0.32, 0.41, 0.38])
data = np.column_stack([scenarios, recovery])

fig = viz.make_plot(
    data,
    plot_type="bar",
    x_label="Scenario",
    y_label="Recovery Factor",
    series_names=["Recovery Factor"],
)

Scatter Plots

Scatter plots show individual data points without connecting lines. They support optional trendline fitting. Use them for cross-plots like permeability vs. porosity or rate vs. pressure drawdown.

fig = viz.make_plot(
    data,
    plot_type="scatter",
    x_label="Porosity (fraction)",
    y_label="Permeability (mD)",
)

Tornado Plots

Tornado plots display sensitivity analysis results as horizontal bars, showing the impact of parameter variations on a target metric. Each parameter has two bars: one for the low case and one for the high case, centered on the base case value.

# Each row: [parameter_index, low_delta, high_delta]
sensitivity_data = np.array([
    [1, -50, 80],     # Permeability: base - 50, base + 80
    [2, -30, 25],     # Porosity
    [3, -20, 15],     # Water saturation
    [4, -10, 12],     # Oil viscosity
])
fig = viz.make_plot(
    sensitivity_data,
    plot_type="tornado",
    series_names=["Permeability", "Porosity", "Sw", "Viscosity"],
)

Configuration Reference

The PlotConfig class controls all visual aspects of 1D plots:

Parameter Default Description
width 800 Figure width in pixels
height 600 Figure height in pixels
title None Default title for plots
show_legend True Whether to display legend
legend_position "right" Legend position: top, bottom, left, right
line_width 2.0 Default line width
marker_size 8 Default marker size
opacity 0.8 Default opacity for plot elements
font_family "Arial, sans-serif" Font family
font_size 12 Default font size
title_font_size 16 Title font size
show_grid True Whether to show grid lines
show_hover True Whether to show hover information
hover_mode "x unified" Hover interaction mode
background_color "white" Figure background color
plot_background_color "white" Plot area background color

Common Workflows

Production Rate Over Time

import numpy as np
from bores.visualization.plotly1d import DataVisualizer

viz = DataVisualizer()
states = list(bores.run(model, config))

time_days = np.array([s.time_in_days for s in states])
oil_rate = np.array([s.production.oil.sum() for s in states])
water_rate = np.array([s.production.water.sum() for s in states])

data = {
    "Oil Rate": np.column_stack([time_days, oil_rate]),
    "Water Rate": np.column_stack([time_days, water_rate]),
}
fig = viz.make_plot(data, x_label="Time (days)", y_label="Rate (ft³/day)")
fig.show()

Pressure and Saturation Dashboard

avg_pressure = np.array([s.model.fluid_properties.pressure_grid.mean() for s in states])
avg_So = np.array([s.model.fluid_properties.oil_saturation_grid.mean() for s in states])
avg_Sw = np.array([s.model.fluid_properties.water_saturation_grid.mean() for s in states])

fig = viz.make_plots(
    data_list=[
        np.column_stack([time_days, avg_pressure]),
        {
            "Oil": np.column_stack([time_days, avg_So]),
            "Water": np.column_stack([time_days, avg_Sw]),
        },
    ],
    plot_types="line",
    rows=2,
    cols=1,
    subplot_titles=["Average Pressure (psi)", "Average Saturations"],
    shared_xaxes=True,
)
fig.show()