"""Module for fast XSection plots of wells/surfaces etc, using matplotlib."""
import math
import warnings
import numpy as np
import numpy.ma as ma
import pandas as pd
from scipy.ndimage import gaussian_filter
from xtgeo.common.log import null_logger
from xtgeo.common.xtgeo_dialog import XTGeoDialog
from xtgeo.well.well1 import Well
from xtgeo.xyz.polygons import Polygons
from .baseplot import BasePlot, _get_colormap
xtg = XTGeoDialog()
logger = null_logger(__name__)
[docs]
class XSection(BasePlot):
"""Class for plotting a cross-section of a well.
Args:
zmin (float): Upper level of the plot (top Y axis).
zmax (float): Lower level of the plot (bottom Y axis).
well (Well): XTGeo well object.
surfaces (list): List of XTGeo RegularSurface objects
surfacenames (list): List of surface names (str) for legend
cube (Cube): A XTGeo Cube instance
grid (Grid): A XTGeo Grid instance
gridproperty (GridProperty): A XTGeo GridProperty instance
colormap (str): Name of colormap, e.g. 'Set1'. Default is 'xtgeo'
outline (obj): XTGeo Polygons object
"""
[docs]
def __init__(
self,
zmin=0,
zmax=9999,
well=None,
surfaces=None,
sampling=20,
nextend=5,
colormap=None,
zonelogshift=0,
surfacenames=None,
cube=None,
grid=None,
gridproperty=None,
outline=None,
):
"""Init method."""
super().__init__()
self._zmin = zmin
self._zmax = zmax
self._well = well
self._nextend = nextend
self._sampling = sampling
self._surfaces = surfaces
self._surfacenames = surfacenames
self._cube = cube
self._grid = grid
self._gridproperty = gridproperty
self._zonelogshift = zonelogshift
self._outline = outline
self._has_axes = True
self._has_legend = True
self._pagesize = "A4"
self._fence = None
self._legendtitle = "Zones"
self._legendsize = 5
self._ax1 = None
self._ax2 = None
self._ax3 = None
self._allfigs = []
self._colormap_cube = None
self._colorlegend_cube = False
self._colormap_grid = None
self._colorlegend_grid = False
if colormap is None:
self._colormap = _get_colormap("viridis")
else:
self.define_colormap(colormap)
self._colormap_facies = self.define_any_colormap("xtgeo")
self._colormap_facies_dict = {idx: idx for idx in range(100)}
self._colormap_perf = self.define_any_colormap("xtgeo")
self._colormap_perf_dict = {idx: idx for idx in range(100)}
self._colormap_zonelog = None
self._colormap_zonelog_dict = {idx: idx for idx in range(100)}
logger.info("Ran __init__ ...")
logger.debug("Colormap is %s", self._colormap)
# ==================================================================================
# Properties
# Notice additonal props in base class
# ==================================================================================
@property
def pagesize(self):
"""Returns page size."""
return self._pagesize
@property
def legendsize(self):
"""Returns or set the legend size."""
return self._legendsize
@legendsize.setter
def legendsize(self, lsize):
self._legendsize = lsize
@property
def has_legend(self):
"""Returns or set the legends."""
return self._has_legend
@has_legend.setter
def has_legend(self, value):
if not isinstance(value, bool):
raise ValueError("Input is not a bool")
self._has_legend = value
@property
def has_axes(self):
"""Returns or set the axes status."""
return self._has_axes
@has_axes.setter
def has_axes(self, value):
if not isinstance(value, bool):
raise ValueError("Input is not a bool")
self._has_axes = value
@property
def colormap_facies(self):
"""Set or get the facies colormap."""
return self._colormap_facies
@colormap_facies.setter
def colormap_facies(self, cmap):
self._colormap_facies = self.define_any_colormap(cmap)
@property
def colormap_zonelog(self):
"""Set or get the zonelog colormap."""
return self._colormap_zonelog
@colormap_zonelog.setter
def colormap_zonelog(self, cmap):
self._colormap_zonelog = self.define_any_colormap(cmap)
@property
def colormap_perf(self):
"""Set or get the perforations colormap."""
return self._colormap_perf
@colormap_perf.setter
def colormap_perf(self, cmap):
self._colormap_perf = self.define_any_colormap(cmap)
@property
def colormap_facies_dict(self):
"""Set or get the facies colormap actual dict table."""
return self._colormap_facies_dict
@colormap_facies_dict.setter
def colormap_facies_dict(self, xdict):
if not isinstance(xdict, dict):
raise ValueError("Input is not a dict")
# if not all(isinstance(item, int) for item in list(xdict.values)):
# raise ValueError('Dict values is a list, but some elems are '
# 'not ints!')
self._colormap_facies_dict = xdict
@property
def colormap_perf_dict(self):
"""Set or get the perf colormap actual dict table."""
return self._colormap_perf_dict
@colormap_perf_dict.setter
def colormap_perf_dict(self, xdict):
if not isinstance(xdict, dict):
raise ValueError("Input is not a dict")
# if not all(isinstance(item, int) for item in list(xdict.values)):
# raise ValueError('Dict values is a list, but some elems are '
# 'not ints!')
self._colormap_perf_dict = xdict
@property
def colormap_zonelog_dict(self):
"""Set or get the zonelog colormap actual dict table."""
return self._colormap_zonelog_dict
@colormap_zonelog_dict.setter
def colormap_zonelog_dict(self, xdict):
if not isinstance(xdict, dict):
raise ValueError("Input is not a dict")
# if not all(isinstance(item, int) for item in list(xdict.values)):
# raise ValueError('Dict values is a list, but some elems are '
# 'not ints!')
self._colormap_zonelog_dict = xdict
@property
def fence(self):
"""Set or get the fence spesification."""
if self._fence is None:
if self._well is not None:
wfence = self._well.get_fence_polyline(
sampling=self._sampling, nextend=self._nextend, tvdmin=self._zmin
)
self._fence = wfence
if wfence is False:
self._fence = None
else:
raise ValueError("Input well is None") # should be more flexible
return self._fence
@fence.setter
def fence(self, myfence):
# this can be extended with checks and various types of input...
self._fence = myfence
# ==================================================================================
# Functions methods (public)
# ==================================================================================
[docs]
def canvas(self, title=None, subtitle=None, infotext=None, figscaling=1.0):
"""Prepare the canvas to plot on, with title and subtitle.
Args:
title (str, optional): Title of plot.
subtitle (str, optional): Sub title of plot.
infotext (str, optional): Text to be written as info string.
figscaling (str, optional): Figure scaling, default is 1.0
"""
# overriding the base class canvas
import matplotlib.pyplot as plt
plt.rcParams["axes.xmargin"] = 0 # fill the plot margins
if not self._has_axes:
plt.rcParams["axes.titlecolor"] = (0, 0, 0, 0)
plt.rcParams["axes.edgecolor"] = (0, 0, 0, 0)
plt.rcParams["axes.labelcolor"] = (0, 0, 0, 0)
plt.rcParams["axes.titlecolor"] = (0, 0, 0, 0)
plt.rcParams["xtick.color"] = (0, 0, 0, 0)
plt.rcParams["ytick.color"] = (0, 0, 0, 0)
# self._fig, (ax1, ax2) = plt.subplots(2, figsize=(11.69, 8.27))
self._fig, _ = plt.subplots(figsize=(11.69 * figscaling, 8.27 * figscaling))
self._allfigs.append(self._fig)
ax1 = {}
# since the plan is to at some point remove plotting from xtgeo:
warnings.filterwarnings("ignore", category=DeprecationWarning)
ax1["main"] = plt.subplot2grid((20, 28), (0, 0), rowspan=20, colspan=23)
ax2 = plt.subplot2grid(
(20, 28), (10, 23), rowspan=5, colspan=5, frame_on=self._has_legend
)
ax3 = plt.subplot2grid(
(20, 28), (15, 23), rowspan=5, colspan=5, frame_on=self._has_legend
)
if self._has_legend:
# indicate A to B
plt.text(
0.02,
0.98,
"A",
ha="left",
va="top",
transform=ax1["main"].transAxes,
fontsize=8,
)
plt.text(
0.98,
0.98,
"B",
ha="right",
va="top",
transform=ax1["main"].transAxes,
fontsize=8,
)
# title here:
if title is not None:
plt.text(
0.5,
1.09,
title,
ha="center",
va="center",
transform=ax1["main"].transAxes,
fontsize=18,
)
if subtitle is not None:
ax1["main"].set_title(subtitle, size=14)
if infotext is not None:
plt.text(
-0.11,
-0.11,
infotext,
ha="left",
va="center",
transform=ax1["main"].transAxes,
fontsize=6,
)
ax1["main"].set_ylabel("Depth", fontsize=12.0)
ax1["main"].set_xlabel("Length along well", fontsize=12)
ax2.tick_params(
axis="both",
which="both",
bottom=False,
top=False,
right=False,
left=False,
labelbottom=False,
labeltop=False,
labelright=False,
labelleft=False,
)
ax3.tick_params(
axis="both",
which="both",
bottom=False,
top=False,
right=False,
left=False,
labelbottom=False,
labeltop=False,
labelright=False,
labelleft=False,
)
# need these also, a bug in functions above?
ax2.xaxis.set_major_formatter(plt.NullFormatter())
ax2.yaxis.set_major_formatter(plt.NullFormatter())
ax3.xaxis.set_major_formatter(plt.NullFormatter())
ax3.yaxis.set_major_formatter(plt.NullFormatter())
self._ax1 = ax1
self._ax2 = ax2
self._ax3 = ax3
[docs]
def plot_well(
self,
zonelogname="ZONELOG",
facieslogname=None,
perflogname=None,
wellcrossings=None,
wellcrossingnames=True,
wellcrossingyears=False,
welltrajcolor="black",
welltrajwidth=6,
):
"""Input an XTGeo Well object and plot it."""
if self.fence is None:
return
wo = self._well
# reduce the well data by Pandas operations
dfr = wo.get_dataframe()
wo.set_dataframe(dfr[dfr["Z_TVDSS"] > self._zmin])
# Create a relative XYLENGTH vector (0.0 where well starts)
wo.create_relative_hlen()
dfr = wo.get_dataframe()
if dfr.empty:
self._showok = False
return
# get the well trajectory (numpies) as copy
zv = dfr["Z_TVDSS"].values.copy()
hv = dfr["R_HLEN"].values.copy()
# plot the perflog, if any, first
if perflogname:
ax, bba = self._currentax(axisname="perf")
self._plot_well_perflog(dfr, ax, bba, perflogname, legend=self._has_legend)
# plot the facies, if any, behind the trajectory; ie. first or second
if facieslogname:
ax, bba = self._currentax(axisname="facies")
self._plot_well_faclog(dfr, ax, bba, facieslogname, legend=self._has_legend)
axx, _bbxa = self._currentax(axisname="well")
self._plot_well_traj(
axx, zv, hv, welltrajcolor=welltrajcolor, linewidth=welltrajwidth
)
if zonelogname:
ax, bba = self._currentax(axisname="main")
self._plot_well_zlog(dfr, axx, bba, zonelogname, legend=self._has_legend)
if wellcrossings is not None and wellcrossings.empty:
wellcrossings = None
if wellcrossings is not None:
self._plot_well_crossings(
dfr, axx, wellcrossings, wellcrossingnames, wellcrossingyears
)
[docs]
def set_xaxis_md(self, gridlines=False):
"""Set x-axis labels to measured depth."""
md_start = self._well.get_dataframe(copy=False)["MDEPTH"].iloc[0]
md_start_round = int(math.floor(md_start / 100.0)) * 100
md_start_delta = md_start - md_start_round
import matplotlib.pyplot as plt
auto_ticks = plt.xticks()
auto_ticks_delta = auto_ticks[0][1] - auto_ticks[0][0]
ax, _ = self._currentax(axisname="main")
lim = ax.get_xlim()
new_ticks = []
new_tick_labels = []
delta = 0
for tick in auto_ticks[0]:
new_ticks.append(int(float(tick) - md_start_delta))
new_tick_labels.append(int(md_start_round + delta))
delta += auto_ticks_delta
# Set new xticks and labels
plt.xticks(new_ticks, new_tick_labels)
if gridlines:
ax.tick_params(axis="y", direction="in", which="both")
ax.minorticks_on()
ax.grid(color="black", linewidth=0.8, which="major", linestyle="-")
ax.grid(color="black", linewidth=0.5, which="minor", linestyle="--")
# Restore xaxis limits and set axis title
ax.set_xlim(lim)
ax.set_ylabel("TVD MSL [m]", fontsize=12.0)
ax.set_xlabel("Measured Depth [m]", fontsize=12)
def _plot_well_traj(self, ax, zv, hv, welltrajcolor, linewidth):
"""Plot the trajectory as a black line."""
zv_copy = ma.masked_where(zv < self._zmin, zv)
hv_copy = ma.masked_where(zv < self._zmin, hv)
ax.plot(hv_copy, zv_copy, linewidth=linewidth, c=welltrajcolor)
@staticmethod
def _line_segments_colors(df, idx, ctable, logname, fillnavalue):
"""Get segment and color array for plotting matplotlib lineCollection."""
df_idx = pd.DataFrame(
{"idx_log": list(idx.keys()), "idx_color": list(idx.values())}
)
df_ctable = df_idx.merge(
pd.DataFrame({"ctable": ctable}),
how="left",
left_on="idx_color",
right_index=True,
)
dff = df.merge(df_ctable, how="left", left_on=logname, right_on="idx_log")
dff["point"] = list(zip(dff["R_HLEN"], dff["Z_TVDSS"]))
# find line segments
segments = []
segments_i = -1
colorlist = []
previous_color = None
for point, color in zip(dff["point"], dff["ctable"]):
if np.any(np.isnan(color)):
color = fillnavalue
if color == previous_color:
segments[segments_i].append(point)
previous_color = color
else:
# add endpoint to current segment
if segments_i > 0:
segments[segments_i].append(point)
# start new segment
segments.append([point])
colorlist.append(color)
previous_color = color
segments_i += 1
colorlist = np.asarray(colorlist, dtype=object)
return segments, colorlist
def _plot_well_zlog(self, df, ax, bba, zonelogname, logwidth=4, legend=False):
"""Plot the zone log as colored segments."""
if zonelogname not in df.columns:
return
zomin = 0
zomax = 0
try:
zomin = int(df[zonelogname].min())
zomax = int(df[zonelogname].max())
except ValueError:
self._showok = False
return
logger.info("ZONELOG min - max is %s - %s", zomin, zomax)
zshift = 0
if self._zonelogshift != 0:
zshift = self._zonelogshift
if self.colormap_zonelog is not None:
cmap = self.colormap_zonelog
ctable = self.get_any_colormap_as_table(cmap)
else:
ctable = self.get_colormap_as_table()
idx = self.colormap_zonelog_dict
# adjust for zoneshift.
idx_zshift = {}
for key in idx:
idx_zshift[key - zshift + 1] = idx[key]
fillnavalue = (0.9, 0.9, 0.9)
segments, segments_colors = self._line_segments_colors(
df, idx_zshift, ctable, zonelogname, fillnavalue
)
import matplotlib as mpl
lc = mpl.collections.LineCollection(
segments, colors=segments_colors, linewidth=logwidth, zorder=202
)
ax.add_collection(lc)
if legend:
zrecord = self._well.get_logrecord(zonelogname)
zrecord = {val: zname for val, zname in zrecord.items() if val >= 0}
zcolors = {}
for zone in zrecord:
color = idx[zone] if isinstance(idx[zone], str) else ctable[idx[zone]]
zcolors[zrecord[zone]] = color
self._drawproxylegend(ax, bba, items=zcolors, title="Zonelog")
def _plot_well_faclog(self, df, ax, bba, facieslogname, logwidth=9, legend=True):
"""Plot the facies log as colored segments.
Args:
df (dataframe): The Well dataframe.
ax (axes): The ax plot object.
bba: Bounding box
facieslogname (str): name of the facies log.
logwidth (int): Log linewidth.
legend (bool): Plot log legend?
"""
if facieslogname not in df.columns:
return
cmap = self.colormap_facies
ctable = self.get_any_colormap_as_table(cmap)
idx = self.colormap_facies_dict
fillnavalue = (0, 0, 0, 0) # transparent
segments, segments_colors = self._line_segments_colors(
df, idx, ctable, facieslogname, fillnavalue
)
import matplotlib as mpl
lc = mpl.collections.LineCollection(
segments, colors=segments_colors, linewidth=logwidth, zorder=201
)
ax.add_collection(lc)
if legend:
frecord = self._well.get_logrecord(facieslogname)
frecord = {val: fname for val, fname in frecord.items() if val >= 0}
fcolors = {}
for facies in frecord:
if isinstance(idx[facies], str):
color = idx[facies]
else:
color = ctable[idx[facies]]
fcolors[frecord[facies]] = color
self._drawproxylegend(ax, bba, items=fcolors, title="Facies")
def _plot_well_perflog(self, df, ax, bba, perflogname, logwidth=12, legend=True):
"""Plot the perforation log as colored segments.
Args:
df (dataframe): The Well dataframe.
ax (axes): The ax plot object.
zv (ndarray): The numpy Z TVD array.
bba: Boundinng box
hv (ndarray): The numpy Length array.
perflogname (str): name of the perforation log.
logwidth (int): Log linewidth.
legend (bool): Plot log legend?
"""
if perflogname not in df.columns:
return
cmap = self.colormap_perf
ctable = self.get_any_colormap_as_table(cmap)
idx = self.colormap_perf_dict
fillnavalue = (0, 0, 0, 0) # transparent
segments, segments_colors = self._line_segments_colors(
df, idx, ctable, perflogname, fillnavalue
)
import matplotlib as mpl
lc = mpl.collections.LineCollection(
segments, colors=segments_colors, linewidth=logwidth, zorder=200
)
ax.add_collection(lc)
if legend:
precord = self._well.get_logrecord(perflogname)
precord = {val: pname for val, pname in precord.items() if val >= 0}
pcolors = {}
for perf in precord:
color = idx[perf] if isinstance(idx[perf], str) else ctable[idx[perf]]
pcolors[precord[perf]] = color
self._drawproxylegend(ax, bba, items=pcolors, title="Perforations")
@staticmethod
def _plot_well_crossings(dfr, ax, wcross, names=True, years=False):
"""Plot well crossing based on dataframe (wcross).
The well crossing coordinates are identified for this well,
and then it is looking for the closest coordinate. Given this
coordinate, a position is chosen.
The pandas dataframe wcross shall have the following columns:
* Name of crossing wells named CWELL
* Coordinate X named X_UTME
* Coordinate Y named Y_UTMN
* Coordinate Z named Z_TVDSS
Optional column:
* Drilled year of crossing well named CYEAR
Args:
dfr: Well dataframe
ax: current axis
wcross: A pandas dataframe with precomputed well crossings
names: Display the names of the crossed wells
years: Display the drilled year of the crossed wells
"""
placings = {
0: (40, 40),
1: (40, -20),
2: (-30, 30),
3: (30, 20),
4: (-40, 30),
5: (-20, 40),
}
for index, row in wcross.iterrows():
xcoord = row.X_UTME
ycoord = row.Y_UTMN
dfrc = dfr.copy()
dfrc["DLEN"] = pow(
pow(dfrc.X_UTME - xcoord, 2) + pow(dfrc.Y_UTMN - ycoord, 2), 0.5
)
minindx = dfrc.DLEN.idxmin()
ax.scatter(
dfrc.R_HLEN[minindx],
row.Z_TVDSS,
marker="o",
color="black",
s=70,
zorder=300,
)
ax.scatter(
dfrc.R_HLEN[minindx],
row.Z_TVDSS,
marker="o",
color="orange",
s=38,
zorder=302,
)
modulo = index % 5
text = ""
if names:
text = Well.get_short_wellname(row.CWELL)
if years:
text = text + "\n" + row.CYEAR if names else row.CYEAR
if names or years:
ax.annotate(
text,
size=6,
xy=(dfrc.R_HLEN[minindx], row.Z_TVDSS),
xytext=placings[modulo],
textcoords="offset points",
arrowprops={
"arrowstyle": "->",
"connectionstyle": "angle3,angleA=0,angleB=90",
},
color="black",
)
def _drawproxylegend(self, ax, bba, items, title=None):
proxies = []
labels = []
import matplotlib as mpl
for item in items:
color = items[item]
proxies.append(mpl.lines.Line2D([0, 1], [0, 1], color=color, linewidth=5))
labels.append(item)
ax.legend(
proxies,
labels,
loc="upper left",
bbox_to_anchor=bba,
prop={"size": self._legendsize},
title=title,
handlelength=2,
)
def _drawlegend(self, ax, bba, title=None):
leg = ax.legend(
loc="upper left",
bbox_to_anchor=bba,
prop={"size": self._legendsize},
title=title,
handlelength=2,
)
for myleg in leg.get_lines():
myleg.set_linewidth(5)
def _currentax(self, axisname="main"):
"""Keep track of current axis; is needed as one new legend need one new axis."""
# for multiple legends, bba is dynamic
bbapos = {
"main": (1.22, 1.12, 1, 0),
"contacts": (1.01, 1.12),
"second": (1.22, 0.50),
"facies": (1.01, 1.00),
"perf": (1.22, 0.45),
}
ax1 = self._ax1
if axisname != "main":
ax1[axisname] = self._ax1["main"].twinx()
# invert min,max to invert the Y axis
ax1[axisname].set_ylim([self._zmax, self._zmin])
ax1[axisname].set_yticklabels([])
ax1[axisname].tick_params(axis="y", direction="in")
ax = self._ax1[axisname]
bba = bbapos.get(axisname, (1.22, 0.5))
return ax, bba
[docs]
def plot_cube(
self,
colormap="seismic",
vmin=None,
vmax=None,
alpha=0.7,
interpolation="gaussian",
sampling="nearest",
):
"""Plot a cube backdrop.
Args:
colormap (ColorMap): Name of color map (default 'seismic')
vmin (float): Minimum value in plot.
vmax (float); Maximum value in plot
alpha (float): Alpah blending number beween 0 and 1.
interpolation (str): Interpolation for plotting, cf. matplotlib
documentation on this. Also gaussianN is allowed, where
N = 1..9.
sampling (str): 'nearest' (default) or 'trilinear' (more precise)
Raises:
ValueError: No cube is loaded
"""
if self.fence is None:
return
if self._cube is None:
raise ValueError("Ask for plot cube, but noe cube is loaded")
ax, _ = self._currentax(axisname="main")
zinc = self._cube.zinc / 2.0
zvv = self._cube.get_randomline(
self.fence,
zmin=self._zmin,
zmax=self._zmax,
zincrement=zinc,
sampling=sampling,
)
h1, h2, v1, v2, arr = zvv
# if vmin is not None or vmax is not None:
# arr = np.clip(arr, vmin, vmax)
if self._colormap_cube is None:
if colormap is None:
colormap = "seismic"
self._colormap_cube = self.define_any_colormap(colormap)
if "gaussian" in interpolation: # allow gaussian3 etc
nnv = interpolation[-1]
try:
nnv = int(nnv)
arr = gaussian_filter(arr, nnv)
interpolation = "none"
except ValueError:
interpolation = "gaussian"
img = ax.imshow(
arr,
cmap=self._colormap_cube,
interpolation=interpolation,
vmin=vmin,
vmax=vmax,
extent=(h1, h2, v2, v1),
aspect="auto",
alpha=alpha,
)
logger.info("Actual VMIN and VMAX: %s", img.get_clim())
# steer this?
if self._colorlegend_cube:
self._fig.colorbar(img, ax=ax)
[docs]
def plot_grid3d(
self,
colormap="rainbow",
vmin=None,
vmax=None,
alpha=0.7,
zinc=0.5,
interpolation="auto",
):
"""Plot a sampled grid with gridproperty backdrop.
Args:
colormap (ColorMap): Name of color map (default 'rainbow')
vmin (float): Minimum value in plot.
vmax (float); Maximum value in plot
alpha (float): Alpha blending number beween 0 and 1.
zinc (float): Sampling vertically, default is 0.5
interpolation (str): Interpolation for plotting, cf. matplotlib
documentation on this. "auto" uses "nearest" for discrete
parameters and "antialiased" for floats.
Raises:
ValueError: No grid or gridproperty is loaded
"""
if self.fence is None:
return
if self._grid is None or self._gridproperty is None:
raise ValueError("Ask for plot of grid, but no grid is loaded")
ax, _bba = self._currentax(axisname="main")
zvv = self._grid.get_randomline(
self.fence,
self._gridproperty,
zmin=self._zmin,
zmax=self._zmax,
zincrement=zinc,
)
h1, h2, v1, v2, arr = zvv
# if vmin is not None or vmax is not None:
# arr = np.clip(arr, vmin, vmax)
if self._colormap_grid is None:
if colormap is None:
colormap = "rainbow"
self._colormap_grid = self.define_any_colormap(colormap)
if interpolation == "auto":
if self._gridproperty.isdiscrete:
interpolation = "nearest"
else:
interpolation = "antialiased"
img = ax.imshow(
arr,
cmap=self._colormap_grid,
vmin=vmin,
vmax=vmax,
extent=(h1, h2, v2, v1),
aspect="auto",
alpha=alpha,
interpolation=interpolation,
)
logger.info("Actual VMIN and VMAX: %s", img.get_clim())
# steer this?
if self._colorlegend_grid:
self._fig.colorbar(img, ax=ax)
[docs]
def plot_surfaces(
self,
fill=False,
surfaces=None,
surfacenames=None,
colormap=None,
onecolor=None,
linewidth=1.0,
linestyle="-",
legend=True,
legendtitle=None,
fancyline=False,
axisname="main",
gridlines=False,
):
"""Input a surface list (ordered from top to base) , and plot them."""
if self.fence is None:
return
ax, bba = self._currentax(axisname=axisname)
# either use surfaces from __init__, or override with surfaces
# speciefied here
if surfaces is None:
surfaces = self._surfaces
surfacenames = self._surfacenames
surfacenames = [surf.name for surf in surfaces]
if legendtitle is None:
legendtitle = self._legendtitle
if colormap is None:
colormap = self._colormap
else:
self.define_colormap(colormap)
nlen = len(surfaces)
# legend
slegend = []
if surfacenames is None:
for i in range(nlen):
slegend.append(f"Surf {i}")
else:
# do a check
if len(surfacenames) != nlen:
msg = (
"Wrong number of entries in surfacenames! Number of names "
f"is {len(surfacenames)} while number of files is {nlen}"
)
raise ValueError(msg)
slegend = surfacenames
if nlen > self._colormap.N:
raise ValueError(
f"Too few colors in color table ({self._colormap.N}) "
f"vs number of surfaces ({nlen})"
)
# sample the horizon to the fence:
colortable = self.get_colormap_as_table()
for i in range(nlen):
usecolor = colortable[i]
if onecolor:
usecolor = onecolor
if not fill:
hfence = surfaces[i].get_randomline(self.fence)
xcol = "white"
if fancyline:
cxx = usecolor
if cxx[0] + cxx[1] + cxx[2] > 1.5:
xcol = "black"
ax.plot(
hfence[:, 0], hfence[:, 1], linewidth=1.2 * linewidth, c=xcol
)
ax.plot(
hfence[:, 0],
hfence[:, 1],
linewidth=linewidth,
c=usecolor,
label=slegend[i],
linestyle=linestyle,
)
if fancyline:
ax.plot(
hfence[:, 0], hfence[:, 1], linewidth=0.3 * linewidth, c=xcol
)
else:
# need copy() .. why?? found by debugging...
hfence1 = surfaces[i].get_randomline(self.fence).copy()
x1 = hfence1[:, 0]
y1 = hfence1[:, 1]
if i < (nlen - 1):
hfence2 = surfaces[i + 1].get_randomline(self.fence).copy()
y2 = hfence2[:, 1]
else:
y2 = y1.copy()
ax.plot(
x1, y1, linewidth=0.1 * linewidth, linestyle=linestyle, c="black"
)
ax.fill_between(x1, y1, y2, facecolor=colortable[i], label=slegend[i])
# invert min,max to invert the Y axis
ax.set_ylim([self._zmax, self._zmin])
if legend:
self._drawlegend(ax, bba, title=legendtitle)
if axisname != "main":
ax.set_yticklabels([])
ax.tick_params(axis="y", direction="in")
if axisname == "main" and gridlines:
ax.grid(color="grey", linewidth=0.2)
[docs]
def plot_md_data(
self,
data=None,
markersize=10,
color="red",
linestyle="",
label=False,
zorder=350,
**kwargs,
):
"""Plot MD vs TVD data as lines and/or markers.
The input pandas dataframe points shall have the following columns:
* Name of well(s) named WELL
* Coordinate X named MDEPTH
* Coordinate Y named Z_TVDSS
"""
ax, _ = self._currentax(axisname="main")
well = self._well
data_well = data.copy()
data_well = data_well.loc[data_well["WELL"] == well.xwellname]
del data_well["WELL"]
md_start = well.get_dataframe(copy=False)["MDEPTH"].iloc[0]
data_well["R_HLEN"] = data_well["MDEPTH"]
data_well["R_HLEN"] = data_well["R_HLEN"].subtract(md_start)
data_well.plot(
ax=ax,
x="R_HLEN",
y="Z_TVDSS",
legend=None,
linestyle=linestyle,
markersize=markersize,
color=color,
label=label,
zorder=zorder,
**kwargs,
)
[docs]
def plot_wellmap(self, otherwells=None, expand=1):
"""Plot well map as local view, optionally with nearby wells.
Args:
otherwells (list of Polygons): List of surrounding wells to plot,
these wells are repr as Polygons instances, one per well.
expand (float): Plot axis expand factor (default is 1); larger
values may be used if other wells are plotted.
"""
ax = self._ax2
if self.fence is not None:
xwellarray = self._well.get_dataframe(copy=False)["X_UTME"].values
ywellarray = self._well.get_dataframe(copy=False)["Y_UTMN"].values
ax.plot(xwellarray, ywellarray, linewidth=4, c="cyan")
ax.plot(self.fence[:, 0], self.fence[:, 1], linewidth=1, c="black")
ax.annotate("A", xy=(self.fence[0, 0], self.fence[0, 1]), fontsize=8)
ax.annotate("B", xy=(self.fence[-1, 0], self.fence[-1, 1]), fontsize=8)
ax.set_aspect("equal", "datalim")
left, right = ax.get_xlim()
xdiff = right - left
bottom, top = ax.get_ylim()
ydiff = top - bottom
ax.set_xlim(left - (expand - 1.0) * xdiff, right + (expand - 1.0) * xdiff)
ax.set_ylim(bottom - (expand - 1.0) * ydiff, top + (expand - 1.0) * ydiff)
if otherwells:
for poly in otherwells:
if not isinstance(poly, Polygons):
xtg.warn(f"<otherw> not a Polygons instance, but a {type(poly)}")
continue
if poly.name == self._well.xwellname:
continue
xwp = poly.get_dataframe(copy=False)[poly.xname].values
ywp = poly.get_dataframe(copy=False)[poly.yname].values
ax.plot(xwp, ywp, linewidth=1, c="grey")
ax.annotate(poly.name, xy=(xwp[-1], ywp[-1]), color="grey", size=5)
[docs]
def plot_map(self):
"""Plot well location map as an overall view (with field outline)."""
if self.fence is None:
return
if not self._outline:
return
ax = self._ax3
if self.fence is not None:
xp = self._outline.get_dataframe(copy=False)["X_UTME"].values
yp = self._outline.get_dataframe(copy=False)["Y_UTMN"].values
ip = self._outline.get_dataframe(copy=False)["POLY_ID"].values
ax.plot(self._fence[:, 0], self._fence[:, 1], linewidth=3, c="red")
for i in range(int(ip.min()), int(ip.max()) + 1):
xpc = xp.copy()[ip == i]
ypc = yp.copy()[ip == i]
if len(xpc) > 1:
ax.plot(xpc, ypc, linewidth=0.3, c="black")
ax.set_aspect("equal", "datalim")