Source code for groundhog.general.plotting

#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'Bruno Stuyts'

# Native Python packages
import datetime
import warnings
import traceback

# 3rd party packages
from jinja2 import Environment, BaseLoader
import plotly.graph_objs as go
from plotly.colors import DEFAULT_PLOTLY_COLORS
from plotly import subplots
import numpy as np
import matplotlib.pyplot as plt

# Project imports

GROUNDHOG_PLOTTING_CONFIG = {
    'showLink': True,
    'plotlyServerURL': "https://github.com/snakesonabrain/groundhog",
    'linkText': 'Created by groundhog using Plotly!'
}

PLOTLY_GLOBAL_FONT = dict(family='Century Gothic', size=12, color='#5f5f5f')
C0 = '#1f77b4'; C1 = '#ff7f0e'; C2 = '#2ca02c'; C3 = '#d62728'; C4 = '#9467bd'; C5 = '#8c564b'; C6 = '#e377c2'; C7 = '#7f7f7f'; C8 = '#bcbd22'; C9 = '#17becf'
PLOTLY_COLORS = [C0, C1, C2, C3, C4, C5, C6, C7, C8, C9]

BRIGHTCOLORS = ['#4477AA', '#EE6677', '#228833', '#CCBB44', '#66CCEE', '#AA3377', '#BBBBBB']

USCS_HATCHES = {
    'SW': '..',
    'SP': '....',
    'SM': '||...',
    'SC': '///...',
    'ML': '||||',
    'CL': '////',
    'OL': '--',
    'MH': '||',
    'CH': '//'
}

[docs] def plot_with_log(x=[[],], z=[[],], names=[[],], showlegends=None, hide_all_legends=False, modes=None, markerformats=None, soildata=None, fillcolordict={'SAND': 'yellow', 'CLAY': 'brown', 'SILT': 'green', 'ROCK': 'grey'}, depth_from_key="Depth from [m]", depth_to_key="Depth to [m]", colors=None, logwidth=0.05, xtitles=[], ztitle=None, xranges=None, zrange=None, ztick=None, dticks=None, layout=dict(), showfig=True): """ Plots a given number of traces in a plot with a soil mini-log on the left hand side. The traces are given as a list of lists, the traces are grouped per plotting panel. For example x=[[np.linspace(0, 1, 100), np.logspace(0,2,100)], [np.linspace(1, 3, 100), ]] leads to the first two traces plotted in the first panel and one trace in the second panel. The same goes for the z arrays, trace names, ... :param x: List of lists of x-arrays for the traces :param z: List of lists of z-arrays for the traces :param names: List of lists of names for the traces (used in legend) :param showlegends: Array of booleans determining whether or not to show the trace in the legend. Showing/hiding legends can be specified per trace. :param hide_all_legends: Boolean indicating whether all legends need to be hidden (default=False). :param modes: List of display modes for the traces (select from 'lines', 'markers' or 'lines+markers' :param markerformats: List of formats for the markers (see Plotly docs for more info) :param soildata: Pandas dataframe with keys 'Soil type': Array with soil type for each layer, 'Depth from [m]': Array with start depth for each layer, 'Depth to [m]': Array with bottom depth for each layer :param fillcolordict: Dictionary with fill colours (default yellow for 'SAND', brown from 'CLAY' and grey for 'ROCK') :param depth_from_key: Key for the column with start depths of each layer :param depth_to_key: Key for the column with end depths of each layer :param colors: List of colours to be used for plotting (default = default Plotly colours) :param logwidth: Width of the soil width as a ratio of the total plot with (default = 0.05) :param xtitles: Array with X-axis titles for the panels :param ztitle: Depth axis title (Depth axis is shared between all panels) :param xranges: List with ranges to be used for X-axes :param zrange: Range to be used for Y-axis :param ztick: Tick interval to be used for the Y-axis :param dticks: List of tick intervals to be used for the X-axes :param layout: Dictionary with the layout settings :param showfig: Boolean determining whether the figure needs to be shown :return: Plotly figure object which can be further modified """ no_panels = x.__len__() panel_widths = list(map(lambda _x: (1 - logwidth) / no_panels, x)) panel_widths = list(np.append(logwidth, panel_widths)) _fig = subplots.make_subplots(rows=1, cols=no_panels + 1, column_widths=panel_widths, shared_yaxes=True, print_grid=False) _showlegends = [] _modes = [] _markerformats = [] _colors = [] for i, _x in enumerate(x): _showlegends_panel = [] _modes_panel = [] _markerformats_panel = [] _colors_panel = [] for j, _trace_x in enumerate(_x): _showlegends_panel.append(not(hide_all_legends)) _modes_panel.append('lines') _markerformats_panel.append(dict(size=5)) _colors_panel.append(DEFAULT_PLOTLY_COLORS[j]) _showlegends.append(_showlegends_panel) _modes.append(_modes_panel) _markerformats.append(_markerformats_panel) _colors.append(_colors_panel) if showlegends is None: showlegends = _showlegends if modes is None: modes = _modes if markerformats is None: markerformats = _markerformats if colors is None: colors = _colors _traces = [] log_dummy_trace = go.Scatter(x=[0, 1], y=[np.nan, np.nan], showlegend=False) _fig.append_trace(log_dummy_trace, 1, 1) for i, _x in enumerate(x): for j, _trace_x in enumerate(_x): try: _trace = go.Scatter( x=x[i][j], y=z[i][j], mode=modes[i][j], name=names[i][j], showlegend=showlegends[i][j], marker=markerformats[i][j], line=dict(color=colors[i][j])) _fig.append_trace(_trace, 1, i + 2) except Exception as err: warnings.warn( "Error during traces creation for trace %s - %s" % (names[i][j], str(traceback.format_exc()))) _layers = [] for i, row in soildata.iterrows(): _fillcolor = fillcolordict[row['Soil type']] _y0 = row[depth_from_key] _y1 = row[depth_to_key] _layers.append( dict(type='rect', xref='x1', yref='y', x0=0, y0=_y0, x1=1, y1=_y1, fillcolor=_fillcolor, opacity=1)) if zrange is None: _fig['layout']['yaxis1'].update(title=ztitle, autorange='reversed') else: _fig['layout']['yaxis1'].update(title=ztitle, range=zrange) _fig['layout'].update(layout) _fig['layout'].update(shapes=_layers) if ztick is not None: _fig['layout']['yaxis1'].update(dtick=ztick) _fig['layout']['xaxis1'].update( anchor='y', title=None, side='top', tickvals=[]) for i, _x in enumerate(x): _fig['layout']['xaxis%i' % (i + 2)].update( anchor='y', title=xtitles[i], side='top') if dticks is not None: _fig['layout']['xaxis%i' % (i + 2)].update(dtick=dticks[i]) if xranges is not None: _fig['layout']['xaxis%i' % (i + 2)].update(range=xranges[i]) if showfig: _fig.show(config=GROUNDHOG_PLOTTING_CONFIG) return _fig
[docs] class LogPlot(object): """ Class for planneled plots with a minilog on the side. """ def __init__(self, soilprofile, no_panels=1, logwidth=0.05, fillcolordict={"Sand": 'yellow', "Clay": 'brown', 'Rock': 'grey'}, soiltypelegend=True, soiltypecolumn="Soil type", line_width=1, **kwargs): """ Initializes a figure with a minilog on the side. :param soilprofile: Soilprofile used for the minilog :param no_panels: Number of panels :param logwidth: Width of the minilog as a ratio to the total width of the figure (default=0.05) :param fillcolordict: Dictionary with fill colors for each of the soil types. Every unique ``Soil type`` needs to have a corresponding color. Default: ``{"Sand": 'yellow', "Clay": 'brown', 'Rock': 'grey'}`` :param soiltypelegend: Boolean determining whether legend entries need to be shown for the soil types in the log :param soiltypecolumn: Column name used to identify the soil type. The entries in this column need to correspond to keys in ``fillcolordict`` :param line_width: Line width for the boundary between layers :param kwargs: Optional keyword arguments for the make_subplots method """ self.soilprofile = soilprofile # Determine the panel widths panel_widths = list(map(lambda _x: (1 - logwidth) / no_panels, range(0, no_panels))) panel_widths = list(np.append(logwidth, panel_widths)) # Set up the figure self.fig = subplots.make_subplots( rows=1, cols=no_panels+1, column_widths=panel_widths, shared_yaxes=True, print_grid=False, **kwargs) self.fig['layout']['yaxis1'].update(range=(soilprofile.max_depth, soilprofile.min_depth)) # Create rectangles for the log plot _layers = [] for i, row in soilprofile.iterrows(): try: _fillcolor = fillcolordict[row[soiltypecolumn]] except: _fillcolor = DEFAULT_PLOTLY_COLORS[i % 10] _y0 = row[self.soilprofile.depth_from_col] _y1 = row[self.soilprofile.depth_to_col] _layers.append( dict(type='rect', xref='x1', yref='y', x0=0, y0=_y0, x1=1, y1=_y1, fillcolor=_fillcolor, opacity=1, line_width=line_width)) for _soiltype in soilprofile[soiltypecolumn].unique(): try: _fillcolor = fillcolordict[_soiltype] except: soiltypelegend = False try: if soiltypelegend: _trace = go.Bar( x=[-10, -10], y=[row['Depth to [m]'], row['Depth to [m]']], name=_soiltype, marker=dict(color=_fillcolor)) self.fig.append_trace(_trace, 1, 1) except: pass self.fig['layout'].update(shapes=_layers) self.fig['layout']['xaxis1'].update( anchor='y', title=None, side='top', tickvals=[], range=(0, 1)) self.fig['layout']['yaxis1'].update(title='Depth [m]') for i in range(0, no_panels): _dummy_data = go.Scatter( x=[0, 100], y=[np.nan, np.nan], mode='lines', name='Dummy', showlegend=False, line=dict(color='black')) self.fig.append_trace(_dummy_data, 1, i + 2) self.fig['layout']['xaxis%i' % (i + 2)].update( anchor='y', title='X-axis %i' % (i+1), side='top')
[docs] def add_trace(self, x, z, name, panel_no, resetaxisrange=False, **kwargs): """ Adds a trace to the plot. By default, lines are added but optional keyword arguments can be added for go.Scatter as ``**kwargs`` :param x: Array with the x-values :param z: Array with the z-values :param name: Name for the trace (LaTeX allowed, e.g. ``r'$ \\alpha $'``) :param panel_no: Panel to plot the trace on (1-indexed) :param resetaxisrange: Boolean determining whether the axis range needs to be reset to fit this trace :param kwargs: Optional keyword arguments for the ``go.Scatter`` constructor :return: Adds the trace to the specified panel """ try: mode = kwargs['mode'] kwargs.pop('mode') except: mode = 'lines' _data = go.Scatter( x=x, y=z, mode=mode, name=name, **kwargs) self.fig.append_trace(_data, 1, panel_no + 1) if resetaxisrange: self.fig['layout']['xaxis%i' % (panel_no + 1)].update( range=(np.array(x).min(), np.array(x).max()))
[docs] def add_soilparameter_trace(self, parametername, panel_no, legendname=None, resetaxisrange=False, **kwargs): """ Adds a trace to the plot based on a soil parameter available in the SoilProfile. By default, lines are added but optional keyword arguments can be added for go.Scatter as ``**kwargs`` :param parametername: Name of the soil parameter (with units) e.g. ``'Su [kPa]'`` when ``'Su from [kPa]'`` and ``'Su to [kPa]'`` are available in the SoilProfile :param panel_no: Panel to plot the trace on (1-indexed) :param legendname: Name for the trace (LaTeX allowed, e.g. ``r'$ \\alpha $'``), default is None to use ``parametername`` :param resetaxisrange: Boolean determining whether the axis range needs to be reset to fit this trace :param kwargs: Optional keyword arguments for the ``go.Scatter`` constructor :return: Adds the trace to the specified panel """ if not parametername in self.soilprofile.soil_parameters(): raise ValueError("Soil parameter %s not encoded in the soil profile. Check soil profile definition and try again" % parametername) x = self.soilprofile.soilparameter_series(parametername)[1] z = self.soilprofile.soilparameter_series(parametername)[0] if legendname is not None: name = legendname else: name = parametername self.add_trace(x, z, name, panel_no, resetaxisrange, **kwargs)
[docs] def set_xaxis(self, title, panel_no, **kwargs): """ Changes the X-axis title of a panel :param title: Title to be set (LaTeX allowed, e.g. ``r'$ \alpha $'``) :param panel_no: Panel number (1-indexed) :param kwargs: Additional keyword arguments for the axis layout update function, e.g. ``range=(0, 100)`` :return: Adjusts the X-axis of the specified panel """ self.fig['layout']['xaxis%i' % (panel_no + 1)].update( title=title, **kwargs)
[docs] def set_zaxis(self, title, **kwargs): """ Changes the Z-axis :param title: Title to be set (LaTeX allowed, e.g. ``r'$ \alpha $'``) :param kwargs: Additional keyword arguments for the axis layout update function, e.g. ``range=(0, 100)`` :return: Adjusts the Z-axis """ self.fig['layout']['yaxis1'].update( title=title, **kwargs)
[docs] def set_size(self, width, height): """ Adjust the size of the plot :param width: Width of the plot in pixels :param height: Height of the plot in pixels :return: Adjust the height and width as specified """ self.fig['layout'].update(height=height, width=width)
[docs] def set_title(self, title): """ Set a title for the plot :param title: Title for the plot :return: Sets the title as specified """ self.fig['layout'].update(title=title)
def show(self): self.fig.show(config=GROUNDHOG_PLOTTING_CONFIG)
[docs] class LogPlotMatplotlib(object): """ Class for planneled plots with a minilog on the side, using the Matplotlib plotting backend """ def __init__(self, soilprofile, no_panels=1, logwidth=0.05, fillcolordict={"Sand": 'yellow', "Clay": 'brown', 'Rock': 'grey', 'Silt': 'green'}, hatchpatterns={"Sand": "...", "Clay": '////', 'Rock':'oo', 'Silt': '|||'}, soiltypelegend=True, soiltypecolumn='Soil type', edgecolor='black', figheight=6, plot_layer_transitions=True, showgrid=True, **kwargs): """ Initializes a figure with a minilog on the side. :param soilprofile: Soilprofile used for the minilog :param no_panels: Number of panels :param logwidth: Width of the minilog as a percentage of the total width (default=0.05) :param fillcolordict: Dictionary with fill colors for each of the soil types. Every unique ``Soil type`` needs to have a corresponding color. Default: ``{"Sand": 'yellow', "Clay": 'brown', 'Rock': 'grey'}`` :param hatchpatterns: Matplotlib letters used for hatching of the soil types :param soiltypelegend: Boolean determining whether legend entries need to be shown for the soil types in the log :param soiltypecolumn: Column name used to identify the soil type. The entries in this column need to correspond to keys in ``fillcolordict`` :param edgecolor: Color of the edge of a layer :param figheight: Figure height in inches (default=6in) :param plot_layer_transitions: Boolean determining whether layer transitions need to be plotted or not :param showgrid: Boolean determining whether a grid is shown on the plot panels or not (default=True) :param kwargs: Optional keyword arguments for the make_subplots method """ self.soilprofile = soilprofile self.no_panels = no_panels # Determine the panel widths panel_widths = list(map(lambda _x: (1 - logwidth) / no_panels, range(0, no_panels))) panel_widths = list(np.append(logwidth, panel_widths)) # Set up the figure self.fig, self.axes = plt.subplots(1, no_panels + 1, figsize=(4 * no_panels, figheight), sharex=False, sharey=True, constrained_layout=False, gridspec_kw={'width_ratios': panel_widths}) self.axes[0].set_ylim([soilprofile.max_depth, soilprofile.min_depth]) # Create rectangles for the log plot _layers = [] _color_assignment = dict() for i, row in soilprofile.iterrows(): try: _fillcolor = fillcolordict[row[soiltypecolumn]] _color_assignment[row[soiltypecolumn]] = _fillcolor except: if row[soiltypecolumn] in _color_assignment.keys(): _fillcolor = _color_assignment[row[soiltypecolumn]] else: _fillcolor = BRIGHTCOLORS[i % 7] _color_assignment[row[soiltypecolumn]] = _fillcolor try: _hatch = hatchpatterns[row[soiltypecolumn]] except: _hatch = None _y0 = row[self.soilprofile.depth_from_col] _y1 = row[self.soilprofile.depth_to_col] self.axes[0].fill( [0.0,0.0,1.0,1.0],[_y0, _y1, _y1, _y0], fill=True, color=_fillcolor, label='_nolegend_', edgecolor=edgecolor, hatch=_hatch) _legend_handles = [] for _soiltype in soilprofile[soiltypecolumn].unique(): try: _fillcolor = _color_assignment[_soiltype] except: soiltypelegend = False try: if soiltypelegend: _legend_entry, = self.axes[0].fill( [-11.0,-11.0,-10.0,-10.0],[_y0, _y1, _y1, _y0], fill=True, color=_fillcolor, label=_soiltype, edgecolor=edgecolor) _legend_handles.append(_legend_entry) except: pass self._legend_entries = _legend_handles self.axes[0].set_xlim([0, 1]) self.axes[0].get_xaxis().set_ticks([]) self.axes[0].set_ylabel('Depth below mudline [m]',size=15) for i in range(0, no_panels): _dummy_data = self.axes[i+1].plot([0, 100], [np.nan, np.nan], label='_nolegend_') self.axes[i+1].tick_params(labelbottom=False,labeltop=True) self.axes[i+1].set_xlabel('X-axis %i' % (i + 1), size=15) self.axes[i+1].xaxis.set_label_position('top') self.axes[i+1].set_xlim([0, 1]) self.axes[i+1].set_ylim([soilprofile.max_depth, soilprofile.min_depth]) self.plot_layer_transitions = plot_layer_transitions if showgrid: for i in range(0, no_panels): self.axes[i+1].grid() else: pass
[docs] def add_trace(self, x, z, name, panel_no, resetaxisrange=False, line=True, showlegend=False, **kwargs): """ Adds a trace to the plot. By default, lines are added but optional keyword arguments can be added for plt.plot as ``**kwargs`` :param x: Array with the x-values :param z: Array with the z-values :param name: Label for the trace (LaTeX allowed, e.g. ``r'$ \alpha $'``) :param panel_no: Panel to plot the trace on (1-indexed) :param resetaxisrange: Boolean determining whether the axis range needs to be reset to fit this trace :param line: Boolean determining whether the data needs to be shown as a line or as individual markers :param showlegend: Boolean determining whether the trace name needs to be added to the legend entries :param kwargs: Optional keyword arguments for the ``go.Scatter`` constructor :return: Adds the trace to the specified panel """ if line: _axes_obj = self.axes[panel_no].plot(x, z,label=name, **kwargs) else: _axes_obj = self.axes[panel_no].scatter(x, z,label=name, **kwargs) if resetaxisrange: self.axes[panel_no].set_xlim([x[~np.isnan(x)].min(), x[~np.isnan(x)].max()]) if showlegend: if line: self._legend_entries.append(_axes_obj[0]) else: self._legend_entries.append(_axes_obj)
[docs] def add_soilparameter_trace(self, parametername, panel_no, legendname=None, resetaxisrange=False, line=True, showlegend=False, **kwargs): """ Adds a trace to the plot based on a soil parameter available in the SoilProfile. By default, lines are added but optional keyword arguments can be added for plt.plot as ``**kwargs`` :param parametername: Name of the soil parameter (with units) e.g. ``'Su [kPa]'`` when ``'Su from [kPa]'`` and ``'Su to [kPa]'`` are available in the SoilProfile :param panel_no: Panel to plot the trace on (1-indexed) :param legendname: Label for the trace (LaTeX allowed, e.g. ``r'$ \\alpha $'``) :param resetaxisrange: Boolean determining whether the axis range needs to be reset to fit this trace :param line: Boolean determining whether the data needs to be shown as a line or as individual markers :param showlegend: Boolean determining whether the trace name needs to be added to the legend entries :param kwargs: Optional keyword arguments for the ``go.Scatter`` constructor :return: Adds the trace to the specified panel """ if not parametername in self.soilprofile.soil_parameters(): raise ValueError("Soil parameter %s not encoded in the soil profile. Check soil profile definition and try again" % parametername) x = self.soilprofile.soilparameter_series(parametername)[1] z = self.soilprofile.soilparameter_series(parametername)[0] if legendname is not None: name = legendname else: name = parametername self.add_trace(x, z, name, panel_no, resetaxisrange, line, showlegend, **kwargs)
[docs] def set_xaxis_title(self, title, panel_no, size=15, **kwargs): """ Changes the X-axis title of a panel :param title: Title to be set (LaTeX allowed, e.g. ``r'$ \alpha $'``) :param panel_no: Panel number (1-indexed) :param kwargs: Additional keyword arguments for the axis layout update function, e.g. ``range=(0, 100)`` :return: Adjusts the X-axis of the specified panel """ self.axes[panel_no].set_xlabel(title, size=size)
[docs] def set_xaxis_range(self, min_value, max_value, panel_no, ticks=None, **kwargs): """ Changes the X-axis range of a panel :param min_value: Minimum value of the plot panel range :param max_value: Maximum value of the plot panel range :param panel_no: Panel number (1-indexed) :param ticks: List of ticks to set (default=None for Matplotlib defaults) :param kwargs: Additional keyword arguments for the ``set_xlim`` method :return: Adjusts the X-axis range of the specified panel """ self.axes[panel_no].set_xlim([min_value, max_value]) if ticks is not None: self.axes[panel_no].set_xticks(ticks)
[docs] def set_zaxis_title(self, title, size=15, **kwargs): """ Changes the Z-axis :param title: Title to be set (LaTeX allowed, e.g. ``r'$ \alpha $'``) :param kwargs: Additional keyword arguments for the ``set_label`` method :return: Adjusts the Z-axis title """ self.axes[0].set_ylabel(title, size=size)
[docs] def set_zaxis_range(self, min_depth, max_depth, ticks=None, **kwargs): """ Changes the Z-axis :param min_depth: Minimum depth of the plot :param max_depth: Maximum depth of the plot :param ticks: List of ticks to set (default=None for Matplotlib defaults) :param kwargs: Additional keyword arguments for the ``set_ylim`` method :return: Adjusts the Z-axis range """ self.axes[0].set_ylim([max_depth, min_depth]) if ticks is not None: self.axes[0].set_yticks(ticks)
[docs] def set_size(self, width, height): """ Adjust the size of the plot :param width: Width of the plot in inches :param height: Height of the plot in inches :return: Adjust the height and width as specified """ plt.gcf().set_size_inches(width, height)
def show_legend(self): plt.legend(handles=self._legend_entries, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) def plot_layers(self): for i in range(0, self.no_panels): for _y in self.soilprofile.layer_transitions(): self.axes[i+1].plot( self.axes[i+1].get_xlim(), (_y, _y), color='grey', ls="--" ) def show(self, showlegend=True, showfig=True): if self.plot_layer_transitions: self.plot_layers() else: pass if showlegend: self.show_legend() else: pass if showfig: plt.show() else: pass
[docs] def save_fig(self, path, dpi=250, bbox_inches='tight',pad_inches=1): """ Exports the figure to png format :param path: Path of the figure (filename ends in .png) :param dpi: Output resolution :param bbox_inches: Setting for the bounding box :param pad_inches: Inches for padding """ plt.savefig(path, dpi=dpi,bbox_inches=bbox_inches, pad_inches=pad_inches)
[docs] def select_additional_layers(self, no_additional_layers, panel_no=1, precision=2): """ Allows for the selection of additional layer transitions for the ``SoilProfile`` object. The number of additional transition is controlled by the ``no_additional_layers`` argument. Click on the desired layer transition location in the specified panel (default ``panel_no=1``) The depth of the layer transition is rounded according to the ``precision`` argument. Default=2 for cm accuracy.""" ax = self.axes[panel_no] xy = plt.ginput(no_additional_layers, timeout=120) x = [p[0] for p in xy] y = [round(p[1], precision) for p in xy] for _y in y: for i in range(self.axes.__len__() - 1): line = self.axes[i+1].plot( self.axes[i+1].get_xlim(), (_y, _y), color='grey', ls="--") self.soilprofile.insert_layer_transition(_y) ax.figure.canvas.draw()
[docs] def select_layering(self, panel_no=1, precision=2, stop_threshold=0): """ Allows for the selection of layer transitions for the ``SoilProfile`` object. The number of additional transition is controlled by how often the user clicks. Click on the desired layer transition location in the specified panel (default ``panel_no=1``). The selection stops when the user clicks on a point with x-coordinate below the ``stop_threshold``. The depth of the layer transition is rounded according to the ``precision`` argument. Default=2 for cm accuracy.""" ax = self.axes[panel_no] final = False while not final: xy = plt.ginput(1, timeout=120) x = [p[0] for p in xy] y = [round(p[1], precision) for p in xy] if x[0] < stop_threshold: final = True else: for _y in y: for i in range(self.axes.__len__() - 1): line = self.axes[i+1].plot( self.axes[i+1].get_xlim(), (_y, _y), color='grey', ls="--") self.soilprofile.insert_layer_transition(_y) ax.figure.canvas.draw()
[docs] def select_constant(self, panel_no, parametername, units, nan_tolerance=0.1): """ Selects a constant value in each layer. Click the desired value in each layer, working from the top down. If a nan value needs to be set in a layer, click sufficiently close to the minimum of the x axis. The ``nan_tolerance`` argument determines which values are interpreted as nan. The parameter is added to the ``SoilProfile`` object with the ``'parametername [units]'`` key. """ ax = self.axes[panel_no] xy = plt.ginput(self.soilprofile.__len__(), timeout=120) x = [p[0] for p in xy] y = [p[1] for p in xy] for i, _x in enumerate(x): if _x < nan_tolerance: x[i] = np.nan self.soilprofile["%s [%s]" % (parametername, units)] = x self.add_soilparameter_trace( parametername="%s [%s]" % (parametername, units), panel_no=panel_no) ax.figure.canvas.draw()
[docs] def select_linear(self, panel_no, parametername, units, nan_tolerance=0.1): """ Selects a linear variation in each layer. Click the desired value at each layer boundary. Note that a value needs to be selected at the top and bottom of each layer (2 x no layers clicks). If a nan value needs to be set in a layer, click sufficiently close to the minimum of the x axis. The ``nan_tolerance`` argument determines which values are interpreted as nan. The parameter is added to the ``SoilProfile`` object with the ``'parametername [units]'`` key. """ ax = self.axes[panel_no] xy = plt.ginput(2 * self.soilprofile.__len__(), timeout=120) x = [p[0] for p in xy] y = [p[1] for p in xy] for i, _x in enumerate(x): if _x < nan_tolerance: x[i] = np.nan self.soilprofile["%s from [%s]" % (parametername, units)] = x[::2] self.soilprofile["%s to [%s]" % (parametername, units)] = x[1::2] self.add_soilparameter_trace( parametername="%s [%s]" % (parametername, units), panel_no=panel_no) ax.figure.canvas.draw()
[docs] def peak_picker(x, y, correct_selected_point=True): """ Generates an interactive Matplotlib plot which allows you to pick the peak from a graph with e.g. load-displacement data :param x: Array with x-values :param y: Array with y-values :param correct_selected_point: Boolean determining whether a correction is applied to interpolate the peak based on the selected value of X for the peak :returns: Dictionary with the following keys: - 'x100': x-coordinate of the peak - 'y100': y-coordinate of the peak - 'x50': x-coordinate where y reached 50% of its peak y - 'y50': y-coordinate with y equal to 50% of the peak y """ # Coerce to Numpy arrays x = np.array(x) y = np.array(y) # Close all open figures plt.close('all') # Generate the figure for peak picking plt.figure(1, figsize=(8,12)) plt.plot(x, y) plt.xlabel('$ x $', size=15) plt.ylabel('$ y $', size=15) # Click on the peak xy = plt.ginput(1, timeout=120) # Calculate derived quantities x100 = xy[0][0] if correct_selected_point: y100 = np.interp(x100, x, y) else: y100 = xy[0][1] prepeak_x = x[np.where(x <= x100)] prepeak_y = y[np.where(x <= x100)] y50 = 0.5 * y100 x50 = np.interp(y50, prepeak_y, prepeak_x) plt.scatter([x50, x100], [y50, y100], c='red') return { 'x100': x100, 'y100': y100, 'x50': x50, 'y50': y50, 'plot': plt.gcf() }