class PlotImage(FigureCanvas): def __init__(self, model, parent, main): super(FigureCanvas, self).__init__(Figure()) FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) self.model = model self.mw = main self.parent = parent self.rubber_band = QRubberBand(QRubberBand.Rectangle, self) self.band_origin = QtCore.QPoint() self.x_plot_origin = None self.y_plot_origin = None self.menu = QMenu(self) def enterEvent(self, event): self.setCursor(QtCore.Qt.CrossCursor) self.mw.coord_label.show() def leaveEvent(self, event): self.mw.coord_label.hide() self.mw.statusBar().showMessage("") def mousePressEvent(self, event): self.mw.coord_label.hide() # Set rubber band absolute and relative position self.band_origin = event.pos() self.x_plot_origin, self.y_plot_origin = self.getPlotCoords( event.pos()) # Create rubber band self.rubber_band.setGeometry( QtCore.QRect(self.band_origin, QtCore.QSize())) FigureCanvas.mousePressEvent(self, event) def getPlotCoords(self, pos): cv = self.model.currentView # get the normalized axis coordinates from the event display units xPlotCoord, yPlotCoord = self.ax.transAxes.inverted().transform( (pos.x(), pos.y())) # flip the y-axis (its zero is in the upper left) yPlotCoord = 1 - yPlotCoord # scale axes using the plot extents xPlotCoord = self.ax.dataLim.x0 + xPlotCoord * self.ax.dataLim.width yPlotCoord = self.ax.dataLim.y0 + yPlotCoord * self.ax.dataLim.height # set coordinate label if pointer is in the axes if self.ax.contains_point((pos.x(), pos.y())): self.mw.coord_label.show() self.mw.showCoords(xPlotCoord, yPlotCoord) else: self.mw.coord_label.hide() return (xPlotCoord, yPlotCoord) def getIDinfo(self, event): cv = self.model.currentView # get origin in axes coordinates x0, y0 = self.ax.transAxes.transform((0., 0.)) # get the extents of the axes box in axes coordinates bbox = self.ax.get_window_extent().transformed( self.figure.dpi_scale_trans.inverted()) # get dimensions and scale using dpi width, height = bbox.width, bbox.height width *= self.figure.dpi height *= self.figure.dpi # use factor to get proper x,y position in pixels factor = (width / cv.h_res, height / cv.v_res) xPos = int((event.pos().x() - x0 + 0.05) / factor[0]) yPos = int((event.pos().y() - y0 + 0.05) / factor[1]) # check that the position is in the axes view if yPos < self.model.currentView.v_res \ and xPos < self.model.currentView.h_res: id = f"{self.model.ids[yPos][xPos]}" temp = f"{self.model.props[yPos][xPos][0]:g}" density = f"{self.model.props[yPos][xPos][1]:g}" else: id = '-1' density = '-1' temp = '-1' if self.model.currentView.colorby == 'cell': domain = self.model.activeView.cells domain_kind = 'Cell' else: domain = self.model.activeView.materials domain_kind = 'Material' properties = {'density': density, 'temperature': temp} return id, properties, domain, domain_kind def mouseDoubleClickEvent(self, event): xCenter, yCenter = self.getPlotCoords(event.pos()) self.mw.editPlotOrigin(xCenter, yCenter, apply=True) FigureCanvas.mouseDoubleClickEvent(self, event) def mouseMoveEvent(self, event): # Show Cursor position relative to plot in status bar xPlotPos, yPlotPos = self.getPlotCoords(event.pos()) # Show Cell/Material ID, Name in status bar id, properties, domain, domain_kind = self.getIDinfo(event) if self.ax.contains_point((event.pos().x(), event.pos().y())): if id != str(_NOT_FOUND_) and domain[id].name: domainInfo = (f"{domain_kind} {id}: \"{domain[id].name}\"\t " f"Density: {properties['density']} g/cm3\t" f"Temperature: {properties['temperature']} K") elif id != str(_NOT_FOUND_): domainInfo = (f"{domain_kind} {id}\t" f"Density: {properties['density']} g/cm3\t" f"Temperature: {properties['temperature']} K") else: domainInfo = "" else: domainInfo = "" self.mw.statusBar().showMessage(f" {domainInfo}") # Update rubber band and values if mouse button held down if event.buttons() == QtCore.Qt.LeftButton: self.rubber_band.setGeometry( QtCore.QRect(self.band_origin, event.pos()).normalized()) # Show rubber band if both dimensions > 10 pixels if self.rubber_band.width() > 10 and self.rubber_band.height( ) > 10: self.rubber_band.show() else: self.rubber_band.hide() # Update plot X Origin xCenter = (self.x_plot_origin + xPlotPos) / 2 yCenter = (self.y_plot_origin + yPlotPos) / 2 self.mw.editPlotOrigin(xCenter, yCenter) modifiers = event.modifiers() # Zoom out if Shift held if modifiers == QtCore.Qt.ShiftModifier: cv = self.model.currentView bandwidth = abs(self.band_origin.x() - event.pos().x()) width = cv.width * (cv.h_res / max(bandwidth, .001)) bandheight = abs(self.band_origin.y() - event.pos().y()) height = cv.height * (cv.v_res / max(bandheight, .001)) else: # Zoom in width = max(abs(self.x_plot_origin - xPlotPos), 0.1) height = max(abs(self.y_plot_origin - yPlotPos), 0.1) self.mw.editWidth(width) self.mw.editHeight(height) def mouseReleaseEvent(self, event): if self.rubber_band.isVisible(): self.rubber_band.hide() self.mw.applyChanges() else: self.mw.revertDockControls() def wheelEvent(self, event): if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier: numDegrees = event.delta() / 8 if 24 < self.mw.zoom + numDegrees < 5001: self.mw.editZoom(self.mw.zoom + numDegrees) def contextMenuEvent(self, event): self.menu.clear() self.mw.undoAction.setText(f'&Undo ({len(self.model.previousViews)})') self.mw.redoAction.setText( f'&Redo ({len(self.model.subsequentViews)})') id, properties, domain, domain_kind = self.getIDinfo(event) if id != '-1': # Domain ID domainID = self.menu.addAction(f"{domain_kind} {id}") domainID.setDisabled(True) # Domain Name (if any) if domain[id].name: domainName = self.menu.addAction(domain[id].name) domainName.setDisabled(True) self.menu.addSeparator() self.menu.addAction(self.mw.undoAction) self.menu.addAction(self.mw.redoAction) self.menu.addSeparator() colorAction = self.menu.addAction(f'Edit {domain_kind} Color...') colorAction.setDisabled(self.model.currentView.highlighting) colorAction.setToolTip(f'Edit {domain_kind} color') colorAction.setStatusTip(f'Edit {domain_kind} color') colorAction.triggered.connect( lambda: self.mw.editDomainColor(domain_kind, id)) maskAction = self.menu.addAction(f'Mask {domain_kind}') maskAction.setCheckable(True) maskAction.setChecked(domain[id].masked) maskAction.setDisabled(not self.model.currentView.masking) maskAction.setToolTip(f'Toggle {domain_kind} mask') maskAction.setStatusTip(f'Toggle {domain_kind} mask') maskAction.triggered[bool].connect( lambda bool=bool: self.mw.toggleDomainMask( bool, domain_kind, id)) highlightAction = self.menu.addAction(f'Highlight {domain_kind}') highlightAction.setCheckable(True) highlightAction.setChecked(domain[id].highlighted) highlightAction.setDisabled( not self.model.currentView.highlighting) highlightAction.setToolTip(f'Toggle {domain_kind} highlight') highlightAction.setStatusTip(f'Toggle {domain_kind} highlight') highlightAction.triggered[bool].connect( lambda bool=bool: self.mw.toggleDomainHighlight( bool, domain_kind, id)) else: self.menu.addAction(self.mw.undoAction) self.menu.addAction(self.mw.redoAction) self.menu.addSeparator() bgColorAction = self.menu.addAction('Edit Background Color...') bgColorAction.setToolTip('Edit background color') bgColorAction.setStatusTip('Edit plot background color') bgColorAction.triggered.connect( lambda: self.mw.editBackgroundColor(apply=True)) self.menu.addSeparator() self.menu.addAction(self.mw.saveImageAction) self.menu.addAction(self.mw.saveViewAction) self.menu.addAction(self.mw.openAction) self.menu.addSeparator() self.menu.addMenu(self.mw.basisMenu) self.menu.addMenu(self.mw.colorbyMenu) self.menu.addSeparator() self.menu.addAction(self.mw.maskingAction) self.menu.addAction(self.mw.highlightingAct) self.menu.addSeparator() self.menu.addAction(self.mw.dockAction) self.mw.maskingAction.setChecked(self.model.currentView.masking) self.mw.highlightingAct.setChecked(self.model.currentView.highlighting) if self.mw.dock.isVisible(): self.mw.dockAction.setText('Hide &Dock') else: self.mw.dockAction.setText('Show &Dock') self.menu.exec_(event.globalPos()) def setPixmap(self, w, h): # clear out figure self.figure.clear() cv = self.model.currentView # set figure bg color to match window window_background = self.parent.palette().color( QtGui.QPalette.Background) self.figure.patch.set_facecolor( rgb_normalize(window_background.getRgb())) # set figure width self.figure.set_figwidth(0.99 * w / self.figure.get_dpi()) self.figure.set_figheight(0.99 * h / self.figure.get_dpi()) # set data extents for automatic reporting of pointer location data_bounds = [ cv.origin[self.mw.xBasis] - cv.width / 2., cv.origin[self.mw.xBasis] + cv.width / 2., cv.origin[self.mw.yBasis] - cv.height / 2., cv.origin[self.mw.yBasis] + cv.height / 2. ] # make sure we have an image to load if not hasattr(self.model, 'image'): self.model.generatePlot() c = self.figure.subplots().imshow(self.model.image, extent=data_bounds, alpha=cv.plotAlpha) self.ax = self.figure.axes[0] self.ax.margins(0.0, 0.0) self.figure.set_tight_layout({'pad': 1.0}) # set axis labels axis_label_str = "{} (cm)" self.ax.set_xlabel(axis_label_str.format(cv.basis[0])) self.ax.set_ylabel(axis_label_str.format(cv.basis[1])) self.draw()
class PlotImage(FigureCanvas): def __init__(self, model, parent, main_window): self.figure = Figure(dpi=main_window.logicalDpiX()) super().__init__(self.figure) FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) self.model = model self.main_window = main_window self.parent = parent self.rubber_band = QRubberBand(QRubberBand.Rectangle, self) self.band_origin = QtCore.QPoint() self.x_plot_origin = None self.y_plot_origin = None self.colorbar = None self.data_indicator = None self.tally_data_indicator = None self.image = None self.menu = QMenu(self) def enterEvent(self, event): self.setCursor(QtCore.Qt.CrossCursor) self.main_window.coord_label.show() def leaveEvent(self, event): self.main_window.coord_label.hide() self.main_window.statusBar().showMessage("") def mousePressEvent(self, event): self.main_window.coord_label.hide() position = event.pos() # Set rubber band absolute and relative position self.band_origin = position self.x_plot_origin, self.y_plot_origin = self.getPlotCoords(position) # Create rubber band self.rubber_band.setGeometry( QtCore.QRect(self.band_origin, QtCore.QSize())) def getPlotCoords(self, pos): x, y = self.mouseEventCoords(pos) # get the normalized axis coordinates from the event display units transform = self.ax.transAxes.inverted() xPlotCoord, yPlotCoord = transform.transform((x, y)) # flip the y-axis (its zero is in the upper left) # scale axes using the plot extents xPlotCoord = self.ax.dataLim.x0 + xPlotCoord * self.ax.dataLim.width yPlotCoord = self.ax.dataLim.y0 + yPlotCoord * self.ax.dataLim.height # set coordinate label if pointer is in the axes if self.parent.underMouse(): self.main_window.coord_label.show() self.main_window.showCoords(xPlotCoord, yPlotCoord) else: self.main_window.coord_label.hide() return (xPlotCoord, yPlotCoord) def _resize(self): z = self.main_window.zoom / 100.0 # manage scroll bars if z <= 1.0: self.parent.verticalScrollBar().hide() self.parent.horizontalScrollBar().hide() self.parent.cornerWidget().hide() self.parent.verticalScrollBar().setEnabled(False) self.parent.horizontalScrollBar().setEnabled(False) else: self.parent.verticalScrollBar().show() self.parent.horizontalScrollBar().show() self.parent.cornerWidget().show() self.parent.verticalScrollBar().setEnabled(True) self.parent.horizontalScrollBar().setEnabled(True) # resize plot self.resize(self.parent.width() * z, self.parent.height() * z) def getDataIndices(self, event): cv = self.model.currentView x, y = self.mouseEventCoords(event.pos()) # get origin in axes coordinates x0, y0 = self.ax.transAxes.transform((0.0, 0.0)) # get the extents of the axes box in axes coordinates bbox = self.ax.get_window_extent().transformed( self.figure.dpi_scale_trans.inverted()) # get dimensions and scale using dpi width, height = bbox.width, bbox.height width *= self.figure.dpi height *= self.figure.dpi # use factor to get proper x,y position in pixels factor = (width / cv.h_res, height / cv.v_res) xPos = int((x - x0 + 0.01) / factor[0]) # flip y-axis yPos = cv.v_res - int((y - y0 + 0.01) / factor[1]) return xPos, yPos def getTallyIndices(self, event): xPos, yPos = self.getPlotCoords(event.pos()) ext = self.model.tally_extents x0 = ext[0] y0 = ext[2] v_res, h_res = self.model.tally_data.shape dx = (ext[1] - ext[0]) / h_res dy = (ext[3] - ext[2]) / v_res i = int((xPos - x0) // dx) j = v_res - int((yPos - y0) // dy) - 1 return i, j def getTallyInfo(self, event): cv = self.model.currentView xPos, yPos = self.getTallyIndices(event) if self.model.tally_data is None: return -1, None if not cv.selectedTally or not cv.tallyDataVisible: return -1, None # don't look up mesh filter data (for now) tally = self.model.statepoint.tallies[cv.selectedTally] # check that the position is in the axes view v_res, h_res = self.model.tally_data.shape if 0 <= yPos < v_res and 0 <= xPos < h_res: value = self.model.tally_data[yPos][xPos] else: value = None return cv.selectedTally, value def getIDinfo(self, event): xPos, yPos = self.getDataIndices(event) # check that the position is in the axes view if 0 <= yPos < self.model.currentView.v_res \ and 0 <= xPos and xPos < self.model.currentView.h_res: id = self.model.ids[yPos][xPos] temp = "{:g}".format(self.model.properties[yPos][xPos][0]) density = "{:g}".format(self.model.properties[yPos][xPos][1]) else: id = _NOT_FOUND density = str(_NOT_FOUND) temp = str(_NOT_FOUND) if self.model.currentView.colorby == 'cell': domain = self.model.activeView.cells domain_kind = 'Cell' elif self.model.currentView.colorby == 'temperature': domain = self.model.activeView.materials domain_kind = 'Temperature' elif self.model.currentView.colorby == 'density': domain = self.model.activeView.materials domain_kind = 'Density' else: domain = self.model.activeView.materials domain_kind = 'Material' properties = {'density': density, 'temperature': temp} return id, properties, domain, domain_kind def mouseDoubleClickEvent(self, event): xCenter, yCenter = self.getPlotCoords(event.pos()) self.main_window.editPlotOrigin(xCenter, yCenter, apply=True) def mouseMoveEvent(self, event): cv = self.model.currentView # Show Cursor position relative to plot in status bar xPlotPos, yPlotPos = self.getPlotCoords(event.pos()) # Show Cell/Material ID, Name in status bar id, properties, domain, domain_kind = self.getIDinfo(event) domainInfo = "" tallyInfo = "" if self.parent.underMouse(): if domain_kind.lower() in _MODEL_PROPERTIES: line_val = float(properties[domain_kind.lower()]) line_val = max(line_val, 0.0) self.updateDataIndicatorValue(line_val) domain_kind = 'Material' temperature = properties['temperature'] density = properties['density'] if id == _VOID_REGION: domainInfo = ("VOID") elif id == _OVERLAP: domainInfo = ("OVERLAP") elif id != _NOT_FOUND and domain[id].name: domainInfo = ("{} {}: \"{}\"\t Density: {} g/cc\t" "Temperature: {} K".format( domain_kind, id, domain[id].name, density, temperature)) elif id != _NOT_FOUND: domainInfo = ("{} {}\t Density: {} g/cc\t" "Temperature: {} K".format( domain_kind, id, density, temperature)) else: domainInfo = "" if self.model.tally_data is not None: tid, value = self.getTallyInfo(event) if value is not None and value != np.nan: self.updateTallyDataIndicatorValue(value) tallyInfo = "Tally {} {}: {:.5E}".format( tid, cv.tallyValue, value) else: self.updateTallyDataIndicatorValue(0.0) else: self.updateTallyDataIndicatorValue(0.0) self.updateDataIndicatorValue(0.0) if domainInfo: self.main_window.statusBar().showMessage(" " + domainInfo + " " + tallyInfo) else: self.main_window.statusBar().showMessage(" " + tallyInfo) # Update rubber band and values if mouse button held down if event.buttons() == QtCore.Qt.LeftButton: self.rubber_band.setGeometry( QtCore.QRect(self.band_origin, event.pos()).normalized()) # Show rubber band if both dimensions > 10 pixels if self.rubber_band.width() > 10 and self.rubber_band.height( ) > 10: self.rubber_band.show() else: self.rubber_band.hide() # Update plot X Origin xCenter = (self.x_plot_origin + xPlotPos) / 2 yCenter = (self.y_plot_origin + yPlotPos) / 2 self.main_window.editPlotOrigin(xCenter, yCenter) modifiers = event.modifiers() # Zoom out if Shift held if modifiers == QtCore.Qt.ShiftModifier: cv = self.model.currentView bandwidth = abs(self.band_origin.x() - event.pos().x()) width = cv.width * (cv.h_res / max(bandwidth, .001)) bandheight = abs(self.band_origin.y() - event.pos().y()) height = cv.height * (cv.v_res / max(bandheight, .001)) # Zoom in else: width = max(abs(self.x_plot_origin - xPlotPos), 0.1) height = max(abs(self.y_plot_origin - yPlotPos), 0.1) self.main_window.editWidth(width) self.main_window.editHeight(height) def mouseReleaseEvent(self, event): if self.rubber_band.isVisible(): self.rubber_band.hide() self.main_window.applyChanges() else: self.main_window.revertDockControls() def wheelEvent(self, event): if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier: numDegrees = event.delta() / 8 if 24 < self.main_window.zoom + numDegrees < 5001: self.main_window.editZoom(self.main_window.zoom + numDegrees) def contextMenuEvent(self, event): self.menu.clear() self.main_window.undoAction.setText('&Undo ({})'.format( len(self.model.previousViews))) self.main_window.redoAction.setText('&Redo ({})'.format( len(self.model.subsequentViews))) id, properties, domain, domain_kind = self.getIDinfo(event) cv = self.model.currentView # always provide undo option self.menu.addSeparator() self.menu.addAction(self.main_window.undoAction) self.menu.addAction(self.main_window.redoAction) self.menu.addSeparator() if int(id) not in (_NOT_FOUND, _OVERLAP) and \ cv.colorby not in _MODEL_PROPERTIES: # Domain ID if domain[id].name: domainID = self.menu.addAction("{} {}: \"{}\"".format( domain_kind, id, domain[id].name)) else: domainID = self.menu.addAction("{} {}".format(domain_kind, id)) self.menu.addSeparator() colorAction = self.menu.addAction( 'Edit {} Color...'.format(domain_kind)) colorAction.setDisabled(cv.highlighting) colorAction.setToolTip('Edit {} color'.format(domain_kind)) colorAction.setStatusTip('Edit {} color'.format(domain_kind)) domain_color_connector = partial(self.main_window.editDomainColor, domain_kind, id) colorAction.triggered.connect(domain_color_connector) maskAction = self.menu.addAction('Mask {}'.format(domain_kind)) maskAction.setCheckable(True) maskAction.setChecked(domain[id].masked) maskAction.setDisabled(not cv.masking) maskAction.setToolTip('Toggle {} mask'.format(domain_kind)) maskAction.setStatusTip('Toggle {} mask'.format(domain_kind)) mask_connector = partial(self.main_window.toggleDomainMask, kind=domain_kind, id=id) maskAction.toggled.connect(mask_connector) highlightAction = self.menu.addAction( 'Highlight {}'.format(domain_kind)) highlightAction.setCheckable(True) highlightAction.setChecked(domain[id].highlight) highlightAction.setDisabled(not cv.highlighting) highlightAction.setToolTip( 'Toggle {} highlight'.format(domain_kind)) highlightAction.setStatusTip( 'Toggle {} highlight'.format(domain_kind)) highlight_connector = partial( self.main_window.toggleDomainHighlight, kind=domain_kind, id=id) highlightAction.toggled.connect(highlight_connector) else: self.menu.addAction(self.main_window.undoAction) self.menu.addAction(self.main_window.redoAction) if cv.colorby not in _MODEL_PROPERTIES: self.menu.addSeparator() if int(id) == _NOT_FOUND: bgColorAction = self.menu.addAction( 'Edit Background Color...') bgColorAction.setToolTip('Edit background color') bgColorAction.setStatusTip('Edit plot background color') connector = partial(self.main_window.editBackgroundColor, apply=True) bgColorAction.triggered.connect(connector) elif int(id) == _OVERLAP: olapColorAction = self.menu.addAction( 'Edit Overlap Color...') olapColorAction.setToolTip('Edit overlap color') olapColorAction.setStatusTip('Edit plot overlap color') connector = partial(self.main_window.editOverlapColor, apply=True) olapColorAction.triggered.connect(connector) self.menu.addSeparator() self.menu.addAction(self.main_window.saveImageAction) self.menu.addAction(self.main_window.saveViewAction) self.menu.addAction(self.main_window.openAction) self.menu.addSeparator() self.menu.addMenu(self.main_window.basisMenu) self.menu.addMenu(self.main_window.colorbyMenu) self.menu.addSeparator() if domain_kind.lower() not in ('density', 'temperature'): self.menu.addAction(self.main_window.maskingAction) self.menu.addAction(self.main_window.highlightingAct) self.menu.addAction(self.main_window.overlapAct) self.menu.addSeparator() self.menu.addAction(self.main_window.dockAction) self.main_window.maskingAction.setChecked(cv.masking) self.main_window.highlightingAct.setChecked(cv.highlighting) self.main_window.overlapAct.setChecked(cv.color_overlaps) if self.main_window.dock.isVisible(): self.main_window.dockAction.setText('Hide &Dock') else: self.main_window.dockAction.setText('Show &Dock') self.menu.exec_(event.globalPos()) def generatePixmap(self, update=False): self.model.generatePlot() if update: self.updatePixmap() def updatePixmap(self): # clear out figure self.figure.clear() cv = self.model.currentView # set figure bg color to match window window_bg = self.parent.palette().color(QtGui.QPalette.Background) self.figure.patch.set_facecolor(rgb_normalize(window_bg.getRgb())) # set data extents for automatic reporting of pointer location # in model units data_bounds = [ cv.origin[self.main_window.xBasis] - cv.width / 2., cv.origin[self.main_window.xBasis] + cv.width / 2., cv.origin[self.main_window.yBasis] - cv.height / 2., cv.origin[self.main_window.yBasis] + cv.height / 2. ] # make sure we have a domain image to load if not hasattr(self.model, 'image'): self.model.generatePlot() ### DRAW DOMAIN IMAGE ### # still generate the domain image if the geometric # plot isn't visible so mouse-over info can still # be shown alpha = cv.domainAlpha if cv.domainVisible else 0.0 if cv.colorby in ('material', 'cell'): self.image = self.figure.subplots().imshow(self.model.image, extent=data_bounds, alpha=alpha) else: cmap = cv.colormaps[cv.colorby] if cv.colorby == 'temperature': idx = 0 cmap_label = "Temperature (K)" else: idx = 1 cmap_label = "Density (g/cc)" norm = SymLogNorm(1E-10) if cv.color_scale_log[ cv.colorby] else None data = self.model.properties[:, :, idx] self.image = self.figure.subplots().imshow(data, cmap=cmap, norm=norm, extent=data_bounds, alpha=cv.domainAlpha) # add colorbar self.colorbar = self.figure.colorbar(self.image, anchor=(1.0, 0.0)) self.colorbar.set_label(cmap_label, rotation=-90, labelpad=15) # draw line on colorbar dl = self.colorbar.ax.dataLim.get_points() self.data_indicator = mlines.Line2D(dl[:][0], [0.0, 0.0], linewidth=3., color='blue', clip_on=True) self.colorbar.ax.add_line(self.data_indicator) self.colorbar.ax.margins(0.0, 0.0) self.updateDataIndicatorVisibility() self.updateColorMinMax(cv.colorby) self.ax = self.figure.axes[0] self.ax.margins(0.0, 0.0) # set axis labels axis_label_str = "{} (cm)" self.ax.set_xlabel(axis_label_str.format(cv.basis[0])) self.ax.set_ylabel(axis_label_str.format(cv.basis[1])) # generate tally image image_data, extents, data_min, data_max, units = self.model.create_tally_image( ) ### DRAW TALLY IMAGE ### # draw tally image if image_data is not None: if not cv.tallyDataUserMinMax: cv.tallyDataMin = data_min cv.tallyDataMax = data_max else: data_min = cv.tallyDataMin data_max = cv.tallyDataMax # always mask out negative values image_mask = image_data < 0.0 if cv.clipTallyData: image_mask |= image_data < data_min image_mask |= image_data > data_max if cv.tallyMaskZeroValues: image_mask |= image_data == 0.0 # mask out invalid values image_data = np.ma.masked_where(image_mask, image_data) if extents is None: extents = data_bounds self.model.tally_data = image_data self.model.tally_extents = extents if extents is not None else data_bounds norm = SymLogNorm(1E-30) if cv.tallyDataLogScale else None if cv.tallyContours: # parse the levels line levels = self.parseContoursLine(cv.tallyContourLevels) self.tally_image = self.ax.contour(image_data, origin='image', levels=levels, alpha=cv.tallyDataAlpha, cmap=cv.tallyDataColormap, norm=norm, extent=extents) else: self.tally_image = self.ax.imshow(image_data, alpha=cv.tallyDataAlpha, cmap=cv.tallyDataColormap, norm=norm, extent=extents) # add colorbar self.tally_colorbar = self.figure.colorbar(self.tally_image, anchor=(1.0, 0.0)) if cv.tallyContours: fmt = "%.2E" self.ax.clabel(self.tally_image, self.tally_image.levels, inline=True, fmt=fmt) # draw line on colorbar self.tally_data_indicator = mlines.Line2D([0.0, 1.0], [0.0, 0.0], linewidth=3., color='blue', clip_on=True) self.tally_colorbar.ax.add_line(self.tally_data_indicator) self.tally_colorbar.ax.margins(0.0, 0.0) self.tally_data_indicator.set_visible(cv.tallyDataIndicator) self.main_window.updateTallyMinMax() self.tally_colorbar.mappable.set_clim(data_min, data_max) self.tally_colorbar.set_label(units, rotation=-90, labelpad=15) # annotate outlines self.add_outlines() # always make sure the data bounds are set correctly self.ax.set_xbound(data_bounds[0], data_bounds[1]) self.ax.set_ybound(data_bounds[2], data_bounds[3]) self.ax.dataLim.x0 = data_bounds[0] self.ax.dataLim.x1 = data_bounds[1] self.ax.dataLim.y0 = data_bounds[2] self.ax.dataLim.y1 = data_bounds[3] self.draw() return "Done" def add_outlines(self): cv = self.model.currentView # draw outlines as isocontours if cv.outlines: # set data extents for automatic reporting of pointer location data_bounds = [ cv.origin[self.main_window.xBasis] - cv.width / 2., cv.origin[self.main_window.xBasis] + cv.width / 2., cv.origin[self.main_window.yBasis] - cv.height / 2., cv.origin[self.main_window.yBasis] + cv.height / 2. ] levels = np.unique(self.model.ids) self.contours = self.ax.contour(self.model.ids, origin='upper', colors='k', linestyles='solid', levels=levels, extent=data_bounds) @staticmethod def parseContoursLine(line): # if there are any commas in the line, treat as level values line = line.strip() if ',' in line: return [float(val) for val in line.split(",") if val != ''] else: return int(line) def updateColorbarScale(self): self.updatePixmap() def updateTallyDataIndicatorValue(self, y_val): cv = self.model.currentView if not cv.tallyDataVisible or not cv.tallyDataIndicator: return if self.tally_data_indicator is not None: data = self.tally_data_indicator.get_data() # use norm to get axis value if log scale if cv.tallyDataLogScale: y_val = self.tally_image.norm(y_val) self.tally_data_indicator.set_data([data[0], [y_val, y_val]]) dl_color = invert_rgb(self.tally_image.get_cmap()(y_val), True) self.tally_data_indicator.set_c(dl_color) self.draw() def updateDataIndicatorValue(self, y_val): cv = self.model.currentView if cv.colorby not in _MODEL_PROPERTIES or \ not cv.data_indicator_enabled[cv.colorby]: return if self.data_indicator: data = self.data_indicator.get_data() # use norm to get axis value if log scale if cv.color_scale_log[cv.colorby]: y_val = self.image.norm(y_val) self.data_indicator.set_data([data[0], [y_val, y_val]]) dl_color = invert_rgb(self.image.get_cmap()(y_val), True) self.data_indicator.set_c(dl_color) self.draw() def updateDataIndicatorVisibility(self): cv = self.model.currentView if self.data_indicator and cv.colorby in _MODEL_PROPERTIES: val = cv.data_indicator_enabled[cv.colorby] self.data_indicator.set_visible(val) self.draw() def updateColorMap(self, colormap_name, property_type): if self.colorbar and property_type == self.model.activeView.colorby: self.image.set_cmap(colormap_name) self.colorbar.draw_all() self.draw() def updateColorMinMax(self, property_type): av = self.model.activeView if self.colorbar and property_type == av.colorby: clim = av.getColorLimits(property_type) self.colorbar.mappable.set_clim(*clim) self.data_indicator.set_data(clim[:2], (0.0, 0.0)) self.colorbar.draw_all() self.draw()
class PlotImage(QLabel): def __init__(self, model, FM, parent=None): super(PlotImage, self).__init__(parent) self.model = model self.FM = FM self.mw = parent self.setAlignment(QtCore.Qt.AlignCenter) self.setMouseTracking(True) self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.bandOrigin = QtCore.QPoint() self.xPlotOrigin = None self.yPlotOrigin = None self.menu = QMenu(self) def enterEvent(self, event): self.setCursor(QtCore.Qt.CrossCursor) self.mw.coordLabel.show() def leaveEvent(self, event): self.mw.coordLabel.hide() self.mw.statusBar().showMessage('') def mousePressEvent(self, event): # Set rubber band absolute and relative position self.bandOrigin = event.pos() self.xPlotOrigin, self.yPlotOrigin = self.getPlotCoords(event.pos()) # Create rubber band self.rubberBand.setGeometry( QtCore.QRect(self.bandOrigin, QtCore.QSize())) QLabel.mousePressEvent(self, event) def mouseDoubleClickEvent(self, event): xCenter, yCenter = self.getPlotCoords(event.pos()) self.mw.editPlotOrigin(xCenter, yCenter, apply=True) QLabel.mouseDoubleClickEvent(self, event) def mouseMoveEvent(self, event): # Show Cursor position relative to plot in status bar xPlotPos, yPlotPos = self.getPlotCoords(event.pos()) # Show Cell/Material ID, Name in status bar id, domain, domain_kind = self.getIDinfo(event) if id != '-1' and domain[id].name: domainInfo = f"{domain_kind} {id}: {domain[id].name}" elif id != '-1': domainInfo = f"{domain_kind} {id}" else: domainInfo = "" self.mw.statusBar().showMessage(f" {domainInfo}") # Update rubber band and values if mouse button held down if event.buttons() == QtCore.Qt.LeftButton: self.rubberBand.setGeometry( QtCore.QRect(self.bandOrigin, event.pos()).normalized()) # Show rubber band if both dimensions > 10 pixels if self.rubberBand.width() > 10 and self.rubberBand.height() > 10: self.rubberBand.show() else: self.rubberBand.hide() # Update plot X Origin xCenter = (self.xPlotOrigin + xPlotPos) / 2 yCenter = (self.yPlotOrigin + yPlotPos) / 2 self.mw.editPlotOrigin(xCenter, yCenter) modifiers = event.modifiers() # Zoom out if Shift held if modifiers == QtCore.Qt.ShiftModifier: cv = self.model.currentView bandwidth = abs(self.bandOrigin.x() - event.pos().x()) width = cv.width * (cv.hRes / max(bandwidth, .001)) bandheight = abs(self.bandOrigin.y() - event.pos().y()) height = cv.height * (cv.vRes / max(bandheight, .001)) else: # Zoom in width = max(abs(self.xPlotOrigin - xPlotPos), 0.1) height = max(abs(self.yPlotOrigin - yPlotPos), 0.1) self.mw.editWidth(width) self.mw.editHeight(height) def mouseReleaseEvent(self, event): if self.rubberBand.isVisible(): self.rubberBand.hide() self.mw.applyChanges() else: self.mw.revertDockControls() def wheelEvent(self, event): if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier: numDegrees = event.delta() / 8 if 24 < self.mw.zoom + numDegrees < 5001: self.mw.editZoom(self.mw.zoom + numDegrees) def contextMenuEvent(self, event): self.menu.clear() self.mw.undoAction.setText(f'&Undo ({len(self.model.previousViews)})') self.mw.redoAction.setText( f'&Redo ({len(self.model.subsequentViews)})') id, domain, domain_kind = self.getIDinfo(event) if id != '-1': # Domain ID domainID = self.menu.addAction(f"{domain_kind} {id}") domainID.setDisabled(True) # Domain Name (if any) if domain[id].name: domainName = self.menu.addAction(domain[id].name) domainName.setDisabled(True) self.menu.addSeparator() self.menu.addAction(self.mw.undoAction) self.menu.addAction(self.mw.redoAction) self.menu.addSeparator() colorAction = self.menu.addAction(f'Edit {domain_kind} Color...') colorAction.setDisabled(self.model.currentView.highlighting) colorAction.setToolTip(f'Edit {domain_kind} color') colorAction.setStatusTip(f'Edit {domain_kind} color') colorAction.triggered.connect( lambda: self.mw.editDomainColor(domain_kind, id)) maskAction = self.menu.addAction(f'Mask {domain_kind}') maskAction.setCheckable(True) maskAction.setChecked(domain[id].masked) maskAction.setDisabled(not self.model.currentView.masking) maskAction.setToolTip(f'Toggle {domain_kind} mask') maskAction.setStatusTip(f'Toggle {domain_kind} mask') maskAction.triggered[bool].connect( lambda bool=bool: self.mw.toggleDomainMask( bool, domain_kind, id)) highlightAction = self.menu.addAction(f'Highlight {domain_kind}') highlightAction.setCheckable(True) highlightAction.setChecked(domain[id].highlighted) highlightAction.setDisabled( not self.model.currentView.highlighting) highlightAction.setToolTip(f'Toggle {domain_kind} highlight') highlightAction.setStatusTip(f'Toggle {domain_kind} highlight') highlightAction.triggered[bool].connect( lambda bool=bool: self.mw.toggleDomainHighlight( bool, domain_kind, id)) else: self.menu.addAction(self.mw.undoAction) self.menu.addAction(self.mw.redoAction) self.menu.addSeparator() bgColorAction = self.menu.addAction('Edit Background Color...') bgColorAction.setToolTip('Edit background color') bgColorAction.setStatusTip('Edit plot background color') bgColorAction.triggered.connect( lambda: self.mw.editBackgroundColor(apply=True)) self.menu.addSeparator() self.menu.addAction(self.mw.saveImageAction) self.menu.addAction(self.mw.saveViewAction) self.menu.addAction(self.mw.openAction) self.menu.addSeparator() self.menu.addMenu(self.mw.basisMenu) self.menu.addMenu(self.mw.colorbyMenu) self.menu.addSeparator() self.menu.addAction(self.mw.maskingAction) self.menu.addAction(self.mw.highlightingAct) self.menu.addSeparator() self.menu.addAction(self.mw.dockAction) self.mw.maskingAction.setChecked(self.model.currentView.masking) self.mw.highlightingAct.setChecked(self.model.currentView.highlighting) if self.mw.dock.isVisible(): self.mw.dockAction.setText('Hide &Dock') else: self.mw.dockAction.setText('Show &Dock') self.menu.exec_(event.globalPos()) def getPlotCoords(self, pos): cv = self.model.currentView factor = (self.width() / cv.hRes, self.height() / cv.vRes) # Cursor position in pixels relative to center of plot image xPos = (pos.x() + 0.5) / factor[0] - cv.hRes / 2 yPos = (-pos.y() - 0.5) / factor[1] + cv.vRes / 2 # Curson position in plot coordinates xPlotCoord = (xPos / self.mw.scale[0]) + cv.origin[self.mw.xBasis] yPlotCoord = (yPos / self.mw.scale[1]) + cv.origin[self.mw.yBasis] self.mw.showCoords(xPlotCoord, yPlotCoord) return (xPlotCoord, yPlotCoord) def getIDinfo(self, event): cv = self.model.currentView factor = (self.width() / cv.hRes, self.height() / cv.vRes) xPos = int((event.pos().x() + .05) / factor[0]) yPos = int((event.pos().y() + .05) / factor[1]) if yPos < self.model.currentView.vRes \ and xPos < self.model.currentView.hRes: id = f"{self.model.ids[yPos][xPos]}" else: id = '-1' if self.model.currentView.colorby == 'cell': domain = self.model.activeView.cells domain_kind = 'Cell' else: domain = self.model.activeView.materials domain_kind = 'Material' return id, domain, domain_kind