callcc
callcc

Reputation: 21

PyQtGraph stacked plots approach

I would like to create a widget that displays several time signals on top of each other. The signals have the same time axis but different value axes. The time axes should be of a custom type. The time axes should be linked.

The whole widget is to be displayed in a QHBox layout next to a QListWidget with an item with a checkbox for each signal that enables the user to enable or disable the individual plots.

I currently have a half working solution but I guess I'm doing things wrong on a conceptual level. I can't get the autoSIprefix and label for the value axis to work and strange floating axes show up that shouldn't.

My current widget with floating axes on to left and missing value axis labels:

My current widget with floating axes on to left and missing value axis labels

class MultiPlotWidget(QWidget):
    def __init__(self, parent, data_frame):
        super().__init__(parent)

        # Data
        self._df = data_frame
        self._grouped_data = self._grouped_data()
        # everything is enabled by default
        self._enabled_parameters = set(self._grouped_data.keys())
        self._parameter_name_map = lambda name: name

        self._plot_items = self._make_plot_items(self._grouped_data)

        # Plot widget
        self._fig_sa = QScrollArea(self)
        self._fig = pg.GraphicsLayoutWidget(self._fig_sa)
        self._fig.setMinimumSize(QSize(400, len(self._enabled_parameters) * 200))
        self._fig_sa.setWidgetResizable(True)
        self._fig_sa.setWidget(self._fig)

        # Side widget
        self._sw = SideWidget(self, sorted(self._enabled_parameters))
        self._sw.parameter_state_change.connect(self._parameter_state_change_action)

        # Layout
        h_layout = QHBoxLayout()
        h_layout.addWidget(self._fig_sa)
        h_layout.addWidget(self._sw)

        self.setLayout(h_layout)

        self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)

    def data(self):
        return self._df

    def set_parameter_name_map(self, pnm):
        self._parameter_name_map = pnm
        self._plot_items = self._make_plot_items(self._grouped_data)
        self._sw.set_parameter_name_map(pnm)

    def _grouped_data(self):
        """Regroup columns of self._df into a dict with groups with common
        name but different suffix e.g. self._df has columns ["foo/a",
        "foo/b", "foo/c"] then this function returns a dict {"foo": {"a": column
        foo/a , "b": column foo/b, "c": column foo/c}}

        """
        grouped = {}
        for col in self._df.columns:
            parts = col
            name = col
            suffix = DES
            if len(parts) == 2:
                name, suffix = parts
            if name not in grouped: grouped[name] = {}
            grouped[name].update({suffix: self._df[col].as_matrix()})

        return grouped

    def _make_plot_items(self, grouped_data):
        plot_items = {}

        last_vb = None
        for group_name in self._enabled_parameters:
            group_cols = grouped_data[group_name]
            ts_name = self._parameter_name_map(group_name)

            date_axis = DateAxisItem(self._df.index.to_series(), "bottom")

            pi = pg.PlotItem(axisItems={"bottom": date_axis# , "left": value_axis
                                    })
            pi.setLabel("left", ts_name, unit="m")

            vb = pi.getViewBox()

            pi.setTitle(ts_name)
            pi.showGrid(x=True, y=True, alpha=0.5)
            value_axis = pi.getAxis("left")
            value_axis.enableAutoSIPrefix()
            value_axis.setLabel(text=ts_name, unit="m")
            value_axis.showLabel(show=True)

            colormap = {DES: pg.hsvColor(0, 0, 1),
                        ADV: pg.hsvColor(0, 1, 1),
                        FAV: pg.hsvColor(0.33, 1, 1)}
            for suffix, col in group_cols.items():
                vb.setLimits(xMin=0, xMax=len(col)-1)
                pi.plot(col, name=suffix, antialias=True,
                        pen={"color": colormap.get(suffix, colormap[DES])})

            # # Only show legend if more than one curve per plot is
            # # present
            # if len(group_cols.items()) > 1:
            #     pi.addLegend()

            if last_vb is not None: # Link all time axes together
                last_vb.setXLink(vb)
                vb.setXLink(last_vb)
            last_vb = vb

            plot_items[group_name] = pi

        return plot_items

    def draw(self):
        self._fig.setMinimumSize(QSize(400, len(self._enabled_parameters) * 220))

        # Remove all items
        row = 0
        while True:
            try:
                item = self._fig.getItem(row, 0)
                # for child_item in item.allChildItems():
                #     item.removeItem(child_item)
                self._fig.removeItem(item)
                row += 1
            except Exception: # Thats exactly what pyqtgraph throws
                break

        # Insert the enabled plot items
        self._fig.clear()
        for group_name in sorted(self._enabled_parameters):
            self._fig.addItem(self._plot_items[group_name])
            self._fig.nextRow()

    def _parameter_state_change_action(self, tpl):
        """Handles enabling and disabling the display of time series when they
        are ticked or unticked in the side widget.

        """
        p_name, p_enabled = tpl
        if p_enabled:
            self._enabled_parameters.add(p_name)
        else:
            self._enabled_parameters.remove(p_name)

        self.draw()

I've had a good look at the documentation and examples but didn't find anything suitable. Could you please give me a hint on how to achieve the features I want the "correct" way? Thanks a lot!

Upvotes: 2

Views: 1672

Answers (1)

JaqenTheMan
JaqenTheMan

Reputation: 41

1) Floating Axis -

This is a known bug in PlotItem.py (PyQtGraph 0.9.10), It will be fixed in later releases. The fixed code is already available on GitHub https://github.com/pyqtgraph/pyqtgraph/blob/develop/pyqtgraph/graphicsItems/PlotItem/PlotItem.py. Pulling the repository and re-installing fixed it for me.

2) SI Units -

You can add AutoUpdating SI units to PlotItem using the setLabels() method.

yourPlotItem.setLabels(left = ('Amplitude','V'))

This will set 'V' as your unit for the left axis, and it will change to mV, kV etc based on scaling.

Upvotes: 1

Related Questions