Source code for node_scatter

# Copyright (c) 2013, 2017, System Engineering Software Society
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the System Engineering Software Society nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.
# IN NO EVENT SHALL SYSTEM ENGINEERING SOFTWARE SOCIETY BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
This node is for visualising data in 3D.

In the configuration Table columns are selected along the
axes in the plots. There exist differences between the nodes how to do this,
but the basic principle is the same. For the actual plots is possible to change
both line/marker style and
plot style in the plot. Below, the available plot styles are listed. A plot
legend is, by default, shown in the plot, but can be hidden by a simple push of
a button. The navigation toolbar under the plot let the user zoom and pan the
plot window.

Available plot types (3D):
    - scatter
    - surf
    - wireframe
    - plot
    - contour
    - heatmap

The advanced plot controller allows the user to draw two lines parallel to
the Y-axis. These can be moved along the X-axis while information about the
intersection points between these lines and the plotted data points is shown
in a table. If a line is drawn in between two points in the plotted data, the
line will always move to the closest point.
"""
from __future__ import (print_function, division, unicode_literals,
                        absolute_import)
import os
import numpy as np
import sys
import six

from sympathy.api import table
from sympathy.api import datasource as dsrc
from sympathy.api import node as synode
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags, deprecated_node
from sympathy.api.exceptions import NoDataError
from sympathy.api import qt as qt_compat
from sylib.util import mock_wrap

from matplotlib.backends.backend_qt4agg import (
    FigureCanvasQTAgg as FigureCanvas)
from matplotlib.backends.backend_agg import (
    FigureCanvasAgg as FigureCanvasNonInteractive)
from matplotlib.backends.backend_qt4agg import (
    NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure

# For 3D plot
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

import warnings
warnings.simplefilter("ignore", UserWarning)
warnings.simplefilter("ignore", FutureWarning)

QtCore = qt_compat.QtCore
QtGui = qt_compat.import_module('QtGui')
qt_compat.backend.use_matplotlib_qt()


def reinit_2d(node_context):
    parameter_root = synode.parameters(node_context.parameters)
    parameter_root['tb_names'].list = []
    parameter_root['tb_names'].value = []
    parameter_root['x_axis'].list = []
    parameter_root['x_axis'].value = []
    parameter_root['x_axis'].value_names = []
    parameter_root['y_axis'].list = []
    parameter_root['y_axis'].value = []
    parameter_root['y_axis'].value_names = []
    parameter_root['line_style'].value = []
    parameter_root['plot_func'].value = []
    parameter_root['filename_extension'].list = []
    parameter_root['filename_extension'].value = []
    parameter_root['advanced_controller'].value = False
    parameter_root['t0'].value = ''
    parameter_root['t1'].value = ''
    parameter_root['visible_legend'].value = True
    parameter_root['x_min'].value = 0.0
    parameter_root['x_max'].value = 0.0
    parameter_root['y_min'].value = 0.0
    parameter_root['y_max'].value = 0.0
    parameter_root['title'].value = ''
    parameter_root['xlabel'].value = ''
    parameter_root['ylabel'].value = ''


def reinit_2dmult(node_context):
    parameter_root = synode.parameters(node_context.parameters)
    parameter_root['x_axis'].list = []
    parameter_root['x_axis'].value = []
    parameter_root['x_axis'].value_names = []
    parameter_root['y_axis'].list = []
    parameter_root['y_axis'].value = []
    parameter_root['y_axis'].value_names = []
    parameter_root['line_style'].value = [0]
    parameter_root['plot_func'].value = [0]
    parameter_root['filename_extension'].list = []
    parameter_root['filename_extension'].value = []
    parameter_root['display_strings']['plot_list'].list = []
    parameter_root['display_strings']['tb_names'].list = []
    parameter_root['display_strings']['ts_names'].list = []
    parameter_root['visible_legend'].value = True


def reinit_3d(node_context):
    """Reinitialize parameter_root for 3d plot. Ex. when file datasource has
    changed.
    """
    parameter_root = synode.parameters(node_context.parameters)
    parameter_root['tb_names'].list = []
    parameter_root['tb_names'].value = []
    parameter_root['x_axis'].list = []
    parameter_root['x_axis'].value = []
    parameter_root['x_axis'].value_names = []
    parameter_root['y_axis'].list = []
    parameter_root['y_axis'].value = []
    parameter_root['y_axis'].value_names = []
    parameter_root['z_axis'].list = []
    parameter_root['z_axis'].value = []
    parameter_root['z_axis'].value_names = []
    parameter_root['line_style'].value = [0]
    parameter_root['plot_func'].value = [0]
    parameter_root['filename_extension'].list = []
    parameter_root['filename_extension'].value = []


def inf_fq_filename(directory, filename):
    return os.path.join(directory, filename)


def create_filenames_from_parameters(parameter_root, index=None,
                                     table_name=None):
    export_directory = parameter_root['directory'].value
    filename = parameter_root['filename'].value
    extension = parameter_root['filename_extension'].selected
    if table_name is not None:
        complete_filename = '{}{}'.format(table_name, extension)
    else:
        if index is not None:
            complete_filename = '{}_{}{}'.format(filename, index, extension)
        else:
            complete_filename = '{}{}'.format(filename, extension)
    fq_filename = inf_fq_filename(export_directory, complete_filename)
    return fq_filename


def get_adaf_info(adaffile):
    table_list = table.FileList()
    for system in adaffile.sys.keys():
        for basis in adaffile.sys[system].keys():
            basis_table = adaffile.sys[system][basis].to_table()
            basis_table.set_name('{}/{}'.format(system, basis))
            table_list.append(basis_table)
    return table_list


def get_adaf_info_timebasis(adaffile):
    """Get information about adaf"""
    adaf_ts = adaffile.ts
    tb_dict = {}
    tb_ts_dict = {}
    tb_name_dict = {}
    tb_tsunits_dict = {}

    for ts_key, ts in adaf_ts.items():
        tb_name = ts.system_name() + '/' + ts.raster_name()
        try:
            tb_name_dict[tb_name]
        except:
            tb_name_dict[tb_name] = ts.basis().name()
        try:
            ts_dict = tb_ts_dict[tb_name]
            ts_dict[ts_key] = ts.y
        except:
            tb_ts_dict[tb_name] = {ts_key: ts.y}
        try:
            tb_dict[tb_name]
        except:
            tb_dict[tb_name] = ts.t
        try:
            tb_tsunits_dict[tb_name]
            tb_tsunits_dict[tb_name][ts_key] = ts.unit()
        except:
            tb_tsunits_dict[tb_name] = {ts_key: ts.unit()}
        tb_tsunits_dict[tb_name][tb_name] = 'unknown'  # FIXA SEN!!
    return tb_ts_dict, tb_name_dict, tb_dict, tb_tsunits_dict


class NavigationToolbarCustom(NavigationToolbar):
    zoomChanged = qt_compat.Signal(float, float, float, float)
    zoomActive = qt_compat.Signal(bool)
    panActive = qt_compat.Signal(bool)
    panDragged = qt_compat.Signal(float, float, float, float)
    parameters_edited = qt_compat.Signal()
    home_clicked = qt_compat.Signal()
    _home = True

    def __init__(self, canvas, parent):
        NavigationToolbar.__init__(self, canvas, parent)
        self._forward = False
        self._back = False

    def draw(self):
        super(NavigationToolbarCustom, self).draw()
        if self._xypress is None or self._home:
            home_view = self._views.home()
            x_min, x_max = home_view[0][0:2]
            y_min, y_max = home_view[0][2:4]
        elif self._forward:
            self._views.back()
            forward_view = self._views.forward()
            x_min, x_max = forward_view[0][0:2]
            y_min, y_max = forward_view[0][2:4]
        elif self._back:
            self._views.forward()
            back_view = self._views.back()
            x_min, x_max = back_view[0][0:2]
            y_min, y_max = back_view[0][2:4]
        else:
            if self._active == 'PAN':
                a = self._xypress_save[0][0].figure.axes[0]
            elif self._active == 'ZOOM':
                _, _, a, _, _, _ = self._xypress[0]
            else:
                a = self.canvas.figure.get_axes()
            x_min, x_max = a.get_xlim()
            y_min, y_max = a.get_ylim()
        self.parameters_edited.emit()
        self.zoomChanged.emit(x_min, x_max, y_min, y_max)

    def pan(self, *args):
        super(NavigationToolbarCustom, self).pan()
        self.panActive.emit(self._active == 'PAN')

    def zoom(self, *args):
        super(NavigationToolbarCustom, self).zoom()
        self.zoomActive.emit(self._active == 'ZOOM')

    def home(self, *args):
        self._home = True
        super(NavigationToolbarCustom, self).home()
        self._home = False
        self.home_clicked.emit()
        self.parameters_edited.emit()

    def release_zoom(self, event):
        super(NavigationToolbarCustom, self).release_zoom(event)
        self._home = False
        self._xypress_save = None
        self.parameters_edited.emit()

    def forward(self, *args):
        self._forward = True
        super(NavigationToolbarCustom, self).forward(*args)
        self._forward = False

    def back(self, *args):
        self._back = True
        super(NavigationToolbarCustom, self).back(*args)
        self._back = False

    def release_pan(self, event):
        self._xypress_save = self._xypress
        super(NavigationToolbarCustom, self).release_pan(event)
        self._home = False
        self._xypress_save = None

    def drag_pan(self, event):
        a, ind = self._xypress[0]
        super(NavigationToolbarCustom, self).drag_pan(event)
        x_min, x_max = a.get_xlim()
        y_min, y_max = a.get_ylim()
        self.parameters_edited.emit()
        self.panDragged.emit(x_min, x_max, y_min, y_max)

    def edit_parameters(self):
        super(NavigationToolbarCustom, self).edit_parameters()
        self.parameters_edited.emit()


class FigureCanvasCustom(FigureCanvas):
    canvasResized = qt_compat.Signal()

    def __init__(self, canvas):
        super(FigureCanvasCustom, self).__init__(canvas)

    def resizeEvent(self, event):
        FigureCanvas.resizeEvent(self, event)
        self.canvasResized.emit()


class SuperNode(object):
    author = 'Helena Olen <helena.olen@combine.se>'
    copyright = '(C) 2013 System Engineering Software Society'
    version = '1.0'
    icon = 'scatter2d.svg'


class SuperTableNode(SuperNode):
    inputs = Ports(
        [Port.Table('Input Table', name='port1', requiresdata=True)])


class SuperTablesNode(SuperNode):
    inputs = Ports(
        [Port.Tables('Input Tables', name='port1', requiresdata=True)])


class SuperADAFNode(SuperNode):
    inputs = Ports([Port.ADAF('Input ADAF', name='port1', requiresdata=True)])


class Super2dNode(SuperNode):
    parameters = synode.parameters()
    parameters.set_list(
        'tb_names', label='Time basis',
        description='Combo of all timebasis.',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'x_axis', label='X-axis',
        description='X-axis selection for plot.',
        editor=synode.Util.combo_editor().value())
    y_axis_editor = synode.Util.list_editor()
    y_axis_editor.set_attribute('filter', True)
    y_axis_editor.set_attribute('selection', 'multi')
    parameters.set_list(
        'y_axis', label='Y-axis', description='Y-axis selection for plot',
        editor=y_axis_editor.value())
    parameters.set_list(
        'line_style', label='Line style',
        plist=['o', '--', '-', '^', '*', '-o', '-*', '.', ':'],
        description='Selectable line styles.',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'plot_func', label='Plot type',
        plist=['plot', 'step', 'fill', 'hist bar', 'hist step'],
        description='Selectable plot types.',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'filename_extension',
        description='Filename extension.',
        editor=synode.Util.combo_editor().value())
    parameters.set_boolean(
        'advanced_controller', value=False,
        label='Use advanced plot controller',
        decription='Advanced plot controller option.')
    parameters.set_string('t0', value='')
    parameters.set_string('t1', value='')
    parameters.set_float('x_min', value=0.0)
    parameters.set_float('x_max', value=0.0)
    parameters.set_float('y_min', value=0.0)
    parameters.set_float('y_max', value=0.0)
    parameters.set_string('title', value='')
    parameters.set_string('ylabel', value='')
    parameters.set_string('xlabel', value='')
    parameters.set_boolean('visible_legend', value=True)


class Super2dMultTbNode(SuperNode):
    parameters = synode.parameters()
    parameters.set_list(
        'x_axis', label='X-axis',
        description='X-axis selection for plot.',
        editor=synode.Util.combo_editor().value())
    y_axis_editor = synode.Util.list_editor()
    y_axis_editor.set_attribute('filter', True)
    y_axis_editor.set_attribute('selection', 'multi')
    parameters.set_list(
        'y_axis', label='Y-axis',
        description='Y-axis selection for plot.',
        editor=y_axis_editor.value())
    parameters.set_list(
        'line_style', label='Line style',
        plist=['o', '--', '-', '^', '*', '-o', '-*', '.', ':'],
        description='Selectable line styles.',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'plot_func', label='Plot type',
        plist=['plot', 'step', 'fill', 'hist bar', 'hist step'],
        description='Selectable plot types.',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'filename_extension',
        description='Filename extension.',
        editor=synode.Util.combo_editor().value())


class Super3dNode(SuperNode):
    icon = 'scatter3d.svg'
    parameters = synode.parameters()
    parameters.set_list(
        'tb_names', label='Time basis',
        description='Combo of all timebasis',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'x_axis', label='X axis',
        description='X axis selection for plot',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'y_axis', label='Y axis',
        description='Y axis selection for plot',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'z_axis', label='Z axis',
        description='Z axis selection for plot',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'line_style', label='Line style',
        plist=['o', '^', '*'],
        description='Selectable line styles',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'plot_func', label='Plot type',
        plist=['scatter', 'surf', 'wireframe', 'plot', 'contour', 'heatmap'],
        description='Selectable plot types',
        editor=synode.Util.combo_editor().value())
    parameters.set_list(
        'filename_extension', label='File extension',
        description='Filename extension',
        editor=synode.Util.combo_editor().value())


[docs]@deprecated_node('1.5.0', 'Figure from Table') class Scatter2dNode(Super2dNode, SuperTableNode, synode.Node): """ Plot data in Table in two dimensions. :Input: **TableInput** : Table Table with data to visualise :Configuration: **X-axis** Select column along the X-axis. **Y-axis** Select columns along the Y-axis. Here, it is possible to select one or many columns. In the plot the columns are separated with different colors. **Line style** Select line style used in the plot. **Plot type** Select plot type for the plot. **Show/hide legend** Turn on/off the legend in the plot window. **Output directory** Specify where in the file-tree to store an exported plot. **Filename** Specify filename and data format of an exported plot. :Ref. nodes: :ref:`Scatter 2D Tables` """ name = 'Scatter 2D Table' description = 'A two dimensional scatter plot' nodeid = 'org.sysess.sympathy.visualize.scatter2dnode' def __init__(self): super(Scatter2dNode, self).__init__() def verify_parameters(self, node_context): parameters = node_context.parameters parameter_root = synode.parameters(parameters) try: filename = parameter_root['filename'].value directory = parameter_root['directory'].value if filename == '' or directory == '': return False except: return False return True def exec_parameter_view(self, node_context): """Create the parameter view""" table = [] in_table = [] try: if ('port1' in node_context.input and node_context.input['port1'].is_valid()): if len(node_context): in_table = node_context.input['port1'] else: in_table = node_context.input.ELEMENT() table.append(in_table) except NoDataError: # When no input is connected pass try: return Scatter2dWidget(node_context, table) except: reinit_2d(node_context) return Scatter2dWidget(node_context, table) def execute(self, node_context): """Execute""" fq_filename = create_filenames_from_parameters(node_context.parameters) fig = Figure() FigureCanvasNonInteractive(fig) axes = fig.add_subplot(111) tablefile = node_context.input['port1'] tabledata = table.FileList() tabledata.append(tablefile) plot_widget = Scatter2dPlot( tabledata, node_context.parameters, axes, index_name='Index') plot_widget.update_figure() fig.savefig(fq_filename)
[docs]@deprecated_node('1.5.0', 'Figures from Tables') class Scatter2dNodeMultiple(Super2dNode, SuperTablesNode, synode.Node): """ Plot data in Tables in two dimensions. :Input: **TableInput** : Tables Table with data to visualise :Configuration: **X-axis** Select column along the X-axis. **Y-axis** Select columns along the Y-axis. Here, it is possible to select one or many columns. In the plot the columns are separated with different colors. **Line style** Select line style used in the plot. **Plot type** Select plot type for the plot. **Show/hide legend** Turn on/off the legend in the plot window. **Output directory** Specify where in the file-tree to store an exported plot. **Filename** Specify filename and data format of an exported plot. :Ref. nodes: :ref:`Scatter 2D Table` """ name = 'Scatter 2D Tables' description = 'A two dimensional scatter plot' nodeid = 'org.sysess.sympathy.visualize.scatter2dmulti' def __init__(self): super(Scatter2dNodeMultiple, self).__init__() def verify_parameters(self, node_context): try: filename = node_context.parameters['filename'].value directory = node_context.parameters['directory'].value if filename == '' or directory == '': return False except: return False return True def exec_parameter_view(self, node_context): """Create the parameter view.""" tabledata = table.FileList() try: in_file = node_context.input['port1'] if len(in_file): tablefile = in_file[0] tabledata.append(tablefile) except NoDataError: # When no input is connected pass try: return Scatter2dWidget(node_context, tabledata) except: reinit_2d(node_context) return Scatter2dWidget(node_context, tabledata) def execute(self, node_context): """Execute""" parameters = node_context.parameters dsrc_list = node_context.output['port1'] input_list = node_context.input['port1'] num_files = len(input_list) for idx, infile in enumerate(input_list): fq_filename = create_filenames_from_parameters( parameters, index=idx, table_name=infile.get_name()) fig = Figure() FigureCanvasNonInteractive(fig) axes = fig.add_subplot(111) tabledata = table.FileList() tabledata.append(infile) plot_widget = Scatter2dPlot( tabledata, parameters, axes, index_name='Index') plot_widget.update_figure() fig.savefig(fq_filename) with dsrc.File() as dsrc_file: dsrc_file.encode_path(fq_filename) dsrc_list.append(dsrc_file) self.set_progress(100.0 * (1 + idx) / num_files)
[docs]@deprecated_node('1.5.0', 'Figure from Table') class Scatter2dNodeADAF(Super2dNode, SuperADAFNode, synode.Node): """ Plot data in ADAF in two dimensions. This node plots only signals that share a common timebasis. :Input: **TableInput** : ADAF ADAF with data to visualise :Configuration: **Time basis** Select time basis that is shared for the signals you want to plot. **X-axis** Select signal along the X-axis. **Y-axis** Select signals along the Y-axis. Here, it is possible to select one or many columns. In the plot the columns are separated with different colors. **Line style** Select line style used in the plot. **Plot type** Select plot type for the plot. **Show/hide legend** Turn on/off the legend in the plot window. **Output directory** Specify where in the file-tree to store an exported plot. **Filename** Specify filename and data format of an exported plot. :Ref. nodes: :ref:`Scatter 2D ADAF with multiple timebases` """ name = 'Scatter 2D ADAF' description = 'A two dimensional scatter plot' nodeid = 'org.sysess.sympathy.visualize.scatter2dnodeadaf' def __init__(self): super(Scatter2dNodeADAF, self).__init__() def exec_parameter_view(self, node_context): """Create the parameter view""" in_table = [] try: if ('port1' in node_context.input and node_context.input['port1'].is_valid()): in_table = get_adaf_info(node_context.input['port1']) except NoDataError: # When no data is connected pass try: return Scatter2dWidget(node_context, in_table) except: reinit_2d(node_context) return Scatter2dWidget(node_context, in_table) def verify_parameters(self, node_context): try: filename = node_context.parameters['filename'].value directory = node_context.parameters['directory'].value if filename == '' or directory == '': return False except: return False return True def execute(self, node_context): """Execute""" fq_filename = create_filenames_from_parameters(node_context.parameters) fig = Figure() FigureCanvasNonInteractive(fig) axes = fig.add_subplot(111) tables = get_adaf_info(node_context.input['port1']) plot_widget = Scatter2dPlot( tables, node_context.parameters, axes, index_name='Index') plot_widget.update_figure() fig.savefig(fq_filename)
[docs]@deprecated_node('1.5.0', 'Figure from Table') class Scatter2dNodeADAFMultipleTb(Super2dMultTbNode, SuperADAFNode, synode.Node): """ Plot data in ADAF in two dimensions. Compared to :ref:`Scatter 2D ADAF` this node can handle signals connected to different timebases in the same plot. :Input: **TableInput** : ADAF ADAF with data to visualise :Configuration: **X-axis** Select timebasis along the X-axis. **Y-axis** Select signals along the Y-axis that is connected to the selected timebasis along the X-axis. **Add selection to plot list** : button Add selected combinations of timebasis along X-axis and signals along the Y-axis to plot list. It is first when the combinations appear in the plot list they will be drawn in the plot window. **Remove plot line** : button When pushed the marked combinations in the plot list will be removed from both the plot list and the plot window. **Line style** Select line style used in the plot. **Plot type** Select plot type for the plot. **Show/hide legend** Turn on/off the legend in the plot window. **Output directory** Specify where in the file-tree to store an exported plot. **Filename** Specify filename and data format of an exported plot. :Ref. nodes: :ref:`Scatter 2D ADAF` """ name = 'Scatter 2D ADAF with multiple timebases' description = 'A two dimensional plot with multiple timebases.' nodeid = 'org.sysess.sympathy.visualize.scatter2dnodeadafmultipletb' def __init__(self): super(Scatter2dNodeADAFMultipleTb, self).__init__() def exec_parameter_view(self, node_context): """Create the parameter view""" tb_ts_dict = None tb_name_dict = None tb_dict = None units = None try: with node_context.input['port1'] as adaffile: (tb_ts_dict, tb_name_dict, tb_dict, units) = ( get_adaf_info_timebasis(adaffile)) except NoDataError: # when no input is connected pass try: return Scatter2dWidgetMultipleTb( node_context, tb_ts_dict, tb_name_dict, tb_dict, units) except: reinit_2dmult(node_context) return Scatter2dWidgetMultipleTb( node_context, tb_ts_dict, tb_name_dict, tb_dict, units) def verify_parameters(self, node_context): try: filename = node_context.parameters['filename'].value directory = node_context.parameters['directory'].value if filename == '' or directory == '': return False except: return False return True def execute(self, node_context): """Execute""" fq_filename = create_filenames_from_parameters(node_context.parameters) fig = Figure() FigureCanvasNonInteractive(fig) axes = fig.add_subplot(111) with node_context.input['port1'] as adaffile: (tb_ts_dict, tb_name_dict, tb_dict, units) = ( get_adaf_info_timebasis(adaffile)) plot_widget = Scatter2dPlotMultipleTb( node_context.parameters, axes, tb_ts_dict, tb_name_dict, tb_dict, units) plot_widget.update_figure() fig.savefig(fq_filename)
[docs]class Scatter3dNode(Super3dNode, SuperTableNode, synode.Node): name = 'Scatter 3D Table' description = 'A three dimensional plot' nodeid = 'org.sysess.sympathy.visualize.scatter3dnode' tags = Tags(Tag.Visual.Plot) outputs = Ports([Port.Datasource('Output file', name='port2')]) def verify_parameters(self, node_context): parameters = node_context.parameters parameter_root = synode.parameters(parameters) try: filename = parameter_root['filename'].value directory = parameter_root['directory'].value if filename == '' or directory == '': return False except: return False return True def exec_parameter_view(self, node_context): """Create the parameter view""" tabledata = None try: with node_context.input['port1'] as tablefile: tabledata = tablefile.to_recarray() except NoDataError: # When no input is connected pass try: return Scatter3dWidget(node_context, [tabledata]) except: reinit_3d(node_context) return Scatter3dWidget(node_context, [tabledata]) def execute(self, node_context): """Execute""" parameter_root = synode.parameters(node_context.parameters) fq_filename = create_filenames_from_parameters(parameter_root) fig = Figure() FigureCanvasNonInteractive(fig) axes = Axes3D(fig) with node_context.input['port1'] as tablefile: tabledata = tablefile.to_recarray() plot_widget = Plot3d( [tabledata], parameter_root, fig, axes) plot_widget.update_figure() fig.savefig(fq_filename) node_context.output['port2'].encode_path(fq_filename)
[docs]@deprecated_node('1.5.0', 'Scatter 3D Table') class Scatter3dNodeADAF(Super3dNode, SuperADAFNode, synode.Node): """ Plot data in ADAF in three dimensions. This node plots only signals that share a common timebasis. :Input: **TableInput** : ADAF ADAF with data to visualise :Configuration: **Time basis** Select time basis that is shared for the signals you want to plot. **X-axis** Select signal along the X-axis. **Y-axis** Select signal along the Y-axis. **Z-axis** Select signal along the Z-axis. **Line style** Select line style used in the plot. **Plot type** Select plot type for the plot. **Output directory** Specify where in the file-tree to store an exported plot. **Filename** Specify filename and data format of an exported plot. :Ref. nodes: :ref:`Scatter 2D ADAF` """ name = 'Scatter 3D ADAF' description = 'A three dimensional plot' nodeid = 'org.sysess.sympathy.visualize.scatter3dnodeadaf' def __init__(self): super(Scatter3dNodeADAF, self).__init__() def exec_parameter_view(self, node_context): """Create the parameter view""" tables = None names = None names_short = None units = None in_table = [] name = None name_short = None unit = None try: with node_context.input['port1'] as adaffile: tables = get_adaf_info(adaffile) in_table.extend([table.to_recarray() for table in tables]) _, names, names_short, units = get_adaf_info_timebasis( adaffile) name = names[0].to_recarray() name_short = names_short[0].to_recarray() unit = units[0].to_recarray() except (NoDataError, ValueError, KeyError): # When no input is connected pass try: return Scatter3dWidget( node_context, in_table, name, name_short, unit) except: reinit_3d(node_context) return Scatter3dWidget( node_context, in_table, name, name_short, unit) def verify_parameters(self, node_context): parameters = node_context.parameters parameter_root = synode.parameters(parameters) try: filename = parameter_root['filename'].value directory = parameter_root['directory'].value if filename == '' or directory == '': return False except: return False return True def execute(self, node_context): """Execute""" parameter_root = synode.parameters(node_context.parameters) fq_filename = create_filenames_from_parameters(parameter_root) fig = Figure() FigureCanvasNonInteractive(fig) axes = Axes3D(fig) with node_context.input['port1'] as adaffile: (tables, _, names_short, units) = get_adaf_info_timebasis(adaffile) try: tables = tables.to_recarray() except AttributeError: tables = None plot_widget = Plot3d( [tables], parameter_root, fig, axes, names_short, units) plot_widget.update_figure() fig.savefig(fq_filename)
@mock_wrap class PlotWidget(QtGui.QWidget): def __init__(self, node_context): super(PlotWidget, self).__init__() self._node_context = node_context self._parameter_root = synode.parameters(node_context.parameters) self._x_axis_combobox = None self._y_axis = None self._line_style_combobox = None self._plot_combobox = None self._file_extension_combo = None self._outputs_hlayout = None self._projection = None self._background = None self._figure = None self._axes = None self._canvas = None self._toolbar = None self._plot = None def _init_gui(self): pass def _pre_init_gui_from_parameters(self): pass def _init_gui_from_parameters(self): pass def _create_figure_gui(self): if sys.platform == 'darwin': backgroundcolor = '#ededed' else: backgroundcolor = self.palette().color( QtGui.QPalette.Window).name() self._figure = Figure(facecolor=backgroundcolor) self._create_subplot() self._create_canvas_tool() def _create_subplot(self): """To be implemented by subclasses""" try: self._axes = self._figure.add_subplot( 111, projection=self._projection) except ValueError: pass def _create_canvas_tool(self): """Create canvas and navigation toolbar.""" self._canvas = FigureCanvasCustom(self._figure) policy = QtGui.QSizePolicy() policy.setHorizontalStretch(1) policy.setVerticalStretch(1) policy.setHorizontalPolicy(QtGui.QSizePolicy.Expanding) policy.setVerticalPolicy(QtGui.QSizePolicy.Expanding) self._canvas.setSizePolicy(policy) self._toolbar = NavigationToolbarCustom(self._canvas, self) def _create_output_layout(self): """Create output layout with directory edit, file editor and file extension combo. """ try: self._parameter_root['directory'] except KeyError: self._parameter_root.set_string( "directory", label="Output directory", description="Select the directory where to export the files.", editor=synode.Util.directory_editor().value()) try: self._parameter_root['filename'] except KeyError: self._parameter_root.set_string( "filename", label="Filename", description="Filename without extension.") self._outputs_hlayout = QtGui.QHBoxLayout() self._outputs_hlayout.addWidget( self._parameter_root['directory'].gui()) self._outputs_hlayout.addWidget(self._parameter_root['filename'].gui()) self._file_extension_combo = (self._parameter_root[ 'filename_extension'].gui()) self._outputs_hlayout.addWidget(self._file_extension_combo) class Scatter2dWidget(PlotWidget): """Widget to plot a two dimensional scatter graph.""" def __init__(self, node_context, tables): """ Args: tables (list): table.FileList() names (list): list of names of the tables in tables tb_names (list): list of short time basis names, corresponding to long names in names units: {table_name: {column_name1: column_unit1, ...}, ...} """ super(Scatter2dWidget, self).__init__(node_context) self._tb_combobox = None self._pressed = False self._current_t = None self._actual_t = None self._t0_line = None self._t1_line = None self._names = [] self._tables = tables if len(self._tables) and self._tables[0].is_valid(): self._names = [t.get_name() for t in self._tables] self._no_lines_by_click = False self._index_name = 'Index' self._t_colors = {'t0': 'k', 't1': 'indigo'} self._init_gui() def _init_gui(self): self._column_names = [] if len(self._tables) and self._tables[0].is_valid(): self._column_names = sorted(list(self._tables[0].column_names())) # Create plot window self._create_figure_gui() self._pre_init_gui_from_parameters() vlayout = QtGui.QVBoxLayout() axes_hlayout = QtGui.QHBoxLayout() axes_hlayout.setSpacing(20) self._tb_combobox = self._parameter_root['tb_names'].gui() self._x_axis_combobox = self._parameter_root['x_axis'].gui() self._y_axis = self._parameter_root['y_axis'].gui() axis_vlayout = QtGui.QVBoxLayout() axis_vlayout.addWidget(self._tb_combobox) axis_vlayout.addWidget(self._x_axis_combobox) axis_vlayout.addWidget(self._y_axis) self._line_style_combobox = self._parameter_root['line_style'].gui() self._plot_combobox = self._parameter_root['plot_func'].gui() self._legend_button = QtGui.QPushButton('Show/hide legend') self._advanced_controller = QtGui.QCheckBox( 'Use advanced plot controller') legend_vlayout = QtGui.QVBoxLayout() legend_vlayout.addSpacing(26) legend_vlayout.addWidget(self._legend_button) legend_vlayout.addSpacing(2) plot_style_hlayout = QtGui.QHBoxLayout() plot_style_hlayout.addWidget( self._line_style_combobox, alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom) plot_style_hlayout.addWidget( self._plot_combobox, alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom) plot_style_hlayout.addLayout(legend_vlayout) plot_style_hlayout.addWidget( self._advanced_controller, alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom) plot_style_hlayout.insertStretch(0) plot_style_hlayout.setContentsMargins(0, 0, 0, 0) # Create output layout self._create_output_layout() # Create advanced plot controller widgets and layouyts self._reset_button = QtGui.QPushButton('Remove lines') self._t0_label = QtGui.QLabel('t0') self._t0_edit = QtGui.QLineEdit('') self._t1_label = QtGui.QLabel('t1') self._t1_label.setStyleSheet('color: indigo') self._t1_edit = QtGui.QLineEdit('') self._tdiff_label = QtGui.QLabel('t1-t0') self._tdiff_edit = QtGui.QLineEdit('') self._tdiff_edit.setReadOnly(True) self._ts_info_table = QtGui.QTableWidget() advanced_plot_layout = QtGui.QGridLayout() advanced_plot_layout.addWidget(self._reset_button, 0, 0, 1, 2) advanced_plot_layout.addWidget(self._t0_label, 1, 0) advanced_plot_layout.addWidget(self._t0_edit, 1, 1) advanced_plot_layout.addWidget(self._t1_label, 2, 0) advanced_plot_layout.addWidget(self._t1_edit, 2, 1) advanced_plot_layout.addWidget(self._tdiff_label, 3, 0) advanced_plot_layout.addWidget(self._tdiff_edit, 3, 1) advanced_plot_layout.addWidget(self._ts_info_table, 4, 0, 1, 2) plot_vlayout = QtGui.QVBoxLayout() plot_vlayout.addLayout(plot_style_hlayout) plot_vlayout.addWidget(self._canvas) plot_vlayout.addWidget(self._toolbar) plot_and_controller_splitlayout = QtGui.QSplitter(QtCore.Qt.Horizontal) axis_frame = QtGui.QFrame() axis_frame.setFrameShape(QtGui.QFrame.StyledPanel) axis_frame.setLayout(axis_vlayout) plot_and_controller_splitlayout.addWidget(axis_frame) plot_frame = QtGui.QFrame() plot_frame.setFrameShape(QtGui.QFrame.StyledPanel) plot_frame.setLayout(plot_vlayout) plot_and_controller_splitlayout.addWidget(plot_frame) advanced_controller_frame = QtGui.QFrame() advanced_controller_frame.setFrameShape(QtGui.QFrame.StyledPanel) advanced_controller_frame.setLayout(advanced_plot_layout) plot_and_controller_splitlayout.addWidget(advanced_controller_frame) outputs_widget = QtGui.QWidget() outputs_widget.setLayout(self._outputs_hlayout) policy = QtGui.QSizePolicy() policy.setVerticalPolicy(QtGui.QSizePolicy.Fixed) policy.setHorizontalPolicy(QtGui.QSizePolicy.Expanding) outputs_widget.setSizePolicy(policy) vlayout.addWidget(plot_and_controller_splitlayout) vlayout.addWidget(outputs_widget) self.setLayout(vlayout) self._init_gui_from_parameters() self._x_axis_combobox.editor().currentIndexChanged[int].connect( self._x_axis_change) self._tb_combobox.editor().currentIndexChanged[int].connect( self._tb_names_change) self._y_axis.editor().itemChanged.connect(self._y_axis_changed) self._line_style_combobox.editor().currentIndexChanged[int].connect( self._line_style_changed) self._plot_combobox.editor().currentIndexChanged.connect( self._plot_func_changed) self._legend_button.clicked.connect(self._hide_legend) self._advanced_controller.stateChanged.connect( self._advanced_controller_changed) self._reset_button.clicked.connect(self._reset_lines) self._t0_edit.editingFinished.connect(self._t0_changed) self._t1_edit.editingFinished.connect(self._t1_changed) self._figure.canvas.mpl_connect( 'button_press_event', self._canvas_mouse_clicked) self._figure.canvas.mpl_connect( 'button_release_event', self._canvas_mouse_released) self._figure.canvas.mpl_connect( 'motion_notify_event', self._move_t) self._toolbar.zoomChanged.connect(self._zoom_changed) self._toolbar.panActive.connect(self._pan_active) self._toolbar.zoomActive.connect(self._zoom_active) self._toolbar.panDragged.connect(self._pan_dragged) self._canvas.canvasResized.connect(self._canvas_resized) self._toolbar.parameters_edited.connect(self._store_plot_parameters) self._toolbar.home_clicked.connect(self._reset_plot) @qt_compat.Slot() def _reset_plot(self): self._parameter_root['x_min'].value = 0 self._parameter_root['x_max'].value = 0 self._parameter_root['y_min'].value = 0 self._parameter_root['y_max'].value = 0 self._update_figure() @qt_compat.Slot() def _store_plot_parameters(self): axis = self._figure.axes[0] pars = { 'x_min': axis.get_xlim()[0], 'x_max': axis.get_xlim()[1], 'y_min': axis.get_ylim()[0], 'y_max': axis.get_ylim()[1], 'title': axis.get_title(), 'xlabel': axis.get_xlabel(), 'ylabel': axis.get_ylabel()} for parameter, function in pars.items(): self._parameter_root[parameter].value = function def _restore_plot_parameters(self): axis = self._axes x_min = self._parameter_root['x_min'].value x_max = self._parameter_root['x_max'].value if x_min < x_max: axis.set_xlim((x_min, x_max)) y_min = self._parameter_root['y_min'].value y_max = self._parameter_root['y_max'].value if y_min < y_max: axis.set_ylim((y_min, y_max)) axis.set_title(self._parameter_root['title'].value) axis.set_xlabel(self._parameter_root['xlabel'].value) axis.set_ylabel(self._parameter_root['ylabel'].value) def _pre_init_gui_from_parameters(self): """Init comboboxes created in parameter_root""" x_axis_list = self._get_x_axis_list(self._column_names) if self._parameter_root['tb_names'].list and self._names: self._parameter_root['tb_names'].list = self._names if not self._parameter_root['x_axis'].list: self._parameter_root['x_axis'].list = x_axis_list if not self._parameter_root['y_axis'].list: self._parameter_root['y_axis'].list = self._column_names if not self._parameter_root['filename_extension'].list: supported_files_dict = (self._figure.canvas. get_supported_filetypes()) supported_files = supported_files_dict.keys() supported_files = (['.' + supported_file for supported_file in supported_files]) self._parameter_root['filename_extension'].list = supported_files if supported_files: self._parameter_root['filename_extension'].value = [0] def _init_gui_from_parameters(self): """Init gui from parameters""" if 'visible_legend' in self._parameter_root: self._parameter_root.set_boolean('visible_legend', value=True) if len(self._names) == 0: self._tb_combobox.editor().setEnabled(False) self._plot = Scatter2dPlot( self._tables, self._parameter_root, self._axes, self._index_name) self._t0_edit.blockSignals(True) self._t1_edit.blockSignals(True) self._t0_edit.setText(self._parameter_root['t0'].value) self._t1_edit.setText(self._parameter_root['t1'].value) self._t0_edit.blockSignals(False) self._t1_edit.blockSignals(False) self._advanced_controller.setChecked( self._parameter_root['advanced_controller'].value) self._hide_advanced_controller() self._background = None self._canvas_size = None # Enable/disable plot function widges and update figure self._plot_func_changed() QtCore.QTimer.singleShot(0, self._update_figure) def resizeEvent(self, event): """Update background and figure when widget is resized.""" if self._canvas_size != self._canvas.size(): self._background = None self.repaint() self.update() self._canvas_size = self._canvas.size() self._update_figure() def _canvas_resized(self): """Update background and figure when canvas is resized.""" if self._canvas_size != self._canvas.size(): self._background = None self.repaint() self.update() self._canvas_size = self._canvas.size() self._update_figure() def _canvas_mouse_clicked(self, event): if (self._parameter_root['advanced_controller'].value and event.button == 1 and not self._no_lines_by_click): time = event.xdata try: table_ind = self._parameter_root['tb_names'].value[0] except IndexError: table_ind = 0 x_name = self._parameter_root['x_axis'].selected if x_name == self._index_name: time_array = self._get_index_array() else: time_array = (self._tables[table_ind][x_name]) nearest_t, _ = self._get_nearest_value(time_array, time) if self._parameter_root['t0'].value in ['', None, 'None']: self._parameter_root['t0'].value = six.text_type(nearest_t) self._current_t = 't0' elif self._parameter_root['t1'].value in ['', None, 'None']: self._current_t = 't1' self._parameter_root['t1'].value = six.text_type(nearest_t) else: t0 = self._parameter_root['t0'].value t1 = self._parameter_root['t1'].value try: t0 = float(t0) except: pass try: t1 = float(t1) except: pass t_vals = np.array([t0, t1]) t_str = ['t0', 't1'] nearest_click, nearest_click_ind = self._get_nearest_value( t_vals, time) try: click_dist = np.abs(nearest_click - time) x_ax = self._axes.get_xlim() # If clicked close enough to one line, move that line. if float(click_dist) / (x_ax[1] - x_ax[0]) < 0.05: self._current_t = t_str[nearest_click_ind] else: self._current_t = None except: self._current_t = None # Check if near lines. then move with mouse. self._actual_t = None self._update_figure(only_line_changed=True) self._pressed = True def _move_t(self, event): """Mouse motion.""" if (self._pressed and self._parameter_root['advanced_controller'].value and self._current_t is not None and not self._no_lines_by_click): # Only do something if advanced plot controller activated and # mouse is pressed. Check if inside axis. Otherwise set line to # first/last t_value time = event.xdata try: table_ind = self._parameter_root['tb_names'].value[0] except IndexError: table_ind = 0 x_name = self._parameter_root['x_axis'].selected if x_name == self._index_name: time_array = self._get_index_array() else: time_array = (self._tables[table_ind] [self._parameter_root['x_axis'].selected]) nearest_t, _ = self._get_nearest_value(time_array, time) if self._current_t == 't0': self._parameter_root['t0'].value = nearest_t else: self._parameter_root['t1'].value = nearest_t self._actual_t = time self._update_figure(only_line_changed=True) def _canvas_mouse_released(self, event): """Mouse released.""" self._pressed = False if (self._parameter_root['advanced_controller'].value and self._current_t is not None and not self._no_lines_by_click): self._actual_t = None time = event.xdata try: table_ind = self._parameter_root['tb_names'].value[0] except IndexError: table_ind = 0 x_name = self._parameter_root['x_axis'].selected if x_name == self._index_name: time_array = self._get_index_array() else: time_array = (self._tables[table_ind] [self._parameter_root['x_axis'].selected]) nearest_t, _ = self._get_nearest_value(time_array, time) if nearest_t is None: nearest_t = '' if self._current_t == 't0': self._parameter_root['t0'].value = six.text_type(nearest_t) elif self._current_t == 't1': self._parameter_root['t1'].value = six.text_type(nearest_t) self._update_figure(only_line_changed=True) self._current_t = None self._actual_t = None def _update_table(self): """Update table with values for curves.""" self._ts_info_table.clear() y_list = self._parameter_root['y_axis'].value_names self._ts_info_table.setRowCount(len(y_list)) self._ts_info_table.setColumnCount(3) row_labels = ['y(t0)', 'y(t1)', 'y(t1)-y(t0)'] self._ts_info_table.setHorizontalHeaderLabels(row_labels) self._ts_info_table.setVerticalHeaderLabels(y_list) t0_nearest, t0_ind = self._get_t_value( self._parameter_root['t0'].value) t1_nearest, t1_ind = self._get_t_value( self._parameter_root['t1'].value) self._update_t(t0_nearest, t1_nearest) for ind, y_name in enumerate(y_list): self._update_y(ind, y_name, t0_ind, t1_ind) self._ts_info_table.resizeColumnsToContents() def _update_t(self, t0_nearest, t1_nearest): """Update values for t0, t1 and t diff.""" if t0_nearest is None: self._t0_edit.setText('') else: self._t0_edit.setText(six.text_type(t0_nearest)) if t1_nearest is None: self._t1_edit.setText('') else: self._t1_edit.setText(six.text_type(t1_nearest)) self._set_tdiff(t0_nearest, t1_nearest) def _update_y(self, ind, y_name, t0_ind, t1_ind): """Update one table column.""" # Get data and calculate y(t0), y(t1) and y(t1)-y(t0) if t0_ind is None: y0 = '--' else: y0 = self._get_y_value(y_name, t0_ind) if t1_ind is None: y1 = '--' else: y1 = self._get_y_value(y_name, t1_ind) try: y_diff = y1 - y0 except: y_diff = '--' self._ts_info_table.setItem( ind, 0, QtGui.QTableWidgetItem(six.text_type(y0))) self._ts_info_table.setItem( ind, 1, QtGui.QTableWidgetItem(six.text_type(y1))) self._ts_info_table.setItem( ind, 2, QtGui.QTableWidgetItem(six.text_type(y_diff))) def _get_y_value(self, y_name, time_ind): """Get value at index time_ind in array with y_axis name.""" # Get ts value at time_ind if time_ind is not None: try: table_index = self._parameter_root['tb_names'].value[0] except IndexError: table_index = 0 y_array = self._tables[table_index][y_name] y_value = y_array[time_ind] else: y_value = None return y_value def _get_index_array(self): """Get array of index for the data.""" try: table_index = self._parameter_root['tb_names'].value[0] except IndexError: table_index = 0 nbr_points = len(self._tables[ table_index][self._tables[table_index].dtype.names[0]]) index_array = np.arange(0, nbr_points) return index_array def _get_t_value(self, time_str): """Get nearest time value from time_str and index for that value.""" try: time = float(time_str) try: table_index = self._parameter_root['tb_names'].value[0] except IndexError: table_index = 0 x_name = self._parameter_root['x_axis'].selected if x_name == self._index_name: time_array = self._get_index_array() else: time_array = (self._tables[table_index] [self._parameter_root['x_axis'].selected]) nearest, ind = self._get_nearest_value(time_array, time) except: nearest = None ind = None return nearest, ind def _get_nearest_value(self, time_array, time): """Get nearest value and index from array with times using time var.""" try: ind = (np.abs(time_array - time)).argmin() return time_array[ind], ind except: return None, None def _t0_changed(self): """t0 line changed, update figure.""" text = self._t0_edit.text() t_nearest, _ = self._get_t_value(six.text_type(text)) self._parameter_root['t0'].value = six.text_type(t_nearest) self._set_tdiff( self._parameter_root['t0'].value, self._parameter_root['t1'].value) self._update_figure() def _t1_changed(self): """t1 line changed, update figure.""" text = self._t1_edit.text() t_nearest, _ = self._get_t_value(six.text_type(text)) self._parameter_root['t1'].value = six.text_type(t_nearest) self._set_tdiff( self._parameter_root['t0'].value, self._parameter_root['t1'].value) self._update_figure() def _set_tdiff(self, t0, t1): """Calculate t1-t0 and update tdiff edit.""" try: diff = (float(t1) - float(t0)) self._tdiff_edit.setText(six.text_type(diff)) except: self._tdiff_edit.setText('') def _tb_names_change(self, index): """Update x_axis and y_axis comboboxes and update figure.""" column_names = sorted(list(self._tables[index].dtype.names)) x_axis_list = self._get_x_axis_list(column_names) x_editor = self._x_axis_combobox.editor() x_editor.blockSignals(True) x_editor.clear() x_editor.addItems(x_axis_list) x_editor.blockSignals(False) self._parameter_root['y_axis'].list = column_names self._y_axis.editor().blockSignals(True) self._y_axis.editor().clear() self._y_axis.editor().addItems(column_names) self._y_axis.editor().blockSignals(False) self._update_figure() def _get_x_axis_list(self, column_names): """Append index_name to x_axis_list.""" x_axis_list = [] x_axis_list.extend(column_names) x_axis_list.append(self._index_name) x_axis_list.sort() return x_axis_list def _x_axis_change(self, index): """Update figure when x-axis has changed.""" self._parameter_root['x_min'].value = 0 self._parameter_root['x_max'].value = 0 self._parameter_root['y_min'].value = 0 self._parameter_root['y_max'].value = 0 self._update_figure() def _y_axis_changed(self): """Update figure when y_axis selection changed""" self._parameter_root['y_min'].value = 0 self._parameter_root['y_max'].value = 0 self._update_figure() def _line_style_changed(self, index): """Update figure with new selected line style""" self._update_figure() def _enable_choices(self, state): """Enable or disable x-axis combo and line stylecombo""" self._enable_x_axis(state) self._enable_line(state) def _enable_line(self, state): """Enable or disable line style combo""" self._line_style_combobox.editor().setEnabled(state) def _enable_x_axis(self, state): """Enable or disable x-axis combo""" self._x_axis_combobox.editor().setEnabled(state) def _plot_func_changed(self): """Update figure with new plot function selection""" plot_func = self._parameter_root['plot_func'].selected if plot_func == 'plot': self._enable_choices(True) elif plot_func in ['fill', 'step']: self._enable_x_axis(True) self._enable_line(False) else: self._enable_choices(False) self._update_figure() def _update_figure(self, only_line_changed=False, lim=None): """Update figure""" self._update_background(only_line_changed=only_line_changed, lim=lim) self._plot_lines() self._restore_plot_parameters() def _resize_temp(self): size = self.size() h = size.height() w = size.width() size.setHeight(h + 5) size.setWidth(w + 5) self.resize(size) size.setWidth(w) size.setHeight(h) self.resize(size) self._update_figure() def _update_background(self, only_line_changed=False, lim=None): """Update canvas background.""" if only_line_changed and self._background is not None and lim is None: self._canvas.restore_region(self._background) else: self._plot.update_figure(lim=lim) self._canvas.draw() self._background = self._canvas.copy_from_bbox(self._figure.bbox) def _plot_lines(self): """Plot lines on background.""" if self._parameter_root['advanced_controller'].value: if self._actual_t is not None: if self._current_t == 't0': self._t1_line = self._plot_or_update_line( self._parameter_root['t1'].value, self._t1_line, color=self._t_colors['t1']) self._t0_line = self._plot_or_update_line( self._actual_t, self._t0_line, color=self._t_colors['t0']) elif self._current_t == 't1': self._t0_line = self._plot_or_update_line( self._parameter_root['t0'].value, self._t0_line, color=self._t_colors['t0']) self._t1_line = self._plot_or_update_line( self._actual_t, self._t1_line, color=self._t_colors['t1']) else: self._t0_line = self._plot_or_update_line( self._parameter_root['t0'].value, self._t0_line, color=self._t_colors['t0']) self._t1_line = self._plot_or_update_line( self._parameter_root['t1'].value, self._t1_line, color=self._t_colors['t1']) self._update_table() try: self._canvas.blit(self._figure.bbox) except: pass def _plot_or_update_line(self, t, line, color=None): """Plot or update data in line at time t.""" if line is None: line = self._plot_line(t, color=color) else: self._update_plot_line(t, line) if line is not None and self._axes is not None: self._axes.draw_artist(line) return line def _plot_line(self, t, lim=None, color=None): """Plot line at time t.""" try: t_float = float(t) line = self._plot.plot_line( t_float, animated=True, lim=lim, color=color) except: line = None return line def _update_plot_line(self, t, line, lim=None): """Update data for line line at time t.""" try: t_float = float(t) self._plot.update_plot_line(t_float, line, lim=lim) except: pass def _hide_legend(self): """Hide or show legend.""" self._parameter_root['visible_legend'].value = ( not self._parameter_root['visible_legend'].value) xlim = self._figure.axes[0].get_xlim() ylim = self._figure.axes[0].get_ylim() lim = list(xlim) lim.extend(list(ylim)) self._update_figure(lim=lim) def _advanced_controller_changed(self): """Activate/deactivate advanced plot controller.""" self._parameter_root['advanced_controller'].value = ( self._advanced_controller.isChecked()) self._hide_advanced_controller() self._background = None self.update() self.repaint() self._update_figure() def _hide_advanced_controller(self): """Hide advanced controller if box unchecked. Else show controller.""" self._hide_advanced_controller_custom( self._parameter_root['advanced_controller'].value) def _hide_advanced_controller_custom(self, state): """Hide advanced controller if state false. Else show controller.""" if state: self._reset_button.show() self._t0_label.show() self._t0_edit.show() self._t1_label.show() self._t1_edit.show() self._tdiff_label.show() self._tdiff_edit.show() self._ts_info_table.show() else: self._reset_button.hide() self._t0_label.hide() self._t0_edit.hide() self._t1_label.hide() self._t1_edit.hide() self._tdiff_label.hide() self._tdiff_edit.hide() self._ts_info_table.hide() def _reset_lines(self): self._t0_edit.blockSignals(True) self._t1_edit.blockSignals(True) self._t0_edit.setText('') self._t1_edit.setText('') self._parameter_root['t0'].value = '' self._parameter_root['t1'].value = '' self._t0_line = None self._t1_line = None self._t0_edit.blockSignals(False) self._t1_edit.blockSignals(False) self._update_figure() def _zoom_changed(self, x_min, x_max, y_min, y_max): new_lim = [x_min, x_max, y_min, y_max] self._background = None self._update_figure(lim=new_lim) def _pan_active(self, state): self._no_lines_by_click = state def _zoom_active(self, state): self._no_lines_by_click = state def _pan_dragged(self, x_min, x_max, y_min, y_max): lim = [x_min, x_max, y_min, y_max] self._background = None self._update_figure(lim=lim) class Scatter2dWidgetMultipleTb(PlotWidget): """Widget to plot a two dimensional scatter graph.""" def __init__(self, node_context, tb_ts_dict, tb_name_dict, tb_dict, units=None): super(Scatter2dWidgetMultipleTb, self).__init__(node_context) self._tb_ts_dict = tb_ts_dict self._tb_name_dict = tb_name_dict self._tb_dict = tb_dict self._units = units self._plot_list = None self._add_button = None self._remove_button = None self._init_gui() def _init_gui(self): max_height = 100 max_width = 300 # Create plot window. self._create_figure_gui() self._pre_init_gui_from_parameters() vlayout = QtGui.QVBoxLayout() axes_hlayout = QtGui.QHBoxLayout() axes_hlayout.setSpacing(20) self._x_axis_combobox = self._parameter_root['x_axis'].gui() self._y_axis = self._parameter_root['y_axis'].gui() self._line_style_combobox = self._parameter_root['line_style'].gui() self._plot_combobox = self._parameter_root['plot_func'].gui() self._add_button = QtGui.QPushButton('Add selection to plot list') self._remove_button = QtGui.QPushButton('Remove plot line') self._legend_button = QtGui.QPushButton('Show/hide legend') self._plot_list = QtGui.QListWidget() self._plot_list.setMaximumHeight(max_height) self._plot_list.setMaximumWidth(max_width) button_hlayout = QtGui.QHBoxLayout() button_hlayout.addWidget(self._add_button) button_hlayout.addWidget(self._remove_button) plot_list_vlayout = QtGui.QVBoxLayout() plot_list_vlayout.addLayout(button_hlayout) plot_list_vlayout.addWidget(self._plot_list) legend_vlayout = QtGui.QVBoxLayout() legend_vlayout.addSpacing(20) legend_vlayout.addWidget(self._legend_button) plot_style_hlayout = QtGui.QHBoxLayout() plot_style_hlayout.addWidget(self._line_style_combobox) plot_style_hlayout.addWidget(self._plot_combobox) plot_style_hlayout.addLayout(legend_vlayout) plot_style_hlayout.insertStretch(0) axes_vlayout = QtGui.QVBoxLayout() axes_vlayout.addWidget(self._x_axis_combobox) axes_vlayout.addWidget(self._y_axis) axes_vlayout.addLayout(plot_list_vlayout) # Create output layout self._create_output_layout() plot_vlayout = QtGui.QVBoxLayout() plot_vlayout.addLayout(plot_style_hlayout) plot_vlayout.addWidget(self._canvas) plot_vlayout.addWidget(self._toolbar) plot_splitlayout = QtGui.QSplitter(QtCore.Qt.Horizontal) axis_frame = QtGui.QFrame() axis_frame.setFrameShape(QtGui.QFrame.StyledPanel) axis_frame.setLayout(axes_vlayout) plot_splitlayout.addWidget(axis_frame) plot_frame = QtGui.QFrame() plot_frame.setFrameShape(QtGui.QFrame.StyledPanel) plot_frame.setLayout(plot_vlayout) plot_splitlayout.addWidget(plot_frame) outputs_widget = QtGui.QWidget() outputs_widget.setLayout(self._outputs_hlayout) policy = QtGui.QSizePolicy() policy.setVerticalPolicy(QtGui.QSizePolicy.Fixed) policy.setHorizontalPolicy(QtGui.QSizePolicy.Expanding) outputs_widget.setSizePolicy(policy) vlayout.addWidget(plot_splitlayout) vlayout.addWidget(outputs_widget) self.setLayout(vlayout) self._init_gui_from_parameters() self._x_axis_combobox.editor().currentIndexChanged[int].connect( self._x_axis_change) self._line_style_combobox.editor().currentIndexChanged[int].connect( self._line_style_changed) self._plot_combobox.editor().currentIndexChanged.connect( self._plot_func_changed) self._add_button.clicked.connect(self._add_plot_line) self._remove_button.clicked.connect(self._remove_plot_line) self._legend_button.clicked.connect(self._hide_legend) def _pre_init_gui_from_parameters(self): """Init widgets already created in parameter_root""" if not self._parameter_root['x_axis'].list: if self._tb_name_dict is not None: self._parameter_root['x_axis'].list = self._tb_name_dict.keys() if not self._parameter_root['y_axis'].list: if (self._tb_ts_dict is not None and self._parameter_root['x_axis'].selected is not None): ts = (self._tb_ts_dict[ self._parameter_root['x_axis'].selected].keys()) self._parameter_root['y_axis'].list = ts self._parameter_root['y_axis'].value_names = [ts[0]] if not self._parameter_root['filename_extension'].list: supported_files_dict = ( self._figure.canvas.get_supported_filetypes()) supported_files = supported_files_dict.keys() supported_files = ( ['.' + supported_file for supported_file in supported_files]) self._parameter_root['filename_extension'].list = supported_files def _init_gui_from_parameters(self): try: self._parameter_root['display_strings'] except: disp_group = self._parameter_root.create_group('display_strings') disp_group.set_list('plot_list') disp_group.set_list('tb_names') disp_group.set_list('ts_names') try: self._parameter_root['visible_legend'] except: self._parameter_root.set_boolean('visible_legend', value=True) self._plot_list.addItems( self._parameter_root['display_strings']['plot_list'].list) self._plot = Scatter2dPlotMultipleTb( self._parameter_root, self._axes, self._tb_ts_dict, self._tb_name_dict, self._tb_dict, units=self._units) self._plot_func_changed() self._update_figure() def _x_axis_change(self, index): """Update y-axis and figure with new selected x-axis value""" column_names = ( self._tb_ts_dict[self._parameter_root['x_axis'].selected].keys()) self._y_axis.blockSignals(True) self._parameter_root['y_axis'].list = column_names self._parameter_root['y_axis'].value = [] self._y_axis.editor().clear() self._y_axis.editor().addItems(column_names) self._y_axis.blockSignals(False) def _line_style_changed(self, index): """Update figure with new selected line style""" self._update_figure() def _enable_line(self, state): """Enable or disable line style combo""" self._line_style_combobox.editor().setEnabled(state) def _plot_func_changed(self): """Update plot function selection""" plot_func = self._parameter_root['plot_func'].selected if plot_func == 'plot': self._enable_line(True) else: self._enable_line(False) self._update_figure() def _update_figure(self): """Update figure""" self._plot.update_figure() self._canvas.draw() def _remove_plot_line(self): """Remove selected plot line and update figure.""" ind = self._plot_list.currentRow() if ind >= 0: item = self._plot_list.currentItem().text() items = [self._plot_list.item(index).text() for index in six.moves.range(self._plot_list.count())] items.remove(item) del(self._parameter_root['display_strings']['plot_list'].list[ind]) del(self._parameter_root['display_strings']['tb_names'].list[ind]) del(self._parameter_root['display_strings']['ts_names'].list[ind]) self._plot_list.clear() self._plot_list.addItems(items) self._update_figure() def _add_plot_line(self): """Add plot lines from selection to plot list and update figure.""" x_axis_name = self._parameter_root['x_axis'].selected columns = self._parameter_root['y_axis'].value_names items = [self._plot_list.item(index).text() for index in six.moves.range(self._plot_list.count())] for column in columns: new_list_item = x_axis_name + ' -- ' + column if new_list_item not in items: items.append(new_list_item) self._parameter_root[ 'display_strings']['plot_list'].list.append(new_list_item) self._parameter_root[ 'display_strings']['tb_names'].list.append(x_axis_name) self._parameter_root[ 'display_strings']['ts_names'].list.append(column) self._plot_list.clear() self._plot_list.addItems(items) self._update_figure() def _hide_legend(self): self._parameter_root['visible_legend'].value = ( not self._parameter_root['visible_legend'].value) self._update_figure() class Scatter3dWidget(PlotWidget): """Widget to plot a three dimensional scatter graph""" def __init__(self, node_context, tables, names=None, names_short=None, units=None): """ Args: tables (list): list of numpy recarrays names (list): list of names of the tables in tables tb_names (list): list of short time basis names, corresponding to lo0ng names in names units: {table_name: {column_name1: column_unit1, ...}, ...} """ super(Scatter3dWidget, self).__init__(node_context) # Init all variables not in PlotWidget self._tables = tables self._names_short = names_short if names is None: self._names = [] else: self._names = names self._units = units self._z_axis_combobox = None self._tb_combobox = None self._projection = '3d' self._init_gui() def _init_gui(self): """Init GUI""" # Create plot window. self._create_figure_gui() self._pre_init_gui_from_parameters() vlayout = QtGui.QVBoxLayout() axes_hlayout = QtGui.QHBoxLayout() axes_hlayout.setSpacing(20) self._tb_combobox = self._parameter_root['tb_names'].gui() self._x_axis_combobox = self._parameter_root['x_axis'].gui() self._y_axis_combobox = self._parameter_root['y_axis'].gui() self._z_axis_combobox = self._parameter_root['z_axis'].gui() self._line_style_combobox = self._parameter_root['line_style'].gui() self._plot_combobox = self._parameter_root['plot_func'].gui() axes_hlayout.addWidget(self._tb_combobox) axes_hlayout.addWidget(self._x_axis_combobox) axes_hlayout.addWidget(self._y_axis_combobox) axes_hlayout.addWidget(self._z_axis_combobox) axes_hlayout.addWidget(self._line_style_combobox) axes_hlayout.addWidget(self._plot_combobox) axes_hlayout.insertStretch(-1) # Create outputlayout self._create_output_layout() vlayout.addItem(axes_hlayout) vlayout.addWidget(self._canvas) vlayout.addWidget(self._toolbar) vlayout.addLayout(self._outputs_hlayout) self.setLayout(vlayout) self._init_gui_from_parameters() self._x_axis_combobox.editor().currentIndexChanged[int].connect( self._x_axis_change) self._tb_combobox.editor().currentIndexChanged[int].connect( self._tb_names_change) self._y_axis_combobox.editor().currentIndexChanged[int].connect( self._y_axis_change) self._z_axis_combobox.editor().currentIndexChanged[int].connect( self._z_axis_change) self._line_style_combobox.editor().currentIndexChanged[int].connect( self._line_style_changed) self._plot_combobox.editor().currentIndexChanged.connect( self._plot_func_changed) self._figure.canvas.mpl_connect( 'button_release_event', self._update_view) def _pre_init_gui_from_parameters(self): column_names = [] if (self._tables is not None and len(self._tables) and self._tables[0] is not None): column_names = list(self._tables[0].dtype.names) if self._parameter_root['tb_names'].list and self._names: self._parameter_root['tb_names'].list = self._names x_list = self._parameter_root['x_axis'].list if (not x_list or len(x_list) == 0): self._parameter_root['x_axis'].list = column_names self._parameter_root['y_axis'].list = column_names self._parameter_root['z_axis'].list = column_names if not self._parameter_root['filename_extension'].list: supported_files_dict = (self._figure.canvas. get_supported_filetypes()) supported_files = supported_files_dict.keys() supported_files = ( ['.' + supported_file for supported_file in supported_files if supported_file in ['svg', 'pdf', 'eps', 'png']]) self._parameter_root['filename_extension'].list = supported_files def _init_gui_from_parameters(self): """Init GUI from parameters""" # variables for figure view. if len(self._names) == 0: self._tb_combobox.editor().setEnabled(False) self._plot = Plot3d( self._tables, self._parameter_root, self._figure, self._axes, self._names_short, self._units) self._plot_func_changed() self._update_figure() def _tb_names_change(self, index): """Update axis combobox items when timebasis is changed""" column_names = list(self._tables[index].dtype.names) x_editor = self._x_axis_combobox.editor() x_editor.blockSignals(True) x_editor.clear() x_editor.addItems(column_names) x_editor.blockSignals(False) y_editor = self._y_axis_combobox.editor() y_editor.blockSignals(True) y_editor.clear() y_editor.addItems(column_names) y_editor.blockSignals(False) z_editor = self._z_axis_combobox.editor() z_editor.blockSignals(True) z_editor.clear() z_editor.addItems(column_names) z_editor.blockSignals(False) self._update_figure() def _x_axis_change(self, index): """Update figure with new x_axis value""" self._update_figure() def _y_axis_change(self, index): """Update figure with new y_axis value""" self._update_figure() def _z_axis_change(self, index): """Update figure with new z_axis value""" self._update_figure() def _line_style_changed(self, index): """Update figure with new line_style""" self._update_figure() def _enable_line(self, state): """Enable or disable line style combo""" self._line_style_combobox.editor().setEnabled(state) def _plot_func_changed(self): """Update GUI and figure when plot function changed""" plot_func = self._parameter_root['plot_func'].selected if plot_func == 'plot': self._enable_line(False) elif plot_func == 'scatter': self._enable_line(True) else: self._enable_line(False) self._update_figure() def _update_figure(self): """Update figure""" self._plot.update_figure() try: self._canvas.draw() except ValueError: self._axes.clear() self._canvas.draw() def _update_view(self, event): """Update view when figure rotated""" self._plot.update_view() class Plot2D(object): def __init__(self, parameter_root, axes): """Init plot""" self._axes = axes self._parameter_root = parameter_root def update_figure(self, lim=None): """Update figure. To be implemented in subclasses""" pass def plot_line(self, t, animated=False, lim=None, color=None): """Plot line in figure.""" set_data = True if color is None: color = 'k-' if lim is None: ylim = self._axes.get_ylim() else: ylim = lim[2:4] if not (lim[0] <= t <= lim[1]): set_data = False if set_data and self._axes is not None: line, = self._axes.plot([t, t], ylim, color, animated=animated) return line else: return None def update_plot_line(self, t, line, lim=None): """Update plot line line in figure.""" set_data = True if lim is None and self._axes is not None: ylim = self._axes.get_ylim() else: ylim = lim[2:4] if not (lim[0] <= t <= lim[1]): set_data = False if set_data: line.set_xdata([t, t]) line.set_ydata(ylim) def _set_labels(self, x_column_name, x_units=[], y_units=[]): """Set labels on axes.""" plot_func = self._parameter_root['plot_func'].selected set_labels = plot_func not in ['hist step', 'hist bar'] if set_labels: if len(y_units) > 0 and self._axes is not None: self._axes.set_ylabel('[' + y_units[0] + ']') elif self._axes is not None: self._axes.set_ylabel('') if len(x_units) > 0: x_label = self._get_label(x_column_name, x_units[0]) else: x_label = self._get_label(x_column_name, '') x_label = '' if self._axes is not None: self._axes.set_xlabel(x_label) if self._parameter_root['visible_legend'].value: self._axes.legend() def _plot_x_y(self, x_data, y_data, y_column_name, y_unit=None): """Plot x_data and y_data with y_column_name and y_unit as legend""" plot_func = self._parameter_root['plot_func'].selected line_style = self._parameter_root['line_style'].selected if y_unit is None: y_unit = '' if type(y_unit) is list: y_unit = ', '.join(y_unit) if np.all(np.isreal(x_data)) and np.all(np.isreal(y_data)): if y_unit not in ['', 'unknown']: y_unit_label = ' [' + y_unit + ']' else: y_unit_label = '' y_label = ( six.text_type(y_column_name) + six.text_type(y_unit_label)) if self._axes is not None: if plot_func == 'hist step': self._axes.hist(y_data, 50, histtype='step', label=y_label) elif plot_func == 'plot': self._axes.plot(x_data, y_data, line_style, label=y_label) elif plot_func == 'step': self._axes.step(x_data, y_data, label=y_label) elif plot_func == 'fill': self._axes.fill(x_data, y_data, label=y_label) elif plot_func == 'hist bar': self._axes.hist(y_data, 50, histtype='bar', label=y_label) else: pass def _get_units(self, x_column_name, y_column_name, table_index=None): """Get units of axis. To be implemented by subclasses.""" pass def _get_special_unit(self, unit): """Handle deg, ^2, ^3 in units""" if u'\xb0' in unit: unit_temp = unit.split(u'\xb0') unit = r'$^\circ$'.join(unit_temp) elif u'\xb2' in unit: unit_temp = unit.split(u'\xb2') unit = '$^2$'.join(unit_temp) elif u'\xb3' in unit: unit_temp = unit.split(u'\xb3') unit = '$^3$'.join(unit_temp) return unit def _get_label(self, column_name, unit=''): """Get axis label""" if unit != 'unknown' and unit != '': label = six.text_type(column_name) + ' [' + unit + ']' else: label = six.text_type(column_name) return label def _is_valid_units_tb(self): """Check if units not None and if tb_names not empty""" return (self._units is not None) class Scatter2dPlot(Plot2D): def __init__(self, tables, parameter_root, axes, index_name=None): super(Scatter2dPlot, self).__init__( parameter_root, axes) self._tables = tables self._index_name = index_name def update_figure(self, lim=None): """Update figure""" x_column_name = '' x_data = [] y_data = [] y_units = [] x_units = '' if self._axes is not None: self._axes.clear() if len(self._parameter_root['x_axis'].value): x_column_name = self._parameter_root['x_axis'].selected else: x_column_name = self._parameter_root['x_axis'].list[0] y_column_names = self._parameter_root['y_axis'].value_names try: table_index = self._parameter_root['tb_names'].value[0] except IndexError: table_index = 0 if len(self._tables) and self._tables[table_index] is not None: if x_column_name == self._index_name: x_data = np.arange( 0, self._tables[table_index].number_of_rows()) elif x_column_name in self._tables[table_index]: x_data = self._tables[table_index].get_column_to_array( x_column_name) # Get units for axis if len(self._parameter_root['tb_names'].list) > 0: x_units = self._get_units(x_column_name, table_index) for y_column_name in y_column_names: y_data = self._tables[table_index].get_column_to_array( y_column_name) y_unit = self._get_units(y_column_name, table_index) y_units.append(y_unit) # Only plot real numbers self._plot_x_y(x_data, y_data, y_column_name, y_unit) # Add y-label if units exist, all y-axis columns have same units # and unit nos unknown. if lim is not None and self._axes is not None: self._axes.set_ylim(lim[2:4]) self._axes.set_xlim(lim[0:2]) self._set_labels(x_column_name, [x_units], y_units) self._restore_plot_parameters() def _restore_plot_parameters(self): axis = self._axes x_min = self._parameter_root['x_min'].value x_max = self._parameter_root['x_max'].value if x_min < x_max: axis.set_xlim((x_min, x_max)) y_min = self._parameter_root['y_min'].value y_max = self._parameter_root['y_max'].value if y_min < y_max: axis.set_ylim((y_min, y_max)) axis.set_title(self._parameter_root['title'].value) axis.set_xlabel(self._parameter_root['xlabel'].value) axis.set_ylabel(self._parameter_root['ylabel'].value) def _get_units(self, column_name, table_index=0): """Get units of axis and handle deg to be displayed correctly in plot. """ attrs = {} unit = '' if 'unit' in attrs: return self._get_special_unit(unit) else: return '' def _is_valid_units_tb(self): """Check if units not None and if tb_names not empty""" return (self._units is not None and len(self._parameter_root['tb_names'].list) > 0) class Scatter2dPlotMultipleTb(Plot2D): def __init__(self, parameter_root, axes, tb_ts_dict, tb_names, tb_dict, units=None): super(Scatter2dPlotMultipleTb, self).__init__(parameter_root, axes) self._tb_ts_dict = tb_ts_dict self._tb_names = tb_names self._tb_dict = tb_dict self._units = units def update_figure(self, lim=None): """Update figure""" if self._axes is not None: self._axes.clear() y_units = [] plot_list = self._parameter_root['display_strings']['plot_list'].list for ind, plot_line in enumerate(plot_list): x_column_name = ( self._parameter_root['display_strings']['tb_names'].list[ind]) y_column_name = ( self._parameter_root['display_strings']['ts_names'].list[ind]) x_data = self._tb_dict[x_column_name] y_data = self._tb_ts_dict[x_column_name][y_column_name] y_unit = self._get_units(x_column_name, y_column_name) if y_unit != '': y_units.append(y_unit) self._plot_x_y(x_data, y_data, y_column_name, y_unit) self._set_labels('time', y_units=y_units) def _get_units(self, x_column_name, y_column_name, table_index=None): """Get units of axis and handle deg to be displayed correctly in plot. """ unit = '' if self._units is not None: unit = self._units[x_column_name][y_column_name] unit = self._get_special_unit(unit) return unit class Plot3d(object): def __init__(self, tables, parameter_root, fig, axes, names_short=None, units=None, cb=None): self._fig = fig self._axes = axes self._parameter_root = parameter_root self._tables = tables self._units = units self._tb_names_short = names_short self._cb = cb self._nbr_points = 100 self._2d_axes = False self._rotation = None def update_figure(self): """Update figure""" x_column_name = '' x_data = [] y_data = [] z_data = [] plot_func = self._parameter_root['plot_func'].selected try: table_index = self._parameter_root['tb_names'].value[0] except IndexError: table_index = 0 if len(self._parameter_root['x_axis'].list): x_column_name = self._parameter_root['x_axis'].selected y_column_name = self._parameter_root['y_axis'].selected z_column_name = self._parameter_root['z_axis'].selected if (self._tables is not None and len(self._tables) and self._tables[table_index] is not None): if x_column_name and y_column_name and z_column_name: x_data = self._tables[table_index][x_column_name] y_data = self._tables[table_index][y_column_name] z_data = self._tables[table_index][z_column_name] if self._axes is not None: self._axes.clear() if not self._2d_axes: self._axes.mouse_init() if len(self._parameter_root['tb_names'].list) > 0: tb_name = self._parameter_root['tb_names'].selected # Get units for axis if (self._units is not None and len(self._parameter_root['tb_names'].list) > 0): x_units = self._get_units(tb_name, x_column_name, table_index) y_units = self._get_units(tb_name, y_column_name, table_index) z_units = self._get_units(tb_name, z_column_name, table_index) else: x_units = '' y_units = '' z_units = '' # Only plot real numbers if (np.all(np.isreal(x_data)) and np.all(np.isreal(y_data)) and np.all(np.isreal(z_data))): if plot_func == 'surf': self._axes_2d_to_3d() if len(x_data) >= 1 and len(y_data) >= 1 and len(z_data) >= 1: self._surf_plot(x_data, y_data, z_data) elif plot_func == 'contour': self._axes_2d_to_3d() if len(x_data) >= 1 and len(y_data) >= 1 and len(z_data) >= 1: self._contour_plot(x_data, y_data, z_data) elif plot_func == 'scatter': self._axes_2d_to_3d() self._check_cb() if self._axes is not None: self._axes.scatter( x_data, y_data, z_data, marker=self._parameter_root['line_style'].selected) self._cb = None elif plot_func == 'plot': self._axes_2d_to_3d() self._check_cb() if self._axes is not None: self._axes.plot(x_data, y_data, z_data) self._cb = None elif plot_func == 'wireframe': self._axes_2d_to_3d() self._wireframe_plot(x_data, y_data, z_data) elif plot_func == 'heatmap': self._heatmap_plot( x_data, y_data, z_data, z_column_name, z_units) else: pass if self._axes is not None: self._axes.set_xlabel(self._get_label(x_column_name, x_units)) self._axes.set_ylabel(self._get_label(y_column_name, y_units)) if not self._2d_axes and self._axes is not None: self._axes.set_zlabel(self._get_label(z_column_name, z_units)) def update_view(self): """Update figure rotation if 3d figure""" pass def _get_label(self, column_name, unit): """Get axis label""" if self._units is not None and unit != 'unknown' and unit != '': label = six.text_type(column_name) + ' [' + unit + ']' else: label = six.text_type(column_name) return label def _interpolate_data(self, data): """Linear interpolation of data when data more than predefined nbr of points. """ len_data = len(data) if len_data > self._nbr_points: x_interp = range(0, len_data) new_x_points = np.linspace(0, len_data - 1, self._nbr_points) new_data = np.interp(new_x_points, x_interp, data) return new_data return data def _interpolate_all(self, x_data, y_data, z_data): """Interpolate x_data, y_data and z_data""" x_data_new = self._interpolate_data(x_data) y_data_new = self._interpolate_data(y_data) z_data_new = self._interpolate_data(z_data) return x_data_new, y_data_new, z_data_new def _surf_plot(self, x_data, y_data, z_data): """3d surface plot""" x_data_new, y_data_new, z_data_new = self._interpolate_all( x_data, y_data, z_data) X, Y, Z = self._get_xyz( x_data_new, y_data_new, z_data_new) self._check_cb() if self._axes is not None: surf = self._axes.plot_surface( X, Y, Z, cmap=cm.coolwarm, rstride=1, cstride=1, linewidth=0, antialiased=False) if len(x_data) > 1 and len(y_data) > 1 and len(z_data) > 1: self._cb = self._fig.colorbar(surf, format='%d') # ????? else: self._cb = None def _contour_plot(self, x_data, y_data, z_data): """3d contour plot""" x_data_new, y_data_new, z_data_new = self._interpolate_all( x_data, y_data, z_data) X, Y, Z = self._get_xyz( x_data_new, y_data_new, z_data_new) if self._axes is not None: cset = self._axes.contour(X, Y, Z, cmap=cm.coolwarm) self._axes.clabel(cset, fontsize=9, inline=1) self._check_cb() self._cb = None def _wireframe_plot(self, x_data, y_data, z_data): """3d wireframe plot""" x_data_new, y_data_new, z_data_new = self._interpolate_all( x_data, y_data, z_data) X, Y, Z = self._get_xyz( x_data_new, y_data_new, z_data_new) if self._axes is not None: self._axes.plot_wireframe( X, Y, Z, rstride=1, cstride=1, alpha=0.4) self._check_cb() self._cb = None def _heatmap_plot(self, x_data, y_data, z_data, z_column_name, z_units): """2d heatmap plot with z_axis as colour""" x_data_new, y_data_new, z_data_new = self._interpolate_all( x_data, y_data, z_data) X, Y, Z = self._get_xyz( x_data_new, y_data_new, z_data_new) x = X.ravel() y = Y.ravel() z = Z.ravel() gridsize = 30 self._check_cb() self._fig.delaxes(self._axes) self._axes = self._fig.add_subplot(111) self._2d_axes = True if (len(x_data) > 1 and len(y_data) > 1 and len(z_data) > 1 and self._axes is not None): heat = self._axes.hexbin( x, y, C=z, gridsize=gridsize, cmap=cm.jet, bins=None) self._axes.axis([x.min(), x.max(), y.min(), y.max()]) self._cb = self._fig.colorbar(heat) z_label = self._get_label(z_column_name, z_units) self._cb.set_label(z_label) else: self._cb = None def _axes_2d_to_3d(self): """Create new 3d figure and delete old figure""" self._check_cb() if self._axes is not None: self._fig.delaxes(self._axes) self._axes = Axes3D(self._fig) self._2d_axes = False def _check_cb(self): """Check if colorbar exists, and then delete it""" if self._cb: try: self._fig.delaxes(self._fig.axes[1]) except: pass def _get_units(self, tb_name, column_name, table_index): """Get units of axis and handle deg to be displayed correctly in plot. """ try: unit = self._units[tb_name][column_name] except: try: if (column_name) == self._tb_names_short[table_index]: unit = self._units[tb_name][( self._parameter_root['tb_names'].list[ table_index])] else: unit = '' except: unit = '' # Handle deg in units if '\xb0' in unit: unit_temp = unit.split('\xb0') unit = r'$^\circ$'.join(unit_temp) return unit def _get_xyz(self, x_data, y_data, z_data): """Get matrices X,Y,Z from 1D arrays""" x, y = np.meshgrid(x_data, y_data) z = np.tile(z_data, (len(z_data), 1)) return x, y, z