class PlotWidget(QtGui.QWidget): COLORS = 'rbgcmyk' def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.history_s = 20.0 self.next_color = 0 self.paused = False self.last_draw_time = 0.0 self.figure = matplotlib.figure.Figure() self.canvas = FigureCanvas(self.figure) self.canvas.mpl_connect('key_press_event', self.handle_key_press) self.canvas.mpl_connect('key_release_event', self.handle_key_release) self.left_axis = self.figure.add_subplot(111) self.left_axis.grid() self.left_axis.fmt_xdata = lambda x: '%.3f' % x self.left_axis.legend_loc = LEFT_LEGEND_LOC self.right_axis = None def draw(): # NOTE jpieper: For some reason, on the first repaint # event, the height is negative, which throws spurious # errors. Paper over that here. l, b, w, h = self.figure.bbox.bounds if h < 0: return FigureCanvas.draw(self.canvas) self.canvas.repaint() self.canvas.draw = draw self.toolbar = backend_qt4agg.NavigationToolbar2QT(self.canvas, self) self.pause_action = QtGui.QAction(u'Pause', self) self.pause_action.setCheckable(True) self.pause_action.toggled.connect(self._handle_pause) self.toolbar.addAction(self.pause_action) layout = QtGui.QVBoxLayout(self) layout.addWidget(self.toolbar, 0) layout.addWidget(self.canvas, 1) self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) def _handle_pause(self, value): self.paused = value def add_plot(self, name, signal, axis_number): axis = self.left_axis if axis_number == 1: if self.right_axis is None: self.right_axis = self.left_axis.twinx() self.right_axis.legend_loc = RIGHT_LEGEND_LOC axis = self.right_axis item = PlotItem(axis, self, name, signal) return item def remove_plot(self, item): item.remove() def data_update(self): now = time.time() elapsed = now - self.last_draw_time if elapsed > 0.1: self.last_draw_time = now self.canvas.draw() def _get_axes_keys(self): result = [] result.append(('1', self.left_axis)) if self.right_axis: result.append(('2', self.right_axis)) return result def handle_key_press(self, event): if event.key not in ['1', '2']: return for key, axis in self._get_axes_keys(): if key == event.key: axis.set_navigate(True) else: axis.set_navigate(False) def handle_key_release(self, event): if event.key not in ['1', '2']: return for key, axis in self._get_axes_keys(): axis.set_navigate(True)
class Tplot(QtGui.QMainWindow): def __init__(self): super(Tplot, self).__init__() self.ui = ui_tplot_main_window.Ui_TplotMainWindow() self.ui.setupUi(self) self.figure = matplotlib.figure.Figure() self.canvas = FigureCanvas(self.figure) self.canvas.mpl_connect('motion_notify_event', self.handle_mouse) self.canvas.mpl_connect('key_press_event', self.handle_key_press) self.canvas.mpl_connect('key_release_event', self.handle_key_release) # Make QT drawing not be super slow. See: # https://github.com/matplotlib/matplotlib/issues/2559/ def draw(): FigureCanvas.draw(self.canvas) self.canvas.repaint() self.canvas.draw = draw self.left_axis = self.figure.add_subplot(111) self.left_axis.tplot_name = 'Left' self.axes = { 'Left' : self.left_axis, } layout = QtGui.QVBoxLayout(self.ui.plotFrame) layout.addWidget(self.canvas, 1) self.toolbar = backend_qt4agg.NavigationToolbar2QT(self.canvas, self) self.addToolBar(self.toolbar) self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) self.canvas.setFocus() self.log = None self.COLORS = 'rgbcmyk' self.next_color = 0 self.timer = QtCore.QTimer() self.timer.timeout.connect(self.handle_timeout) self.time_start = None self.time_end = None self.time_current = None self.ui.recordCombo.currentIndexChanged.connect( self.handle_record_combo) self.ui.addPlotButton.clicked.connect(self.handle_add_plot_button) self.ui.removeButton.clicked.connect(self.handle_remove_button) self.ui.treeWidget.itemExpanded.connect(self.handle_item_expanded) self.tree_items = [] self.ui.treeWidget.header().setResizeMode( QtGui.QHeaderView.ResizeToContents) self.ui.timeSlider.valueChanged.connect(self.handle_time_slider) self._updating_slider = BoolGuard() self.ui.fastReverseButton.clicked.connect( self.handle_fast_reverse_button) self.ui.stepBackButton.clicked.connect( self.handle_step_back_button) self.ui.playReverseButton.clicked.connect( self.handle_play_reverse_button) self.ui.stopButton.clicked.connect(self.handle_stop_button) self.ui.playButton.clicked.connect(self.handle_play_button) self.ui.stepForwardButton.clicked.connect( self.handle_step_forward_button) self.ui.fastForwardButton.clicked.connect( self.handle_fast_forward_button) def open(self, filename): try: maybe_log = Log(filename) except Exception as e: QtGui.QMessageBox.warning(self, 'Could not open log', 'Error: ' + str(e)) raise return # OK, we're good, clear out our UI. self.ui.treeWidget.clear() self.tree_items = [] self.ui.recordCombo.clear() self.ui.xCombo.clear() self.ui.yCombo.clear() self.log = maybe_log for name in self.log.records.keys(): self.ui.recordCombo.addItem(name) item = QtGui.QTreeWidgetItem() item.setText(0, name) self.ui.treeWidget.addTopLevelItem(item) self.tree_items.append(item) exemplar = self.log.records[name] def add_item(parent, element): if 'fields' not in element: return for field in element['fields']: name = field['name'] item = QtGui.QTreeWidgetItem(parent) item.setText(0, name) if 'nelements' in field: child = field['children'][0] # If this is a type with only one child, just # fall down to it. if 'children' in child and len(child['children']) == 1: child = ['children'][0] for i in range(field['nelements']): subitem = QtGui.QTreeWidgetItem(item) subitem.setText(0, str(i)) add_item(subitem, child) elif 'children' in field: for child in field['children']: add_item(item, child) add_item(item, exemplar) def handle_record_combo(self): record = self.ui.recordCombo.currentText() self.ui.xCombo.clear() self.ui.yCombo.clear() exemplar = self.log.records[record] default_x = None index = [0, None] def add_item(index, parent, element): if 'fields' not in element: return for field in element['fields']: name = field['name'] this_name = parent if len(this_name): this_name += '.' this_name += name if 'nelements' in field: child = field['children'][0] if 'children' in child and len(child['children']) == 1: child = ['children'][0] for i in range(field['nelements']): add_item(index, this_name + "." + str(i), child) elif 'children' in field: for child in field['children']: add_item(index, this_name, child) else: self.ui.xCombo.addItem(this_name) self.ui.yCombo.addItem(this_name) if name == 'timestamp': index[1] = index[0] index[0] += 1 add_item(index, '', exemplar) default_x = index[1] if default_x: self.ui.xCombo.setCurrentIndex(default_x) def handle_add_plot_button(self): self.log.get_all() record = self.ui.recordCombo.currentText() xname = self.ui.xCombo.currentText() yname = self.ui.yCombo.currentText() data = self.log.all[record] xdata = [_get_data(x, xname) for x in data] ydata = [_get_data(x, yname) for x in data] line = matplotlib.lines.Line2D(xdata, ydata) line.tplot_record_name = record if 'timestamp' in [x['name'] for x in self.log.records[record]['fields']]: line.tplot_has_timestamp = True line.tplot_xname = xname line.tplot_yname = yname label = self.make_label(record, xname, yname) line.set_label(label) line.set_color(self.COLORS[self.next_color]) self.next_color = (self.next_color + 1) % len(self.COLORS) axis = self.get_current_axis() axis.add_line(line) axis.relim() axis.autoscale_view() axis.legend(loc=LEGEND_LOC[axis.tplot_name]) self.ui.plotsCombo.addItem(label, line) self.ui.plotsCombo.setCurrentIndex(self.ui.plotsCombo.count() - 1) self.canvas.draw() def make_label(self, record, xname, yname): if xname == 'timestamp': return '%s.%s' % (record, yname) return '%s %s vs. %s' % (record, yname, xname) def get_current_axis(self): requested = self.ui.axisCombo.currentText() maybe_result = self.axes.get(requested, None) if maybe_result: return maybe_result result = self.left_axis.twinx() self.axes[requested] = result result.tplot_name = requested return result def get_all_axes(self): return self.axes.values() def handle_remove_button(self): index = self.ui.plotsCombo.currentIndex() if index < 0: return line = self.ui.plotsCombo.itemData(index) if hasattr(line, 'tplot_marker'): line.tplot_marker.remove() line.remove() self.ui.plotsCombo.removeItem(index) self.canvas.draw() def handle_item_expanded(self): self.update_timeline() def update_timeline(self): if self.time_start is not None: return self.log.get_all() # Look through all the records for those which have a # "timestamp" field. Find the minimum and maximum of each. for record, exemplar in self.log.records.iteritems(): if record not in self.log.all: continue timestamp_getter = _make_timestamp_getter(self.log.all[record]) if timestamp_getter is None: continue these_times = [timestamp_getter(x) for x in self.log.all[record]] if len(these_times) == 0: continue this_min = min(these_times) this_max = max(these_times) if self.time_start is None or this_min < self.time_start: self.time_start = this_min if self.time_end is None or this_max > self.time_end: self.time_end = this_max self.time_current = self.time_start self.update_time(self.time_current, update_slider=False) def handle_mouse(self, event): if not event.inaxes: return self.statusBar().showMessage('%f,%f' % (event.xdata, event.ydata)) def handle_key_press(self, event): if event.key not in ['1', '2', '3', '4']: return index = ord(event.key) - ord('1') for key, value in self.axes.iteritems(): if key == AXES[index]: value.set_navigate(True) else: value.set_navigate(False) def handle_key_release(self, event): if event.key not in ['1', '2', '3', '4']: return for key, value in self.axes.iteritems(): value.set_navigate(True) def update_time(self, new_time, update_slider=True): new_time = max(self.time_start, min(self.time_end, new_time)) self.time_current = new_time # Update the tree view. self.update_tree_view(new_time) # Update dots on the plot. self.update_plot_dots(new_time) # Update the text fields. dt = datetime.datetime.utcfromtimestamp(new_time) self.ui.clockEdit.setText('%04d-%02d-%02d %02d:%02d:%02.3f' % ( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second + dt.microsecond / 1e6)) self.ui.elapsedEdit.setText('%.3f' % (new_time - self.time_start)) if update_slider: with self._updating_slider: elapsed = new_time - self.time_start total_time = self.time_end - self.time_start self.ui.timeSlider.setValue( int(1000 * elapsed / total_time)) def handle_time_slider(self): if self._updating_slider.active(): return if self.time_end is None or self.time_start is None: return total_time = self.time_end - self.time_start current = self.ui.timeSlider.value() / 1000.0 self.update_time(self.time_start + current * total_time, update_slider=False) def update_tree_view(self, time): for item in self.tree_items: name = item.text(0) if name not in self.log.all: continue all_data = self.log.all[name] timestamp_getter = _make_timestamp_getter(all_data) if timestamp_getter is None: continue this_data_index = _bisect(all_data, time, key=timestamp_getter) if this_data_index is None: _clear_tree_widget(item) else: this_data = all_data[this_data_index] _set_tree_widget_data(item, this_data) def update_plot_dots(self, new_time): updated = False for axis in self.get_all_axes(): for line in axis.lines: if not hasattr(line, 'tplot_record_name'): continue if not hasattr(line, 'tplot_has_timestamp'): continue all_data = self.log.all[line.tplot_record_name] timestamp_getter = _make_timestamp_getter(all_data) this_index = _bisect(all_data, new_time, timestamp_getter) if this_index is None: continue this_data = all_data[this_index] if not hasattr(line, 'tplot_marker'): line.tplot_marker = matplotlib.lines.Line2D([], []) line.tplot_marker.set_marker('o') line.tplot_marker.set_color(line._color) self.left_axis.add_line(line.tplot_marker) updated = True xdata = [_get_data(this_data, line.tplot_xname)] ydata = [_get_data(this_data, line.tplot_yname)] line.tplot_marker.set_data(xdata, ydata) if updated: self.canvas.draw() def handle_fast_reverse_button(self): self.play_start(-self.ui.fastReverseSpin.value()) def handle_step_back_button(self): self.play_stop() self.update_time(self.time_current - self.ui.stepBackSpin.value()) def handle_play_reverse_button(self): self.play_start(-1.0) def handle_stop_button(self): self.play_stop() def handle_play_button(self): self.play_start(1.0) def handle_step_forward_button(self): self.play_stop() self.update_time(self.time_current + self.ui.stepForwardSpin.value()) def handle_fast_forward_button(self): self.play_start(self.ui.fastForwardSpin.value()) def play_stop(self): self.speed = None self.last_time = None self.timer.stop() def play_start(self, speed): self.speed = speed self.last_time = time.time() self.timer.start(100) def handle_timeout(self): assert self.last_time is not None this_time = time.time() delta_t = this_time - self.last_time self.last_time = this_time self.update_time(self.time_current + delta_t * self.speed)
class CourseMapDialog(QtGui.QDialog): """Implements a dialog box to display course maps.""" time_slider_changed = QtCore.Signal(float) def __init__(self, parent): QtGui.QDialog.__init__(self, parent) self._ui = ui_course_map_dialog.Ui_Dialog() self._ui.setupUi(self) params = matplotlib.figure.SubplotParams(0, 0, 1, 1, 0, 0) self._figure = matplotlib.figure.Figure(subplotpars=params) self._canvas = FigureCanvas(self._figure) self._canvas.mpl_connect('motion_notify_event', self._handle_mouse) self._canvas.mpl_connect('scroll_event', self._handle_scroll) self._canvas.mpl_connect('button_release_event', self._handle_mouse_release) self._mouse_start = None # Make QT drawing not be super slow. See: # https://github.com/matplotlib/matplotlib/issues/2559/ def draw(): FigureCanvas.draw(self._canvas) self._canvas.repaint() self._canvas.draw = draw self._plot = self._figure.add_subplot(111) self._gdal_source = course_gdal.GdalSource() self._gdal = None layout = QtGui.QVBoxLayout(self._ui.mapFrame) layout.addWidget(self._canvas, 1) self._log_data = dict() # TODO sammy make COLORS a project wide config self._COLORS = 'rgbcmyk' self._next_color = 0 self._bounds = ((0, 0), (0, 0)) self._time_current = 0 self._total_time = 0 self._ui.timeSlider.valueChanged.connect(self._handle_time_slider) def add_log(self, log_name, log): log_data = _LogMapData(log_name, log, self._COLORS[self._next_color]) self._log_data[log_name] = log_data self._next_color = (self._next_color + 1) % len(self._COLORS) if self._gdal is None: self._gdal = self._gdal_source.get_gdal( log_data.utm_data[0][1], log_data.utm_data[0][2]) if self._gdal is not None: self._plot.imshow(self._gdal.image, extent=self._gdal.extent) self._plot.add_line(log_data.line) self._plot.add_line(log_data.marker) self._plot.legend(loc=2) self._update_scale() def update_sync(self): for log_data in self._log_data.itervalues(): log_data.update_line_data() log_data.update_marker(self._time_current) self._update_scale() def _update_scale(self): self._plot.relim() minx = 1e10 miny = 1e10 maxx = -1e10 maxy = -1e10 max_time = 0 for log_data in self._log_data.itervalues(): bounds = log_data.bounds minx = min(minx, bounds[0][0]) miny = min(miny, bounds[0][1]) maxx = max(maxx, bounds[1][0]) maxy = max(maxy, bounds[1][1]) max_time = max(max_time, log_data.utm_data[-1][0]) self._total_time = max_time x_size = maxx - minx y_size = maxy - miny xy_delta = x_size - y_size if xy_delta > 0: miny -= xy_delta / 2 maxy += xy_delta / 2 else: minx += xy_delta / 2 maxx -= xy_delta / 2 self._bounds = ((minx, miny), (maxx, maxy)) self._plot.set_xlim(left=minx, right=maxx) self._plot.set_ylim(bottom=miny, top=maxy) self._canvas.draw() def _handle_mouse(self, event): if not event.inaxes or event.button != 1: return if self._mouse_start is None: self._mouse_start = event return # Handle a pan event. What we want is for the point (data) # where the mouse was originally clicked to stay under the # pointer. (width, height) = self._canvas.get_width_height() px_x = (self._bounds[1][0] - self._bounds[0][0]) / width px_y = (self._bounds[1][1] - self._bounds[0][1]) / height x_change = (self._mouse_start.x - event.x) * px_x y_change = (self._mouse_start.y - event.y) * px_y self._plot.set_xlim(left=self._bounds[0][0] + x_change, right=self._bounds[1][0] + x_change) self._plot.set_ylim(bottom=self._bounds[0][1] + y_change, top=self._bounds[1][1] + y_change) self._canvas.draw() def _update_bounds(self): xlim = self._plot.get_xlim() ylim = self._plot.get_ylim() self._bounds = ((xlim[0], ylim[0]), (xlim[1], ylim[1])) def _handle_mouse_release(self, event): if event.button != 1: return self._mouse_start = None self._update_bounds() def _handle_scroll(self, event): # Determine the relative offset of the clicked position to the # center of the frame so that we can keep the data under the # cursor. (width, height) = self._canvas.get_width_height() x_off = float(width - event.x) / width y_off = float(height - event.y) / height x_scale = (self._bounds[1][0] - self._bounds[0][0]) * (event.step / 10.) y_scale = (self._bounds[1][1] - self._bounds[0][1]) * (event.step / 10.) # Check if we've tried to zoom to far and would invert our axes new_xlim = (self._bounds[0][0] + (x_scale * (1. - x_off)), self._bounds[1][0] - (x_scale * x_off)) new_ylim = (self._bounds[0][1] + (y_scale * (1. - y_off)), self._bounds[1][1] - (y_scale * y_off)) if (new_xlim[1] <= new_xlim[0]) or (new_ylim[1] <= new_ylim[0]): return self._plot.set_xlim(left=new_xlim[0], right=new_xlim[1]) self._plot.set_ylim(bottom=new_ylim[0], top=new_ylim[1]) self._update_bounds() self._canvas.draw() def update_time(self, new_time, update_slider=True): # Bound the time to our useful range. new_time = max(0, min(new_time, self._total_time)) self._time_current = new_time for log_data in self._log_data.itervalues(): log_data.update_marker(new_time) self._canvas.draw() self._ui.elapsedTime.setText(str(new_time)) # if update_slider: # self._ui.timeSlider.setValue(1000 * (new_time / self._total_time)) def _handle_time_slider(self): if self._total_time == 0: return current = self._ui.timeSlider.value() / 1000. self.update_time(current * self._total_time, update_slider=False) self.time_slider_changed.emit(self._time_current)
class LinePlot(OWWidget): name = "Line Plot" description = "Make a line plot of data" icon = "icons/lineplot.svg" want_main_area = False DAAP = "daap" COLORPREFIX = "C" DEFAULTCOLOR = COLORPREFIX + "0" FIELDNAMEDATE = "date" FIELDNAMENONE = "NONE" FIELDNAMEMSGID = "msg id" FIELDNAMEFILE = "file" SECONDSPERDAY = 86400 WORDS = "words" MESSAGES = "messages" storedTable = None coloredColumn = -1 xColumn = 0 yColumn = 0 connect = MESSAGES form = None ax = None class Inputs: table = Input("Data", Table) def __init__(self): super().__init__() self.form = QFormLayout() self.figure = Figure() self.canvas = FigureCanvas(self.figure) self.form.addWidget(self.canvas) self.form.setFieldGrowthPolicy(self.form.AllNonFixedFieldsGrow) self.form.setVerticalSpacing(0) self.form.setLabelAlignment(Qt.AlignLeft) gui.widgetBox(self.controlArea, True, orientation=self.form) @Inputs.table def storeTable(self, table): if table != None and hasattr(table, "domain"): self.storedTable = table # 20191210 warning: reloading table may require replacing form # because set of features changed # self.clearCanvas() self.drawGraph() self.drawWindow() def redraw(self): if self.storedTable != None: self.drawGraph() def drawGraph(self): self.ax = self.figure.add_subplot(111) if self.connect == self.WORDS: self.plotWords() else: self.plotMessages() def drawWindow(self): form = self.form if self.storedTable == None: columnNames = [] else: columnNames = [x.name for x in self.storedTable.domain.variables] if self.form.rowCount() <= 1: form.addRow( "x-axis:", gui.comboBox(None, self, "xColumn", items=columnNames, callback=self.redraw)) form.addRow( "y-axis:", gui.comboBox(None, self, "yColumn", items=columnNames, callback=self.redraw)) form.addRow( "split by:", gui.comboBox(None, self, "coloredColumn", items=columnNames + [self.FIELDNAMENONE], callback=self.redraw)) form.addRow( "connect:", gui.comboBox(None, self, "connect", items=[self.MESSAGES, self.WORDS], callback=self.redraw)) form.addRow(gui.button(None, self, 'draw', self.redraw)) def clearCanvas(self): while self.form.rowCount() > 1: print("deleting line plot row...") self.form.removeRow(1) self.canvas.draw() self.canvas.repaint() def getFieldValue(self, table, fieldName, rowId): if rowId < len(table): for i in range(0, len(table.domain)): if table.domain[i].name == fieldName: return (table[rowId].list[i]) for i in range(0, len(table.domain.metas)): if table.domain.metas[i].name == fieldName: return (table[rowId].metas[i]) sys.exit("getFieldValue: field name not found: " + fieldName) def getColumnValues(self, table, columnName): return (list( set([ self.getFieldValue(table, columnName, i) for i in range(0, len(table)) ]))) def makeColorNames(self, columnName): columnValueList = sorted(self.getColumnValues(self.storedTable, columnName), reverse=True) colorNames = {} for i in range(0, len(columnValueList)): colorNames[columnValueList[i]] = self.COLORPREFIX + str(i % 10) return (colorNames) def simplifyLegend(self, ax): handles, labels = ax.get_legend_handles_labels() nbrOfUniqueLabels = len(set(labels)) handlesUnique = [] labelsUnique = [] seen = {} for i in range(0, len(labels)): if not labels[i] in seen: seen[labels[i]] = True labelsUnique.append(labels[i]) handlesUnique.append(handles[i]) if len(labelsUnique) >= nbrOfUniqueLabels: break return (handlesUnique, labelsUnique) def mixSorted(self, thisList): strList = [] numList = [] for i in range(0, len(thisList)): if re.match("^\d+$", thisList[i]): numList.append(int(thisList[i])) else: strList.append(thisList[i]) return (sorted(strList) + [str(x) for x in sorted(numList)]) def normalizeXvalues(self, xValues): for i in range(1, len(xValues)): xValues[i] -= xValues[0] xValues[0] = 0 return (xValues) def convertSecondsToDays(self, xValues): return ([x / self.SECONDSPERDAY for x in xValues]) def plotWords(self): if self.storedTable == None or not hasattr(self.storedTable, "domain"): return columnNames = [x.name for x in self.storedTable.domain.variables] ax = self.ax ax.clear() lastMsgId = "" lastDataValue = None dataX = [] dataY = [] color = self.DEFAULTCOLOR if self.coloredColumn >= 0 and self.coloredColumn < len(columnNames): colorNames = self.makeColorNames(columnNames[self.coloredColumn]) for i in range(0, len(self.storedTable)): currentMsgId = self.getFieldValue(self.storedTable, self.FIELDNAMEMSGID, i) newX = self.getFieldValue(self.storedTable, columnNames[self.xColumn], i) newY = self.getFieldValue(self.storedTable, columnNames[self.yColumn], i) if i > 0 and currentMsgId != lastMsgId and self.connect != self.WORDS: ax.plot([dataX[-1], newX], [dataY[-1], newY], color=self.DEFAULTCOLOR, label=lastDataValue) if currentMsgId != lastMsgId and len(dataX) > 0: if self.coloredColumn >= 0 and self.coloredColumn < len( columnNames): color = colorNames[lastDataValue] ax.plot(dataX, dataY, color=color, label=lastDataValue) dataX = [] dataY = [] dataX.append(newX) dataY.append(newY) lastMsgId = currentMsgId lastDataValue = self.getFieldValue(self.storedTable, columnNames[self.coloredColumn], i) if len(dataX) > 0: ax.plot(dataX, dataY, color=color, label=lastDataValue) if self.coloredColumn >= 0 and self.coloredColumn < len( columnNames): color = colorNames[lastDataValue] if len(dataX) > 1: ax.plot(dataX, dataY, color=color, label=lastDataValue) else: ax.scatter(dataX, dataY, color=color, label=lastDataValue) title = "x-axis: \"" + columnNames[ self.xColumn] + "\"" + "; y-axis: \"" + columnNames[ self.yColumn] + "\"" if self.coloredColumn >= 0 and self.coloredColumn < len(columnNames): title += "; color: \"" + columnNames[self.coloredColumn] + "\"" handlesUnique, labelsUnique = self.simplifyLegend(ax) ax.legend(handlesUnique, labelsUnique) ax.title.set_text(title) self.canvas.draw() self.canvas.repaint() def plotMessages(self): if self.storedTable == None or not hasattr(self.storedTable, "domain"): return columnNames = [x.name for x in self.storedTable.domain.variables] ax = self.ax ax.clear() self.fileName = str( self.getFieldValue(self.storedTable, self.FIELDNAMEFILE, 0)) xValues = self.normalizeXvalues([ self.getFieldValue(self.storedTable, columnNames[self.xColumn], i) for i in range(0, len(self.storedTable)) ]) if columnNames[self.xColumn] == self.FIELDNAMEDATE: xValues = self.convertSecondsToDays(xValues) if self.coloredColumn < 0 or self.coloredColumn >= len(columnNames): dataX = [] dataY = [] for i in range(0, len(self.storedTable)): newX = xValues[i] newY = self.getFieldValue(self.storedTable, columnNames[self.yColumn], i) dataX.append(newX) dataY.append(newY) if len(dataX) > 1: ax.plot(dataX, dataY, color=self.DEFAULTCOLOR) elif len(dataX) > 0: ax.scatter(dataX, dataY, color=self.DEFAULTCOLOR) else: colorNames = self.makeColorNames(columnNames[self.coloredColumn]) columnValues = self.getColumnValues( self.storedTable, columnNames[self.coloredColumn]) for columnValue in columnValues: dataX = [] dataY = [] for i in range(0, len(self.storedTable)): dataValue = self.getFieldValue( self.storedTable, columnNames[self.coloredColumn], i) if dataValue == columnValue: newX = xValues[i] newY = self.getFieldValue(self.storedTable, columnNames[self.yColumn], i) dataX.append(newX) dataY.append(newY) if len(dataX) > 1: ax.plot(dataX, dataY, color=colorNames[columnValue], label=columnValue) elif len(dataX) > 0: ax.scatter(dataX, dataY, color=colorNames[columnValue], label=columnValue) title = "x-axis: \"" + columnNames[ self.xColumn] + "\"" + "; y-axis: \"" + columnNames[ self.yColumn] + "\"" if self.coloredColumn >= 0 and self.coloredColumn < len(columnNames): title += "; color: \"" + columnNames[self.coloredColumn] + "\"" handlesUnique, labelsUnique = self.simplifyLegend(ax) ax.legend(handlesUnique, labelsUnique) ax.title.set_text(title) self.canvas.draw() self.canvas.repaint()
class PlotWidget(QtGui.QWidget): COLORS = "rbgcmyk" def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.history_s = 20.0 self.next_color = 0 self.paused = False self.figure = matplotlib.figure.Figure() self.canvas = FigureCanvas(self.figure) self.canvas.mpl_connect("key_press_event", self.handle_key_press) self.canvas.mpl_connect("key_release_event", self.handle_key_release) self.left_axis = self.figure.add_subplot(111) self.left_axis.grid() self.left_axis.fmt_xdata = lambda x: "%.3f" % x self.left_axis.legend_loc = LEFT_LEGEND_LOC self.right_axis = None def draw(): # NOTE jpieper: For some reason, on the first repaint # event, the height is negative, which throws spurious # errors. Paper over that here. l, b, w, h = self.figure.bbox.bounds if h < 0: return FigureCanvas.draw(self.canvas) self.canvas.repaint() self.canvas.draw = draw self.toolbar = backend_qt4agg.NavigationToolbar2QT(self.canvas, self) self.pause_action = QtGui.QAction(u"Pause", self) self.pause_action.setCheckable(True) self.pause_action.toggled.connect(self._handle_pause) self.toolbar.addAction(self.pause_action) layout = QtGui.QVBoxLayout(self) layout.addWidget(self.toolbar, 0) layout.addWidget(self.canvas, 1) self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) def _handle_pause(self, value): self.paused = value def add_plot(self, name, signal, axis_number): axis = self.left_axis if axis_number == 1: if self.right_axis is None: self.right_axis = self.left_axis.twinx() self.right_axis.legend_loc = RIGHT_LEGEND_LOC axis = self.right_axis item = PlotItem(axis, self, name, signal) return item def remove_plot(self, item): item.remove() def _get_axes_keys(self): result = [] result.append(("1", self.left_axis)) if self.right_axis: result.append(("2", self.right_axis)) return result def handle_key_press(self, event): if event.key not in ["1", "2"]: return for key, axis in self._get_axes_keys(): if key == event.key: axis.set_navigate(True) else: axis.set_navigate(False) def handle_key_release(self, event): if event.key not in ["1", "2"]: return for key, axis in self._get_axes_keys(): axis.set_navigate(True)