def populate_spectrogram_widget(self, widget: GraphicsLayoutWidget, game_name: str, player_name: PlayerName, electrode_name: str): # https://stackoverflow.com/questions/51312923/plotting-the-spectrum-of-a-wavfile-in-pyqtgraph-using-scipy-signal-spectrogram f, t, Sxx = self._acquire_spectrogram_signal(game_name, player_name, electrode_name) plot = widget.addPlot() plot.setTitle('Frequency over time for electrode %s' % ELECTRODES[electrode_name]) img = ImageItem() plot.addItem(img) hist = HistogramLUTItem() hist.setImageItem(img) widget.addItem(hist) hist.setLevels(np.min(Sxx), np.max(Sxx)) hist.gradient.restoreState({ 'mode': 'rgb', 'ticks': [(0.5, (0, 182, 188, 255)), (1.0, (246, 111, 0, 255)), (0.0, (75, 0, 113, 255))] }) img.setImage(Sxx) img.scale(t[-1] / np.size(Sxx, axis=1), f[-1] / np.size(Sxx, axis=0)) plot.setLimits(xMin=0, xMax=t[-1], yMin=0, yMax=f[-1]) plot.setLabel('bottom', "Time", units='s') plot.setLabel('left', "Frequency", units='Hz')
def get_spectrogram(wav: WavFile, graphics_layout: pyqtgraph.GraphicsLayoutWidget): f, t, Sxx = signal.spectrogram(wav.data, wav.rate) # Interpret image data as row-major instead of col-major pyqtgraph.setConfigOptions(imageAxisOrder='row-major') pyqtgraph.mkQApp() graphics_layout.clear() plot_widget = graphics_layout.addPlot() # A plot area (ViewBox + axes) for displaying the image # Item for displaying image data img = pyqtgraph.ImageItem() plot_widget.addItem(img) # Add a histogram with which to control the gradient of the image hist = pyqtgraph.HistogramLUTItem() # Link the histogram to the image hist.setImageItem(img) # If you don't add the histogram to the window, it stays invisible, but I find it useful. graphics_layout.addItem(hist) # Show the window graphics_layout.show() # Fit the min and max levels of the histogram to the data available #print("min: "+ str(np.min(Sxx)) + "max:" + str(np.max(Sxx))) hist.setLevels(0, 40000) # This gradient is roughly comparable to the gradient used by Matplotlib # You can adjust it and then save it using hist.gradient.saveState() hist.gradient.restoreState( {'mode': 'rgb', 'ticks': [(0.5, (0, 182, 188, 255)), (1.0, (246, 111, 0, 255)), (0.0, (75, 0, 113, 255))]}) # Sxx contains the amplitude for each pixel img.setImage(Sxx) # Scale the X and Y Axis to time and frequency (standard is pixels) img.scale(t[-1] / np.size(Sxx, axis=1), f[-1] / np.size(Sxx, axis=0)) # Limit panning/zooming to the spectrogram plot_widget.setLimits(xMin=0, xMax=t[-1], yMin=0, yMax=f[-1]) # Add labels to the axis plot_widget.setLabel('bottom', "Time", units='s') # If you include the units, Pyqtgraph automatically scales the axis and adjusts the SI prefix (in this case kHz) plot_widget.setLabel('left', "Frequency", units='Hz')
class widget_fc_cup( QWidget ) : #----------------------------------------------------------------------- # DEFINE THE INITIALIZATION FUNCTION. #----------------------------------------------------------------------- def __init__( self, core, cup, n_plt_x=None, n_plt_y=None, n_plt=None ) : # Inherit all attributes of an instance of "QWidget". super( widget_fc_cup, self ).__init__( ) # Initialize the counter of repaint events for this widget as # well as a maximum value for this counter. # Note. For some reason, adjusting the individual plots to have # uniform sizes is difficult to achieve before the widget # is rendered. Thus, once a paint event occurs, the # "self.paintEvent( )" function picks it up and makes a # call to "self.ajst_grd( )". This counter and its # maximum value are used ensure that "self.paintEvent( )" # makes such a call only in response to the intial few # painting (so as to prevent an infinite loop). # Note. The first paint seems to be a "dummy" of some sort. # Whatever the case, "self.n_paint_max = 1" seems to # generally be insufficient. self.n_painted = 0 self.n_painted_max = 3 # Store the Janus core and the cup number. # Note. The cups are traditionally numbered "1" and "2", but, # in this class, they are identified by the altitude # index "t" which takes the values "0" and "1", # respectively. self.core = core self.c = None self.c = 0 if ( cup == 1 ) else self.c self.c = 1 if ( cup == 2 ) else self.c # Prepare to respond to signals received from the Janus core. self.connect( self.core, SIGNAL('janus_rset'), self.resp_rset ) self.connect( self.core, SIGNAL('janus_chng_spc'), self.resp_chng_spc ) self.connect( self.core, SIGNAL('janus_chng_mom_sel_bin'), self.resp_chng_mom_sel_bin ) self.connect( self.core, SIGNAL('janus_chng_mom_sel_dir'), self.resp_chng_mom_sel_dir ) self.connect( self.core, SIGNAL('janus_chng_mom_sel_all'), self.resp_chng_mom_sel_all ) self.connect( self.core, SIGNAL('janus_chng_mom_res'), self.resp_chng_mom_res ) self.connect( self.core, SIGNAL('janus_chng_nln_gss'), self.resp_chng_nln_gss ) self.connect( self.core, SIGNAL('janus_chng_nln_sel_bin'), self.resp_chng_nln_sel_bin ) self.connect( self.core, SIGNAL('janus_chng_nln_sel_all'), self.resp_chng_nln_sel_all ) self.connect( self.core, SIGNAL('janus_chng_nln_res'), self.resp_chng_nln_res ) self.connect( self.core, SIGNAL('janus_chng_dsp'), self.resp_chng_dsp ) # Assign (if not done so already) and store the shape of the # plot-grid array. self.n_plt_x = 4 if ( n_plt_x is None ) else n_plt_x self.n_plt_y = 5 if ( n_plt_y is None ) else n_plt_y if ( n_plt is None ) : self.n_plt = self.n_plt_x * self.n_plt_y # Initizalize the pens, brushes, and fonts used by this widget. self.pen_plt = mkPen( color='k' ) self.pen_hst = mkPen( color='k' ) self.pen_pnt_c = mkPen( color='k' ) self.pen_pnt_y = mkPen( color='k' ) self.pen_pnt_r = mkPen( color='k' ) self.pen_crv_b = mkPen( color='b' ) self.pen_crv_g = mkPen( color='g' ) self.bsh_pnt_c = mkBrush( color='c' ) self.bsh_pnt_y = mkBrush( color='y' ) self.bsh_pnt_r = mkBrush( color='r' ) self.fnt = self.core.app.font( ) # Set the maximum number of velocity channels and the maximum # number of ion species. self.n_k = 31 self.n_ion = self.core.nln_n_pop # Initialize the widget and it's plot's. self.init_plt( ) # Populate the plots with the histograms (and labels), the # selection points, and the fit curves. self.make_hst( ) self.make_pnt( ) self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR INITIALIZING THE WIDGET AND ITS PLOTS. #----------------------------------------------------------------------- def init_plt( self ) : # Initialize the "GraphicsLayoutWidget" for this widget. This # will allow a grid of "GraphicsItem" objects, which will # include the plots themselves, the axes, and the axis labels. # Note. The "QGridLayout" object given to this widget as its # layout is essentially a dummy. I tried to just having # this widget be an extention of "GraphicsLayoutWidget" # (i.e., having it inheret that type), but I couldn't get # it to display anything at all. self.setLayout( QGridLayout( ) ) self.grd = GraphicsLayoutWidget( ) self.grd.setBackground( 'w' ) self.layout( ).addWidget( self.grd ) self.layout().setContentsMargins( 0, 0, 0, 0 ) # Initialize the text for the x- and y-axis labels. Then, # create the labels themselves and add them to the grid. self.txt_axs_x = 'Projected Proton Inflow Velocity [km/s]' self.txt_axs_y = 'Current [pA]' if ( self.core.app.res_lo ) : size = '8pt' else : size = '10pt' self.lab_axs_x = LabelItem( self.txt_axs_x, angle=0 , color='b', size=size ) self.lab_axs_y = LabelItem( self.txt_axs_y, angle=270, color='b', size=size ) self.grd.addItem( self.lab_axs_x, self.n_plt_y + 1, 2, 1, self.n_plt_x ) self.grd.addItem( self.lab_axs_y, 0, 0, self.n_plt_y, 1 ) # Initialize the arrays that will contain the individual axes, # plots, and plot elements (i.e., the histograms, fit curves, # labels, and selection points). self.plt = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.axs_x = tile( None, self.n_plt_x ) self.axs_y = tile( None, self.n_plt_y ) self.hst = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.lbl = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.crv = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.crv_ion = tile( None, [ self.n_plt_y, self.n_plt_x, self.n_ion ] ) self.pnt = tile( None, [ self.n_plt_y, self.n_plt_x, self.n_k ] ) # Initialize the scale-type for each axis, then generate the # (default) axis-limits and adjusted axis-limits. self.log_x = False self.log_y = True self.make_lim( ) # Create, store, and add to the grid the individual axes: first # the horizontal and then the vertical. for i in range( self.n_plt_x ) : self.axs_x[i] = AxisItem( 'bottom', maxTickLength=5 ) self.axs_x[i].setLogMode( self.log_x ) self.axs_x[i].setRange( self.alm_x[0], self.alm_x[1] ) self.axs_x[i].setTickFont( self.fnt ) if ( self.core.app.res_lo ) : self.axs_x[i].setHeight( 10 ) else : self.axs_x[i].setHeight( 20 ) self.grd.addItem( self.axs_x[i], self.n_plt_y, i + 2 ) for j in range( self.n_plt_y ) : self.axs_y[j] = AxisItem( 'left', maxTickLength=5 ) self.axs_y[j].setLogMode( self.log_y ) self.axs_y[j].setRange( self.alm_y[0], self.alm_y[1] ) self.axs_y[j].setTickFont( self.fnt ) if ( self.core.app.res_lo ) : self.axs_y[j].setWidth( 32 ) else : self.axs_y[j].setWidth( 40 ) self.grd.addItem( self.axs_y[j], j, 1 ) # Create, store, and add to the grid the individual plots. # Likewise, create, store, and add to each plot a label. for j in range( self.n_plt_y ) : for i in range( self.n_plt_x ) : # Compute the plot number of this plot. d = self.calc_ind_d( j, i ) # If creating this plot would exceed the # specified number of plots, don't create it. if ( d >= self.n_plt ) : continue # Create and store this plot, adjust its limits, # and add it to the grid. self.plt[j,i] = event_ViewBox( self, border=self.pen_plt, enableMouse=False, enableMenu=False ) self.plt[j,i].setRange( xRange=self.alm_x, yRange=self.alm_y, padding=0. ) self.grd.addItem( self.plt[j,i], j, i + 2 ) # Create and store an (empty) label and add it # to this plot. self.lbl[j,i] = TextItem( anchor=(1,0) ) self.lbl[j,i].setFont( self.fnt ) self.plt[j,i].addItem( self.lbl[j,i] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR GENERATING AXIS-LIMITS (AND ADJUSTED LIMITS). #----------------------------------------------------------------------- def make_lim( self ) : # If no spectrum has been loaded, use the default limits; # otherwise, use the spectral data to compute axis limits. if ( self.core.fc_spec is None ) : self.lim_x = [ 250. , 750. ] self.lim_y = [ 0.7, 70. ] else : self.lim_x = [self.core.fc_spec['vel_strt'][self.c][0 ], self.core.fc_spec['vel_stop'][self.c][-1]] arr_curr_flat = self.core.fc_spec['curr_flat'] self.lim_y = [ min(arr_curr_flat), max(arr_curr_flat) ] if ( self.log_y ) : self.lim_y[1] = self.lim_y[1] ** 1.1 else : self.lim_y[1] += 0.1 * ( self.lim_y[1] - self.lim_y[0] ) # Compute the "adjusted limits" for each axis. if ( self.log_x ) : self.alm_x = [ log10( x ) for x in self.lim_x ] else : self.alm_x = self.lim_x if ( self.log_y ) : self.alm_y = [ log10( y ) for y in self.lim_y ] else : self.alm_y = self.lim_y #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CREATING THE PLOTS' HISTOGRAMS (AND LABELS). #----------------------------------------------------------------------- def make_hst( self, curr_min=0.69 ) : # If no spectrum has been loaded, clear any existing histograms # and abort. if ( self.core.fc_spec is None ) : self.rset_hst( ) return # Use the spectral data to compute new axis-limits. self.make_lim( ) # Generate a step function for each look direction associated # with this widget. self.stp = array( [ step( self.core.fc_spec['vel_cen'][self.c] , self.core.fc_spec['vel_del'][self.c] , self.core.fc_spec['curr'][self.c][d]) for d in range(self.core.fc_spec['n_dir']) ]) stp_pnt = array( [ array( self.stp[d]\ .calc_pnt( lev_min=curr_min ) ) for d in range( self.core.fc_spec['n_dir'] ) ] ) self.stp_x = stp_pnt[:,0,:] self.stp_y = stp_pnt[:,1,:] self.asp_x = log10( self.stp_x ) if ( self.log_x ) else \ self.stp_x self.asp_y = log10( self.stp_y ) if ( self.log_y ) else \ self.stp_y # Adjust the individual axes to the new limits. for i in range( self.n_plt_x ) : self.axs_x[i].setRange( self.alm_x[0], self.alm_x[1] ) for j in range( self.n_plt_y ) : self.axs_y[j].setRange( self.alm_y[0], self.alm_y[1] ) # For each plot in the grid, adjust its limits, add a histogram, # and add a direction label. for d in range( min( self.core.fc_spec['n_dir'], self.n_plt ) ) : # Determine the location of this plot within the grid # layout. j = self.calc_ind_j( d ) i = self.calc_ind_i( d ) # If this plot does not exist, move onto the next one. if ( self.plt[j,i] is None ) : continue # If a histogram already exists for this plot, remove # and delete it. if ( self.hst[j,i] is not None ) : self.plt[j,i].removeItem( self.hst[j,i] ) self.hst[j,i] = None # Clear this plot's label of text. self.lbl[j,i].setText( '' ) # Adjust this plot's limits and then move it's label in # response. self.plt[j,i].setRange( xRange=self.alm_x, yRange=self.alm_y, padding=0. ) self.lbl[j,i].setPos( self.alm_x[1], self.alm_y[1] ) # Update this plot's label with appropriate text # indicating the pointing direction. r_alt = round( self.core.fc_spec['elev'][self.c] ) r_dir = round( self.core.fc_spec['azim'][self.c][d]) txt = ( u'({0:+.0f}\N{DEGREE SIGN}, ' + u'{1:+.0f}\N{DEGREE SIGN})' ).format( r_alt, r_dir ) self.lbl[j,i].setText( txt, color=(0,0,0) ) #self.lbl[j,i].setFont( self.fnt ) # Generate the histogram for the data from this look # direction and display it in the plot. self.hst[j,i] = PlotDataItem( self.asp_x[d,:], self.asp_y[d,:], pen=self.pen_hst ) self.plt[j,i].addItem( self.hst[j,i] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CREATING THE PLOTS' SELECTION POINTS. #----------------------------------------------------------------------- def make_pnt( self, curr_min=0.69 ) : # Add selection points to each plot. if ( self.core.fc_spec is None ) : return # Add selection points to each plot. for d in range( min( self.core.fc_spec['n_dir'], self.n_plt ) ) : # Determine the location of this plot within the grid # layout. j = self.calc_ind_j( d ) i = self.calc_ind_i( d ) # If this plot does not exist, move onto the next one. if ( self.plt[j,i] is None ) : continue # Add the selection points to this plot. for b in range( self.core.fc_spec['n_bin'] ) : sel_bin = False sel_dir = True sel_alt = None if ( ( self.core.dsp == 'mom' ) and ( self.core.mom_sel_bin is not None ) and ( self.core.mom_sel_dir is not None ) ) : sel_bin = \ self.core.mom_sel_bin[self.c][d][b] sel_dir = \ self.core.mom_sel_dir[self.c][d] elif ( ( self.core.dsp == 'gsl' ) and ( self.core.nln_sel is not None ) ) : sel_bin = self.core.nln_sel[self.c][d][b] elif ( ( self.core.dsp == 'nln' ) and ( self.core.nln_res_sel is not None ) ) : sel_bin = \ self.core.nln_res_sel[self.c][d][b] if ( self.core.nln_sel is None ) : sel_alt = None else : sel_alt = \ self.core.nln_sel[self.c][d][b] self.chng_pnt( j, i, b, sel_bin, sel_dir=sel_dir, sel_alt=sel_alt ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CREATING THE PLOTS' FIT CURVES. #----------------------------------------------------------------------- def make_crv( self, d_lst=None ) : # If no "list" of "p" index-values has been provided by the # user, assume that the curves in all plots should be # (re-)rendered. if ( self.core.fc_spec is None ) : return if ( d_lst is None ) : d_lst = range( min( self.core.fc_spec['n_dir'], self.n_plt ) ) # If the results of the analysis are missing, abort; otherwise, # extract the current values to be plotted. if ( self.core.dsp == 'mom' ) : curr = self.core.mom_curr curr_ion = None elif ( self.core.dsp == 'gsl' ) : curr = self.core.nln_gss_curr_tot curr_ion = self.core.nln_gss_curr_ion elif ( self.core.dsp == 'nln' ) : curr = self.core.nln_res_curr_tot curr_ion = self.core.nln_res_curr_ion else : curr = None curr_ion = None # For each plot in the grid, generate and display a fit curve # based on the results of the analysis. vel_cen = self.core.fc_spec['vel_cen'][self.c] for d in d_lst : # Determine the location of this plot within the grid # layout. j = self.calc_ind_j( d ) i = self.calc_ind_i( d ) # If this plot does not exist, move onto the next grid # element. if ( self.plt[j,i] is None ) : continue # If any curves already exist for this plot, remove and # delete them. if ( self.crv[j,i] is not None ) : self.plt[j,i].removeItem( self.crv[j,i] ) self.crv[j,i] = None for n in range( self.n_ion ) : if ( self.crv_ion[j,i,n] is not None ) : self.plt[j,i].removeItem( self.crv_ion[j,i,n] ) self.crv_ion[j,i,n] = None # Create and add the curve of the individual # contributions to the modeled current to the plot. if ( curr_ion is not None ) : for n in range( len( curr_ion[self.c][d][0] ) ) : # Extract the points for this fit curve. x = array( vel_cen ) y = array( [ curr_ion[self.c][d][b][n] for b in range( self.core.fc_spec['n_bin'] ) ] ) # Select only those points for which # the fit current is strictly positive. tk = where( y > 0. )[0] # If fewer than two curve points were # selected, skip plotting this curve. if ( len( tk ) < 2 ) : continue # Generate the adjusted points for this # curve. if ( self.log_x ) : ax = log10( x[tk] ) else : ax = x[tk] if ( self.log_y ) : ay = log10( y[tk] ) else : ay = y[tk] # Create, store, and add to the plot # this fit curve. self.crv_ion[j,i,n] = PlotDataItem( ax, ay, pen=self.pen_crv_g ) self.plt[j,i].addItem( self.crv_ion[j,i,n] ) # Create, store, and add to the plot a curve for the # total fit current. if ( curr is not None ) : # Extract the points of the fit curve. x = [ self.core.fc_spec.arr[self.c][0][b][ 'vel_cen'] for b in range( self.core.fc_spec['n_bin'] ) ] y = curr[self.c][d] x = array( x ) y = array( y ) # Select only those points for which the fit # current is strictly positive. valid = [ yy > 0. for yy in y ] n_a = sum( valid ) ax = [ xx for xx, vv in zip( x, valid ) if vv ] ay = [ yy for yy, vv in zip( y, valid ) if vv ] # If at least two points were selected, proceed # with plotting. if ( n_a >= 2 ) : # If needed, convert to a logarithmic # scale. if ( self.log_x ) : ax = [ log10( xx ) for xx in ax ] if ( self.log_y ) : ay = [ log10( yy ) for yy in ay ] # Create, store, and add to the plot # this fit curve. self.crv[j,i] = PlotDataItem( ax, ay, pen=self.pen_crv_b ) self.plt[j,i].addItem( self.crv[j,i] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CHANGING THE VISIBILITY OF A DATUM'S POINTS. #----------------------------------------------------------------------- def chng_pnt( self, j, i, b, sel_bin, sel_dir=True, sel_alt=None ) : # If this point already exists, remove it from its plot and # delete it. if ( self.pnt[j,i,b] is not None ) : self.plt[j,i].removeItem( self.pnt[j,i,b] ) self.pnt[j,i,b] = None # If the user gave no alternative selection state for this # datum, use the primary state for the secondary state. sel_alt = sel_bin if ( sel_alt is None ) else sel_alt # If this point was not selected (based on both its primary and # secondary states), return (since there's nothing more to be # done). if ( ( not sel_bin ) and ( not sel_alt ) ) : return # Determine the "d" index corresponding to this look direction. d = self.calc_ind_d( j, i ) # Computed the adjusted point location in the "ViewBox". if ( self.log_x ) : ax = log10( self.core.fc_spec['vel_cen'][self.c][b] ) else : ax = self.core.fc_spec['vel_cen'][self.c][b] if ( self.log_y ) : ay = log10( self.core.fc_spec['curr'][self.c][d][b] ) else : ay = self.core.fc_spec['curr'][self.c][d][b] # Select the color for the point (i.e., the brush and pen used # to render it) based on whether or not this datum's look # direction has been selected and whether or not the primary and # secondary selection states match. if ( sel_dir ) : if ( sel_bin == sel_alt ) : pen = self.pen_pnt_c brush = self.bsh_pnt_c else : pen = self.pen_pnt_y brush = self.bsh_pnt_y else : pen = self.pen_pnt_r brush = self.bsh_pnt_r # Select the symbol based on the values of the primary and # secondary selection states. # Note. At this point in the code, at least one of these two # states must be "True" since this -- when both states # are "False", this function returns before it reaches # this point. if ( sel_bin ) : if ( sel_alt ) : symbol = 's' else : symbol = 'o' else : symbol = 't' # Create, store, and add this selection point to the plot. if ( self.core.app.res_lo ) : size = 3 else : size = 6 self.pnt[j,i,b] = PlotDataItem( [ax], [ay], symbol=symbol, symbolSize=size, symbolPen=pen, symbolBrush=brush ) self.plt[j,i].addItem( self.pnt[j,i,b] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESETTING THE PLOTS' HISTOGRAMS (AND LABELS). #----------------------------------------------------------------------- def rset_hst( self, rset_lbl=False ) : # For each plot that exists in the grid, remove and delete it's # histogram. Likewise, if requested, empty it's label (but # still leave the label itself intact). for j in range( self.n_plt_y ) : for i in range( self.n_plt_x ) : # If the plot does not exist, move onto the the # next one. if ( self.plt[j,i] is None ) : continue # If a histogram exists for this plot, remove # and delete it. if ( self.hst[j,i] is not None ) : self.plt[j,i].removeItem( self.hst[j,i] ) self.hst[j,i] = None # If requested, reset this plot's label text to # the empty string. if ( rset_lbl ) : self.lbl[j,i].setText( '', color=(0,0,0) ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESETTING THE PLOTS' SELECTION POINTS. #----------------------------------------------------------------------- def rset_pnt( self ) : # For each plot that exists in the grid, hide and remove its # selection points. for j in range( self.n_plt_y ) : for i in range( self.n_plt_x ) : # If the plot does not exist, move onto the the # next grid element. if ( self.plt[j,i] is None ) : continue # Remove and then delete each of this plot's # selection points. for b in range( self.n_k ) : if ( self.pnt[j,i,b] is not None ) : self.plt[j,i].removeItem( self.pnt[j,i,b] ) self.pnt[j,i,b] = None #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESETTING THE PLOTS' FIT CURVES. #----------------------------------------------------------------------- def rset_crv( self ) : # For each plot that exists in the grid, remove and delete its # fit curves. for j in range( self.n_plt_y ) : for i in range( self.n_plt_x ) : # If the plot does not exist, move onto the the # next one. if ( self.plt[j,i] is None ) : continue # Remove and delete this plot's fit curve. if ( self.crv[j,i] is not None ) : self.plt[j,i].removeItem( self.crv[j,i] ) self.crv[j,i] = None for n in range( self.n_ion ) : if ( self.crv_ion[j,i,n] is not None ) : self.plt[j,i].removeItem( self.crv_ion[j,i,n] ) self.crv_ion[j,i,n] = None #----------------------------------------------------------------------- # DEFINE THE FUNCTION CALCULATING THE INDEX "i" FROM THE INDEX "d". #----------------------------------------------------------------------- def calc_ind_i( self, d ) : # Return the index "i" (i.e., column in the grid of plots) # corresponding to the index "d" (i.e., look direction value) # passed by the user. return d % self.n_plt_x #----------------------------------------------------------------------- # DEFINE THE FUNCTION CALCULATING THE INDEX "j" FROM THE INDEX "d". #----------------------------------------------------------------------- def calc_ind_j( self, d ) : # Return the index "j" (i.e., row in the grid of plots) # corresponding to the index "d" (i.e., look direction value) # passed by the user. return int( floor( d / ( 1. * self.n_plt_x ) ) ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION CALCULATING INDEX "d" FROM INDICES "j" AND "i". #----------------------------------------------------------------------- def calc_ind_d( self, j, i ) : # Return the index "d" (i.e., look direction value) # corresponding to the indices "j" and "i" (i.e., location in # the grid of plots) passed by the user. return i + ( j * self.n_plt_x ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO A USER-INITIATED EVENT. #----------------------------------------------------------------------- def user_event( self, event, plt_ji ) : # If a "thread_*" computation thread is already running, abort. if ( n_thread( ) != 0 ) : return # If no spectrum has been loaded, abort. if ( ( self.core.fc_spec is None ) or ( self.core.fc_spec['n_bin'] <= 0 ) ) : return # Extract the location of the plot in the grid. tk = where( self.plt == plt_ji ) j = tk[0][0] i = tk[1][0] # Determine the look direction corresponding to this plot. d = self.calc_ind_d( j, i ) # Extract the data shown in this plot. Convert them first to # their adjusted values, and then to their equivalent pixel # positions in the "ViewBox". dat_x = self.core.fc_spec['vel_cen'][self.c] dat_y = self.core.fc_spec['curr'][self.c][d] dat_ax = log10( dat_x ) if ( self.log_x ) else dat_x dat_ay = log10( dat_y ) if ( self.log_y ) else dat_y dat_a = tile( None, self.core.fc_spec['n_bin'] ) for b in range( self.core.fc_spec['n_bin'] ) : dat_a[b] = QPointF( dat_ax[b], dat_ay[b] ) dat_p = array( [ self.plt[j,i].mapFromView( da ) for da in dat_a ] ) dat_px = array( [ dp.x( ) for dp in dat_p ] ) dat_py = array( [ dp.y( ) for dp in dat_p ] ) # Extract the pixel position of the mouse-click event. evt_p = event.pos( ) evt_px = evt_p.x( ) evt_py = evt_p.y( ) # Compute the distance (in pixels) of each datum's location from # the location of the mouse-click event. Then, identify the # closest datum. dst = sqrt( ( dat_px - evt_px )**2 + ( dat_py - evt_py )**2 ) b = where( dst == amin( dst ) )[0][0] # If the distance between the nearest datum and the mouse click # is within a set tolerance, invert the selection of that datum # (i.e., de-select it if its already selected or select it if it # isn't selected). tol = 25. if ( dst[b] <= tol ) : if ( self.core.dsp == 'mom' ) : Thread( target=thread_chng_mom_sel, args=( self.core, self.c, d, b ) ).start() elif ( self.core.dsp == 'gsl' ) : Thread( target=thread_chng_nln_sel, args=( self.core, self.c, d, b ) ).start() elif ( self.core.dsp == 'nln' ) : Thread( target=thread_chng_nln_sel, args=( self.core, self.c, d, b ) ).start() #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "rset" SIGNAL. #----------------------------------------------------------------------- def resp_rset( self ) : # Clear the plots of all their elements. self.rset_crv( ) self.rset_pnt( ) self.rset_hst( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_spc" SIGNAL. #----------------------------------------------------------------------- def resp_chng_spc( self ) : # Clear the plots of all their elements and regenerate them. self.rset_crv( ) self.rset_pnt( ) self.rset_hst( ) self.make_hst( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mom_sel_bin" SIGNAL. #----------------------------------------------------------------------- def resp_chng_mom_sel_bin( self, c=None, d=None, b=None ) : # If one of the keyword arguments is missing, invalid, or # non-applicable to this widget, abort. if ( ( c is None ) or ( d is None ) or ( b is None ) ) : return try : c = int( c ) d = int( d ) b = int( b ) except : return if ( c != self.c ) : return if ( ( d < 0 ) or ( d >= self.core.fc_spec['n_dir'] ) ) : return if ( ( b < 0 ) or ( b >= self.core.fc_spec['n_bin'] ) ) : return # If the results of the moments analysis are being displayed, # update the color and visibility of the corresponding plot # points based on a possible change in the selection status of # that datum or its pointing window. if ( self.core.dsp == 'mom' ) : # Determine the location of this plot within the grid # layout. j = self.calc_ind_j( d ) i = self.calc_ind_i( d ) # Update the color and visibility of the plot point # corresponding to the specified datum. self.chng_pnt( j, i, b, self.core.mom_sel_bin[c][d][b], sel_dir=self.core.mom_sel_dir[c][ d] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mom_sel_dir" SIGNAL. #----------------------------------------------------------------------- def resp_chng_mom_sel_dir( self, c=None, d=None ) : # If one of the keyword arguments is missing, invalid, or # non-applicable to this widget, abort. if ( ( c is None ) or ( d is None ) ) : return try : c = int( c ) d = int( d ) except : return if ( c != self.c ) : return if ( ( d < 0 ) or ( d >= self.core.fc_spec['n_dir'] ) ) : return # If the results of the moments analysis are being displayed and # the "t" value passed to this function corresponds to that for # this widget, update the color and visibility of the # corresponding plot points based on a possible change in the # selection status of that pointing window. if ( self.core.dsp == 'mom' ) : # Determine the location of this plot within the grid # layout. j = self.calc_ind_j( d ) i = self.calc_ind_i( d ) # Update the color and visibility of the plot points # corresponding to each of this look direction's data. for b in range( self.core.fc_spec['n_bin'] ) : self.chng_pnt( j, i, b, self.core.mom_sel_bin[c][d][b], sel_dir=self.core.mom_sel_dir[c][d] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mom_sel_all" SIGNAL. #----------------------------------------------------------------------- def resp_chng_mom_sel_all( self ) : # If the results of the moments analysis are being displayed, # reset any existing selection points and create new ones. if ( self.core.dsp == 'mom' ) : self.make_pnt( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mom_res" SIGNAL. #----------------------------------------------------------------------- def resp_chng_mom_res( self ) : # If the results of the moments analysis are being displayed, # reset any existing fit curves and make new ones. if ( self.core.dsp == 'mom' ) : self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_nln_gss" SIGNAL. #----------------------------------------------------------------------- def resp_chng_nln_gss( self ) : # If the initial guess for the non-linear analysis is being # displayed, reset any existing fit curves and make new ones. if ( self.core.dsp == 'gsl' ) : self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_nln_sel_bin" SIGNAL. #----------------------------------------------------------------------- def resp_chng_nln_sel_bin( self, c=None, d=None, b=None ) : # If one of the keyword arguments is missing, invalid, or # non-applicable to this widget, abort. if ( ( c is None ) or ( d is None ) or ( b is None ) ) : return try : c = int( c ) d = int( d ) b = int( b ) except : return if ( c != self.c ) : return if ( ( d < 0 ) or ( d >= self.core.fc_spec['n_dir'] ) ) : return if ( ( b < 0 ) or ( b >= self.core.fc_spec['n_bin'] ) ) : return # Determine the location of this plot within the grid layout. j = self.calc_ind_j( d ) i = self.calc_ind_i( d ) # If the point selection for the non-linear analysis is being # displayed, update the color and visibility of the plot point # corresponding to the datum "[c,d,b]" based on a possible # change in the selection status of that datum. if ( self.core.dsp == 'gsl' ) : self.chng_pnt( j, i, b, self.core.nln_sel[self.c][d][b] ) self.make_crv( ) elif ( self.core.dsp == 'nln' ) : if ( self.core.nln_res_sel is None ) : self.chng_pnt( j, i, b, False, sel_alt=self.core.nln_sel[self.c][d][b] ) else : self.chng_pnt( j, i, b, self.core.nln_res_sel[self.c][d][b], sel_alt=self.core.nln_sel[self.c][d][b] ) self.make_crv( d_lst=[d] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_nln_sel_all" SIGNAL. #----------------------------------------------------------------------- def resp_chng_nln_sel_all( self ) : # If the point selection for the non-linear analysis is being # displayed, reset any existing selection points and create new # ones. if ( ( self.core.dsp == 'gsl' ) or ( self.core.dsp == 'nln' ) ) : self.make_pnt( ) self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_nln_res" SIGNAL. #----------------------------------------------------------------------- def resp_chng_nln_res( self ) : # If the results of the non-linear analysis are being displayed, # reset any existing fit curves and make new ones. if ( self.core.dsp == 'nln' ) : self.make_pnt( ) self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_dsp" SIGNAL. #----------------------------------------------------------------------- def resp_chng_dsp( self ) : # Reset the selection points and fit curves. self.make_pnt( ) self.make_crv( )
class Graphexample(): def __init__(self): self.graph = GraphicsLayoutWidget() self.graph.setObjectName("stock UI") self.label = pg.LabelItem(justify='right') self.graph.addItem(self.label) self.p1 = self.graph.addPlot(row=1, col=0) self.p2 = self.graph.addPlot(row=2, col=0) self.p1.showGrid(x=True, y=True, alpha=0.5) self.region = pg.LinearRegionItem() self.region.setZValue(10) # Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this # item when doing auto-range calculations. self.p2.addItem(self.region, ignoreBounds=True) #pg.dbg() self.p1.setAutoVisible(y=True) #create numpy arrays #make the numbers large to show that the xrange shows data from 10000 to all the way 0 self.data1 = 10000 + 15000 * pg.gaussianFilter( np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000) self.data2 = 15000 + 15000 * pg.gaussianFilter( np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000) self.p1.plot(self.data1, pen="r") self.p1.plot(self.data2, pen="g") self.p2.plot(self.data1, pen="w") self.region.sigRegionChanged.connect(self.update) self.p1.sigRangeChanged.connect(self.updateRegion) self.region.setRegion([1000, 2000]) #cross hair self.vLine = pg.InfiniteLine(angle=90, movable=False) self.hLine = pg.InfiniteLine(angle=0, movable=False) self.p1.addItem(self.vLine, ignoreBounds=True) self.p1.addItem(self.hLine, ignoreBounds=True) self.vb = self.p1.vb self.proxy = pg.SignalProxy(self.p1.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) def update(self): self.region.setZValue(10) minX, maxX = self.region.getRegion() self.p1.setXRange(minX, maxX, padding=0) def updateRegion(self, window, viewRange): rgn = viewRange[0] self.region.setRegion(rgn) def mouseMoved(self, evt): pos = evt[ 0] ## using signal proxy turns original arguments into a tuple if self.p1.sceneBoundingRect().contains(pos): mousePoint = self.vb.mapSceneToView(pos) index = int(mousePoint.x()) if index > 0 and index < len(self.data1): self.label.setText( "<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y1=%0.1f</span>, <span style='color: green'>y2=%0.1f</span>" % (mousePoint.x(), self.data1[index], self.data2[index])) self.vLine.setPos(mousePoint.x()) self.hLine.setPos(mousePoint.y()) def ret_GraphicsLayoutWidget(self): return self.graph
self.ui_callback(self.current_state) if __name__ == "__main__": app = QtGui.QApplication(sys.argv) main_window = QtGui.QMainWindow() sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) video_view = VideoView() test_img = cv2.imread("Etaluma.png") video_view.setImage(test_img) video_view.stateUpdate(State.full_view) graphicsLayoutWidget = GraphicsLayoutWidget() main_window.setCentralWidget(graphicsLayoutWidget) graphicsLayoutWidget.addItem(video_view, 0, 0) graphicsLayoutWidget.setSizePolicy(sizePolicy) main_window.show() c = console.ConsoleWidget(namespace={ 's': video_view, 'g': graphicsLayoutWidget, 'n': main_window, 'pg': pg }) c.show() video_view.show() app.exec_()
class widget_pl_grid( QWidget ) : #----------------------------------------------------------------------- # DEFINE THE INITIALIZATION FUNCTION. #----------------------------------------------------------------------- def __init__( self, core, n, n_plt_x=None, n_plt_y=None, n_plt=None ) : # Inherit all attributes of an instance of "QWidget". super( widget_pl_grid, self ).__init__( ) # Initialize the counter of repaint events for this widget as # well as a maximum value for this counter. # Note. For some reason, adjusting the individual plots to have # uniform sizes is difficult to achieve before the widget # is rendered. Thus, once a paint event occurs, the # "self.paintEvent( )" function picks it up and makes a # call to "self.ajst_grd( )". This counter and its # maximum value are used ensure that "self.paintEvent( )" # makes such a call only in response to the intial few # painting (so as to prevent an infinite loop). # Note. The first paint seems to be a "dummy" of some sort. # Whatever the case, "self.n_paint_max = 1" seems to # generally be insufficient. self.n_painted = 0 self.n_painted_max = 3 # Disable user events self.setDisabled( True ) self.core = core self.n = n self.t = [] self.delta_t = [] self.time_label = QLabel( ' ' ) self.time_label.setAlignment( Qt.AlignCenter ) self.time_label.setContentsMargins( 0,5,0,0 ) font = QFont( ) font.setBold(True) self.time_label.setFont( font ) # Prepare to respond to signals received from the Janus core. self.connect( self.core, SIGNAL('janus_rset'), self.resp_rset ) self.connect( self.core, SIGNAL('janus_chng_pl_spc'), self.resp_chng_pl_spc ) self.connect( self.core, SIGNAL('janus_chng_mom_pl_sel'), self.resp_chng_mom_pl_sel ) self.connect( self.core, SIGNAL('janus_chng_mom_pl_res'), self.resp_chng_mom_pl_res ) self.connect( self.core, SIGNAL('janus_chng_nln_gss'), self.resp_chng_nln_gss ) self.connect( self.core, SIGNAL('janus_chng_nln_sel_all'), self.resp_chng_nln_sel_all ) self.connect( self.core, SIGNAL('janus_chng_nln_res'), self.resp_chng_nln_res ) self.connect( self.core, SIGNAL('janus_chng_dsp'), self.resp_chng_dsp ) #TODO add more signals # Assign (if not done so already) and store the shape of the # plot-grid array. self.n_plt_x = 5 if ( n_plt_x is None ) else n_plt_x self.n_plt_y = 5 if ( n_plt_y is None ) else n_plt_y if ( n_plt is None ) : self.n_plt = self.n_plt_x * self.n_plt_y # Initizalize the pens, brushes, and fonts used by this widget. self.pen_plt = mkPen( color='k' ) self.pen_hst = mkPen( color='k' ) self.pen_pnt_c = mkPen( color='k' ) self.pen_pnt_y = mkPen( color='k' ) self.pen_pnt_r = mkPen( color='k' ) self.pen_crv_b = mkPen( color='b' ) self.pen_crv_g = mkPen( color='g' ) self.bsh_pnt_c = mkBrush( color='c' ) self.bsh_pnt_y = mkBrush( color='y' ) self.bsh_pnt_r = mkBrush( color='r' ) self.fnt = self.core.app.font( ) # Set the maximum number of velocity channels and the maximum # number of ion species. self.n_k = 14 self.n_ion = self.core.nln_n_pop # Initialize the widget and it's plot's. self.init_plt( ) # Populate the plots with the histograms (and labels), the # selection points, and the fit curves. self.make_hst( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR INITIALIZING THE WIDGET AND ITS PLOTS. #----------------------------------------------------------------------- def init_plt( self ) : # Initialize the "GraphicsLayoutWidget" for this widget. This # will allow a grid of "GraphicsItem" objects, which will # include the plots themselves, the axes, and the axis labels. # Note. The "QGridLayout" object given to this widget as its # layout is essentially a dummy. I tried to just having # this widget be an extention of "GraphicsLayoutWidget" # (i.e., having it inheret that type), but I couldn't get # it to display anything at all. self.setLayout( QGridLayout( ) ) self.grd = GraphicsLayoutWidget( ) self.grd.setBackground( 'w' ) self.layout( ).addWidget( self.time_label ) self.layout( ).addWidget( self.grd ) self.layout().setContentsMargins( 0, 0, 0, 0 ) # Initialize the text for the x- and y-axis labels. Then, # create the labels themselves and add them to the grid. self.txt_axs_x = 'Projected Proton Inflow Velocity [km/s]' self.txt_axs_y = 'Phase-space Density' +\ u'[cm\u00AF\u00B3/(km/s)\u00B3]' if ( self.core.app.res_lo ) : size = '8pt' else : size = '10pt' self.lab_axs_x = LabelItem( self.txt_axs_x, angle=0 , color='b', size=size ) self.lab_axs_y = LabelItem( self.txt_axs_y, angle=270, color='b', size=size ) self.grd.addItem( self.lab_axs_x, self.n_plt_y + 1, 2, 1, self.n_plt_x ) self.grd.addItem( self.lab_axs_y, 0, 0, self.n_plt_y, 1 ) # Initialize the arrays that will contain the individual axes, # plots, and plot elements (i.e., the histograms, fit curves, # labels, and selection points). self.plt = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.axs_x = tile( None, self.n_plt_x ) self.axs_y = tile( None, self.n_plt_y ) self.hst = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.lbl = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.crv = tile( None, [ self.n_plt_y, self.n_plt_x ] ) self.crv_ion = tile( None, [ self.n_plt_y, self.n_plt_x, self.n_ion ] ) self.pnt = tile( None, [ self.n_plt_y, self.n_plt_x, self.n_k ] ) # Initialize the scale-type for each axis, then generate the # (default) axis-limits and adjusted axis-limits. self.log_x = False self.log_y = True self.make_lim( ) # Create, store, and add to the grid the individual axes: first # the horizontal and then the vertical. for i in range( self.n_plt_x ) : self.axs_x[i] = AxisItem( 'bottom', maxTickLength=5 ) self.axs_x[i].setLogMode( self.log_x ) self.axs_x[i].setRange( self.x_lim[0], self.x_lim[1] ) self.axs_x[i].setTickFont( self.fnt ) if ( self.core.app.res_lo ) : self.axs_x[i].setHeight( 10 ) else : self.axs_x[i].setHeight( 20 ) self.grd.addItem( self.axs_x[i], self.n_plt_y, i + 2 ) for j in range( self.n_plt_y ) : self.axs_y[j] = AxisItem( 'left', maxTickLength=5 ) self.axs_y[j].setLogMode( self.log_y ) self.axs_y[j].setRange( self.y_lim[0], self.y_lim[1] ) self.axs_y[j].setTickFont( self.fnt ) if ( self.core.app.res_lo ) : self.axs_y[j].setWidth( 32 ) else : self.axs_y[j].setWidth( 40 ) self.grd.addItem( self.axs_y[j], j, 1 ) # Create, store, and add to the grid the individual plots. # Likewise, create, store, and add to each plot a label. for t in range( self.n_plt_y ) : for p in range( self.n_plt_x ) : # Compute the plot number of this plot. d = p + ( t * self.n_plt_x ) # If creating this plot would exceed the # specified number of plots, don't create it. if ( d >= self.n_plt ) : continue # Create and store this plot, adjust its limits, # and add it to the grid. # Note: locations of plots are inverted along # theta self.plt[t,p] = event_ViewBox( self, border=self.pen_plt, enableMouse=False, enableMenu=False ) self.plt[t,p].setRange( xRange=self.x_lim, yRange=self.y_lim, padding=0. ) self.grd.addItem( self.plt[t,p], self.n_plt_y-t-1, p + 2 ) # Create and store an (empty) label and add it # to this plot. self.lbl[t,p] = TextItem( anchor=(1,0) ) self.lbl[t,p].setFont( self.fnt ) self.plt[t,p].addItem( self.lbl[t,p] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR GENERATING AXIS-LIMITS (AND ADJUSTED LIMITS). #----------------------------------------------------------------------- def make_lim( self ) : # If no spectrum has been loaded, use the default limits; # otherwise, use the spectral data to compute axis limits. # Note: velocities are recorded in reverse order. if ( self.core.pl_spec_arr == [] ) : self.domain = [ 300. , 900. ] self.range = [ 1.e-10, 1.e-5 ] else : self.domain = [self.core.pl_spec_arr[self.n]['vel_strt'][0], self.core.pl_spec_arr[self.n]['vel_stop'][-1]] arr_psd_flat = [self.core.pl_spec_arr[self.n]['psd_flat'][i] for i in (where(array(self.core.pl_spec_arr[self.n]['psd_flat']) != 0.)[0])] self.range = [ self.core.mom_psd_min, self.core.mom_psd_max ] # Note: psd values are less than 1 if ( self.log_y ) : self.range[0] = self.range[0] ** 1.05 self.range[1] = self.range[1] ** 0.9 else : self.range[1] += 0.1 * ( self.range[1] - self.range[0] ) # Compute the "adjusted limits" for each axis. if ( self.log_x ) : self.x_lim = [ log10( x ) for x in self.domain ] else : self.x_lim = self.domain if ( self.log_y ) : self.y_lim = [ log10( y ) for y in self.range ] else : self.y_lim = self.range #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CREATING THE PLOTS' HISTOGRAMS (AND LABELS). #----------------------------------------------------------------------- def make_hst( self ) : # Reset the timestamp label self.time_label.setText( ' ' ) # If no spectrum has been loaded, abort. if ( self.core.pl_spec_arr == [] ) : return # If the index of this P-L grid is outside the bounds of the P-L # spectrum currently loaded, abort. if ( self.n >= len( self.core.pl_spec_arr ) ) : return # Generate the timestamp label self.t = self.t + [self.core.pl_spec_arr[self.n]['time'][0]] self.t_0 = self.core.fc_spec['time'] self.delta_t = self.delta_t + [( self.t[-1]- self.t_0 ).total_seconds( )] self.time_label.setText( str(self.t[-1])[0:-7] + ' ' + u'\u0394t = {}'.format( round( self.delta_t[-1], 0) ) + 's' ) # Use the spectral data to compute new axis-limits. self.make_lim( ) for p in range( self.n_plt_x ) : self.axs_x[p].setRange( self.x_lim[0], self.x_lim[1] ) for t in range( self.n_plt_y ) : self.axs_y[t].setRange( self.y_lim[0], self.y_lim[1] ) # Histograms are broken down by phi horizontally and # theta vertically for t in range( self.core.pl_spec_arr[self.n]['n_the'] ): for p in range ( self.core.pl_spec_arr[self.n]['n_phi'] ): # If this plot does not exist, move onto # the next one. if ( self.plt[t,p] is None ) : continue #-----------------------------# #---DATA GENERATION SECTION---# #-----------------------------# # Generate a step function for the # look direction associated with this widget. self.stp = array( [step( self.core.pl_spec_arr[self.n]['vel_cen'], self.core.pl_spec_arr[self.n]['vel_del'], self.core.pl_spec_arr[self.n]['psd'][t][p])]) # Calculate the points to be plotted from the # step function stp_pnt = array( [ array( datum.calc_pnt( lev_min=self.range[0]/2.) ) for datum in self.stp ] ) self.x_set = stp_pnt[:,0][0] self.y_set = stp_pnt[:,1][0] # If plotting log(y) and there are any psd # values of zero, replace those points with an # arbitrary minimum y value if ( self.log_y ) : y_min = self.range[0]/2. self.y_lim[0] = log10(y_min) self.y_set = [ max( y, y_min ) for y in self.y_set ] # If generating a log plot, take the log of the # points to be plotted self.x_pnts = log10( self.x_set ) if ( self.log_x ) else \ self.x_set self.y_pnts = log10( self.y_set ) if ( self.log_y ) else \ self.y_set #---------------------------------# #---GRAPHICS GENERATION SECTION---# #---------------------------------# # If a histogram already exists for this plot, # remove and delete it. if ( self.hst[t,p] is not None ) : self.plt[t,p].removeItem(self.hst[t,p]) self.hst[t,p] = None # Clear this plot's label of text. self.lbl[t,p].setText( '' ) # Adjust this plot's limits and then move it's # label in response. self.plt[t,p].setRange( xRange=self.x_lim, yRange=self.y_lim, padding=0. ) self.lbl[t,p].setPos( self.x_lim[1], self.y_lim[1] ) # Update this plot's label with appropriate text # indicating the pointing direction. elev = round( self.core.pl_spec_arr[self.n]['elev_cen'][t] ) azim = round( self.core.pl_spec_arr[self.n]['azim_cen'][p] ) txt = ( u'({0:+.0f}\N{DEGREE SIGN}, ' + u'{1:+.0f}\N{DEGREE SIGN})' ).format( elev, azim-180 ) self.lbl[t,p].setText( txt, color=(0,0,0) ) # Generate the histogram for the data from this # look direction and display it in the plot. self.hst[t,p] = PlotDataItem( self.x_pnts, self.y_pnts, pen=self.pen_hst ) self.plt[t,p].addItem( self.hst[t,p] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CREATING THE PLOTS' SELECTION POINTS. #----------------------------------------------------------------------- def make_pnt( self ) : # If no spectrum has been loaded, abort. if ( self.core.pl_spec_arr == [] ) : return # If the index of this P-L grid is outside the bounds of the P-L # spectrum currently loaded, abort. if ( self.n >= len( self.core.pl_spec_arr ) ) : return # Add selection points to each plot. for d in range( min( self.core.pl_spec_arr[self.n]['n_dir'], self.n_plt ) ) : # Determine the location of this plot within the grid # layout. t = d // self.n_plt_x p = d % self.n_plt_y # If this plot does not exist, move onto the next one. if ( self.plt[t,p] is None ) : continue # Add the selection points to this plot. for b in range( self.core.pl_spec_arr[self.n]['n_bin'] ) : sel_bin = False sel_dir = True sel_alt = None if( self.core.dsp == 'mom' ): sel_bin = self.core.pl_spec_arr[self.n].arr[t][p][b]['mom_sel'] elif ( ( self.core.dsp == 'gsl' or self.core.dsp == 'nln' ) and ( self.core.nln_pl_sel is not None ) ) : sel_bin = self.core.nln_pl_sel[self.n][t][p][b] # elif ( ( self.core.dsp == 'nln' ) and # ( self.core.pl_spec_arr[self.n].nln_res_sel # is not None ) ) : # sel_bin = \ # self.core.nln_pl_sel[t][p][b] # if ( self.core.pl_spec_arr[self.n].nln_sel is None ) : # sel_alt = None # else : # sel_alt = \ # self.core.pl_spec_arr[self.n].nln_sel[t][p][b] self.chng_pnt( t, p, b, sel_bin, sel_alt=sel_alt ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CHANGING THE VISIBILITY OF A DATUM'S POINTS. #----------------------------------------------------------------------- def chng_pnt( self, t, p, b, sel_bin, sel_alt=None ) : # If no spectrum has been loaded, abort. if ( self.core.pl_spec_arr == [] ) : return # If the index of this P-L grid is outside the bounds of the P-L # spectrum currently loaded, abort. if ( self.n >= len( self.core.pl_spec_arr ) ) : return # If this point already exists, remove it from its plot and # delete it. if ( self.pnt[t,p,b] is not None ) : self.plt[t,p].removeItem( self.pnt[t,p,b] ) self.pnt[t,p,b] = None # If this point was not selected (based on both its primary and # secondary states), return (since there's nothing more to be # done). if ( not sel_bin or self.core.pl_spec_arr[self.n]['psd'][t][p][b] == 0) : return # Determine the "d" index corresponding to this look direction. d = p + ( t * self.n_plt_x ) # Computed the adjusted point location in the "ViewBox". if ( self.log_x ) : ax = log10( self.core.pl_spec_arr[self.n]['vel_cen'][b] ) else : ax = self.core.pl_spec_arr[self.n]['vel_cen'][b] if ( self.log_y ) : ay = log10( self.core.pl_spec_arr[self.n]['psd'][t][p][b] ) else : ay = self.core.pl_spec_arr[self.n]['psd'][t][p][b] # Select the color for the point (i.e., the brush and pen used # to render it) based on whether or not this datum's look # direction has been selected and whether or not the primary and # secondary selection states match. # if ( sel_bin == sel_alt ) : pen = self.pen_pnt_c brush = self.bsh_pnt_c # else : # pen = self.pen_pnt_y # brush = self.bsh_pnt_y # else : # pen = self.pen_pnt_r # brush = self.bsh_pnt_r # Select the symbol based on the values of the primary and # secondary selection states. # Note. At this point in the code, at least one of these two # states must be "True" since this -- when both states # are "False", this function returns before it reaches # this point. if ( sel_bin ) : symbol = 'o' else : symbol = 't' # Create, store, and add this selection point to the plot. if ( self.core.app.res_lo ) : size = 3 else : size = 6 self.pnt[t,p,b] = PlotDataItem( [ax], [ay], symbol=symbol, symbolSize=size, symbolPen=pen, symbolBrush=brush ) self.plt[t,p].addItem( self.pnt[t,p,b] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR CREATING THE PLOTS' FIT CURVES. #----------------------------------------------------------------------- def make_crv( self ) : # If no "list" of "p" index-values has been provided by the # user, assume that the curves in all plots should be # (re-)rendered. if ( self.core.pl_spec_arr == [] ) : return # If the index of this P-L grid is outside the bounds of the P-L # spectrum currently loaded, abort. if ( self.n >= len( self.core.pl_spec_arr ) ) : return # Return the "nln_psd_gss_ion axes to their original order. if( self.core.nln_psd_gss_ion is not None ) : if( self.core.nln_psd_gss_ion != [ ] ) : nln_psd_gss_ion = [ [ [ [ [ self.core.nln_psd_gss_ion[n][t][f][b][p] for b in range( self.core.pl_spec_arr[n]['n_bin'] ) ] for f in range( self.core.pl_spec_arr[n]['n_phi'] ) ] for t in range( self.core.pl_spec_arr[n]['n_the'] ) ] for n in range( len( self.core.pl_spec_arr ) ) ] for p in range( self.core.nln_gss_n_pop ) ] # Return the "nln_res_psd_ion axes to their original order. if( self.core.nln_res_psd_ion is not None ) : if( self.core.nln_res_psd_ion != [ ] ) : nln_res_psd_ion = [ [ [ [ [ self.core.nln_res_psd_ion[n][t][f][b][p] for b in range( self.core.pl_spec_arr[n]['n_bin'] ) ] for f in range( self.core.pl_spec_arr[n]['n_phi'] ) ] for t in range( self.core.pl_spec_arr[n]['n_the'] ) ] for n in range( len( self.core.pl_spec_arr ) ) ] for p in range( self.core.nln_gss_n_pop ) ] # For each plot in the grid, generate and display a fit curve # based on the results of the analysis. vel_cen = self.core.pl_spec_arr[self.n]['vel_cen'] for t in range( self.core.pl_spec_arr[self.n]['n_the'] ) : for p in range( self.core.pl_spec_arr[self.n]['n_phi'] ) : # If this plot does not exist, move onto the next grid # element. if ( self.plt[t,p] is None ) : continue # If any curves already exist for this plot, remove and # delete them. if ( self.crv[t,p] is not None ) : self.plt[t,p].removeItem( self.crv[t,p] ) self.crv[t,p] = None for n in range( self.n_ion ) : if ( self.crv_ion[t,p,n] is not None ) : self.plt[t,p].removeItem( self.crv_ion[t,p,n] ) self.crv_ion[t,p,n] = None # Create and add the curve of the indiviadual # contributions to the modeled psd to the plot. for n in range( ( 1 if self.core.dsp == 'mom' else self.core.nln_gss_n_pop ) ) : # Extract the points for this fit curve. x = array( vel_cen ) if( self.core.dsp == 'mom' ) : y = array( self.core.pl_spec_arr[self.n]['psd_mom'][t][p] ) elif( self.core.dsp == 'gsl' ) : y = array( nln_psd_gss_ion[n][self.n][t][p] ) elif( self.core.dsp == 'nln' ) : y = array( nln_res_psd_ion[n][self.n][t][p] ) # If any points are 0 or None, set them # to an arbitrary minimum value for tk in range(len(y)): if ( ( y[tk] == 0 ) or ( y[tk] is None ) ) : y[tk] = 1e-20 if ( self.log_x ) : ax = log10( x ) else : ax = x if ( self.log_y ) : ay = array( [ log10( v ) for v in y ] ) else : ay = y # Create, store, and add to the plot # this fit curve. self.crv_ion[t,p,n] = PlotDataItem( ax, ay, pen=( self.pen_crv_b if self.core.dsp=='mom' else self.pen_crv_g ) ) self.plt[t,p].addItem( self.crv_ion[t,p,n] ) # If applicable, create and add the curve of the # total contributions to the modeled psd to the # plot if( self.core.dsp == 'gsl' ) : x = array( vel_cen ) y = array( self.core.nln_psd_gss_tot[self.n][t][p] ) # If any points are 0 or None, set them # to an arbitrary minimum value for tk in range(len(y)): if ( ( y[tk] == 0 ) or ( y[tk] is None ) ) : y[tk] = 1e-20 if ( self.log_x ) : ax = log10( x ) else : ax = x if ( self.log_y ) : ay = array( [ log10( v ) for v in y ] ) else : ay = y # Create, store, and add to the plot # this fit curve. self.crv[t,p] = PlotDataItem( ax, ay, pen=( self.pen_crv_b ) ) self.plt[t,p].addItem( self.crv[t,p] ) # If applicable, create and add the curve of the # total contributions to the non-linear psd to # the plot if( ( self.core.dsp == 'nln' ) and ( self.core.nln_res_psd_tot is not None ) ) : x = array( vel_cen ) y = array( self.core.nln_res_psd_tot[self.n][t][p] ) # If any points are 0 or None, set them # to an arbitrary minimum value for tk in range(len(y)): if ( ( y[tk] == 0 ) or ( y[tk] is None ) ) : y[tk] = 1e-20 if ( self.log_x ) : ax = log10( x ) else : ax = x if ( self.log_y ) : ay = array( [ log10( v ) for v in y ] ) else : ay = y # Create, store, and add to the plot # this fit curve. self.crv[t,p] = PlotDataItem( ax, ay, pen=( self.pen_crv_b ) ) self.plt[t,p].addItem( self.crv[t,p] ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESETTING THE PLOTS' HISTOGRAMS (AND LABELS). #----------------------------------------------------------------------- def rset_hst( self, rset_lbl=False ) : self.time_label.setText( '' ) self.t = [] self.delta_t = [] # For each plot that exists in the grid, remove and delete it's # histogram. Likewise, if requested, empty it's label (but # still leave the label itself intact). for t in range( self.n_plt_y ) : for p in range( self.n_plt_x ) : # If the plot does not exist, move onto the the # next one. if ( self.plt[t,p] is None ) : continue # If a histogram exists for this plot, remove # and delete it. if ( self.hst[t,p] is not None ) : self.plt[t,p].removeItem( self.hst[t,p] ) self.hst[t,p] = None # If requested, reset this plot's label text to # the empty string. if ( rset_lbl ) : self.lbl[t,p].setText( '', color=(0,0,0) ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESETTING THE PLOTS' SELECTION POINTS. #----------------------------------------------------------------------- def rset_pnt( self ) : # For each plot that exists in the grid, hide and remove its # selection points. for t in range( self.n_plt_y ) : for p in range( self.n_plt_x ) : # If the plot does not exist, move onto the the # next grid element. if ( self.plt[t,p] is None ) : continue # Remove and then delete each of this plot's # selection points. for b in range( self.n_k ) : if ( self.pnt[t,p,b] is not None ) : self.plt[t,p].removeItem( self.pnt[t,p,b] ) self.pnt[t,p,b] = None #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESETTING THE PLOTS' FIT CURVES. #----------------------------------------------------------------------- def rset_crv( self ) : # For each plot that exists in the grid, remove and delete its # fit curves. for t in range( self.n_plt_y ) : for p in range( self.n_plt_x ) : # If the plot does not exist, move onto the the # next one. if ( self.plt[t,p] is None ) : continue # Remove and delete this plot's fit curve. if ( self.crv[t,p] is not None ) : self.plt[t,p].removeItem( self.crv[t,p] ) self.crv[t,p] = None for n in range( self.n_ion ) : if ( self.crv_ion[t,p,n] is not None ) : self.plt[t,p].removeItem( self.crv_ion[t,p,n] ) self.crv_ion[t,p,n] = None #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "rset" SIGNAL. #----------------------------------------------------------------------- def resp_rset( self ) : # Clear the plots of all their elements. self.rset_hst( ) self.rset_pnt( ) self.rset_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_spc" SIGNAL. #----------------------------------------------------------------------- def resp_chng_pl_spc( self ) : # Clear the plots of all their elements and regenerate them. self.rset_crv( ) self.rset_pnt( ) self.rset_hst( ) self.make_hst( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mom_pl_res" SIGNAL. #----------------------------------------------------------------------- def resp_chng_mom_pl_sel( self ) : # If the results of the moments analysis are being displayed, # reset any existing fit curves and make new ones. self.make_pnt( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_mom_pl_res" SIGNAL. #----------------------------------------------------------------------- def resp_chng_mom_pl_res( self ) : # If the results of the moments analysis are being displayed, # reset any existing fit curves and make new ones. self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_nln_gss" SIGNAL. #----------------------------------------------------------------------- def resp_chng_nln_gss( self ) : # If the initial guess for the non-linear analysis is being # displayed, reset any existing fit curves and make new ones. if ( self.core.dsp == 'gsl' ) : self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_nln_sel_all" SIGNAL. #----------------------------------------------------------------------- def resp_chng_nln_sel_all( self ) : # If the point selection for the non-linear analysis is being # displayed, reset any existing selection points and create new # ones. if ( ( self.core.dsp == 'gsl' ) or ( self.core.dsp == 'nln' ) ) : self.make_pnt( ) # self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_dsp" SIGNAL. #----------------------------------------------------------------------- def resp_chng_dsp( self ) : # Reset the selection points and fit curves. self.make_pnt( ) self.make_crv( ) #----------------------------------------------------------------------- # DEFINE THE FUNCTION FOR RESPONDING TO THE "chng_dsp" SIGNAL. #----------------------------------------------------------------------- def resp_chng_nln_res( self ) : # Reset the fit curves. self.make_crv( ) '''
class StockGraph(): def __init__(self, numofplot=2, name=None, stockInfo=None): self.gridContainer = None self.dir = None self.construct_gridlayout() if numofplot < 0: print("numofplot must be greater or equal than 0") return -1 if name != None and len(name) > numofplot: print("Pls Checking numofplot") return -1 #X-axis data self.dataX = None self.dataY = None self.name = name self.stockInfo = stockInfo self.graph = GraphicsLayoutWidget() self.graph.setObjectName("stock UI") self.label = pg.LabelItem(justify='right') self.graph.addItem(self.label) self.date_axis = TimeAxisItem(orientation='bottom') self.date_axis1 = TimeAxisItem(orientation='bottom') self.p1 = self.graph.addPlot(row=1, col=0, axisItems={'bottom': self.date_axis1}) #p1 zoom in/out, move viewbox can auto scaling Y, Important! self.p1.vb.sigXRangeChanged.connect(self.setYRange) self.p2 = self.graph.addPlot(row=2, col=0, axisItems={'bottom': self.date_axis}) #p2 zoom in/out, move viewbox can auto scaling Y. Important! self.p2.vb.sigXRangeChanged.connect(self.setYRange) # self.p1.showGrid(x=True, y=True, alpha=0.5) self.region = pg.LinearRegionItem() self.region.setZValue(10) # Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this # item when doing auto-range calculations. self.p2.addItem(self.region, ignoreBounds=True) self.p1.setAutoVisible(y=True) #cross hair self.vLine = pg.InfiniteLine(angle=90, movable=False) self.hLine = pg.InfiniteLine(angle=0, movable=False) self.p1.addItem(self.vLine, ignoreBounds=True) self.p1.addItem(self.hLine, ignoreBounds=True) self.vb = self.p1.vb self.proxy = pg.SignalProxy(self.p1.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) #Set PlotDataItem self.numofplot = numofplot self.name = name self.colorStyle = ['r', 'g', 'b', 'y', 'w', 'r'] self.PlotDataItemList = self.setPlot() self.p2_plot = self.p2.plot(name='overview') #set gridContainer layout self.gridContainer.layout.addWidget(self.graph, 0, 0, 10, 1) self.gridContainer.setLayout(self.gridContainer.layout) self.calName = ['10avg', '30avg', '90avg'] self.CalPlotDataItemList = self.setCalPlot() #consttuct graph control Panel self.construct_controlPanel() def construct_gridlayout(self): self.gridContainer = QtWidgets.QWidget() sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHeightForWidth( self.gridContainer.sizePolicy().hasHeightForWidth()) self.gridContainer.setSizePolicy(sizePolicy) self.gridContainer.setFocusPolicy(QtCore.Qt.NoFocus) self.gridContainer.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.gridContainer.setObjectName("Graphgrid") self.gridContainer.layout = QtWidgets.QGridLayout() self.gridContainer.setStyleSheet("background-color:black;") def construct_controlPanel(self): self.maxRadioButton = QtWidgets.QCheckBox("Max") self.maxRadioButton.setObjectName("maxRadioButton") self.gridContainer.layout.addWidget(self.maxRadioButton, 0, 1, 1, 1) self.maxRadioButton.click() self.maxRadioButton.setStyleSheet("color: rgb(0, 255, 0);") self.maxRadioButton.toggled.connect( lambda: self.toggleFunc(self.maxRadioButton, 'high')) self.minRadioButton = QtWidgets.QCheckBox("Min") self.minRadioButton.setObjectName("minRadioButton") self.gridContainer.layout.addWidget(self.minRadioButton, 1, 1, 1, 1) self.minRadioButton.click() self.minRadioButton.setStyleSheet("color: rgb(255, 255, 255);") self.minRadioButton.toggled.connect( lambda: self.toggleFunc(self.minRadioButton, 'low')) self.closeRadioButton = QtWidgets.QCheckBox("Close") self.closeRadioButton.setObjectName("closeRadioButton") self.gridContainer.layout.addWidget(self.closeRadioButton, 2, 1, 1, 1) self.closeRadioButton.click() self.closeRadioButton.setStyleSheet("color: rgb(255,0,0);") self.closeRadioButton.toggled.connect( lambda: self.toggleFunc(self.closeRadioButton, 'close')) self.avg10RadioButton = QtWidgets.QCheckBox("10 AVG") self.avg10RadioButton.setObjectName("avg10RadioButton") self.gridContainer.layout.addWidget(self.avg10RadioButton, 3, 1, 1, 1) self.avg10RadioButton.click() self.avg10RadioButton.setStyleSheet("color: rgb(255,255,0);") self.avg10RadioButton.toggled.connect( lambda: self.toggleFunc(self.avg10RadioButton, '10avg')) self.GetButton = QtWidgets.QPushButton("Advance") self.GetButton.setObjectName("Advance") self.GetButton.clicked.connect(self.Analysis) self.GetButton.setStyleSheet("color: rgb(255, 255, 255);") self.gridContainer.layout.addWidget(self.GetButton, 4, 1, 1, 1) def update_p1_tick(self, minX, maxX, interval=60): if self.dataX != None: #0 - minX lowNum = int(minX / interval) #maxX - length HighNum = int((len(self.dataX) - 1 - maxX) / interval) plotPoint = [(minX - interval * i) for i in range(1, lowNum + 1)] + [minX] if (maxX - minX) > interval: plotPoint.append(int((maxX + minX) / 2)) plotPoint = plotPoint + [maxX] + [(maxX + interval * i) for i in range(1, HighNum + 1)] p1_tick = ['' for i in range(0, len(self.dataX))] p1_tick = dict(enumerate(p1_tick)) for index in plotPoint: p1_tick[index] = self.dataX[index] self.date_axis1.setTicks([list(p1_tick.items())[::1]]) def update(self): self.region.setZValue(10) minX, maxX = self.region.getRegion() self.p1.setXRange(minX, maxX, padding=0) if (maxX < len(self.dataX) - 1): self.update_p1_tick(int(minX) + 1, int(maxX)) def updateRegion(self, window, viewRange): rgn = viewRange[0] self.region.setRegion(rgn) def mouseMoved(self, evt): pos = evt[ 0] ## using signal proxy turns original arguments into a tuple if self.p1.sceneBoundingRect().contains(pos): mousePoint = self.vb.mapSceneToView(pos) index = int(mousePoint.x()) if index > 0 and index < len(self.dataX): idx = int(mousePoint.x()) self.label.setText( "<span style='font-size: 12pt'>x=%s, <span style='color: red'>收盤價=%0.1f</span>, <span style='color: green'>最高價=%0.1f</span> <span style='color: white'>最低價=%0.1f</span>" % (self.dataX[idx], self.dataY['close'][idx], self.dataY['high'][idx], self.dataY['low'][idx])) self.vLine.setPos(mousePoint.x()) self.hLine.setPos(mousePoint.y()) def setYRange(self, plotitem): plotitem.enableAutoRange(axis='y') plotitem.setAutoVisible(y=True) def setPlot(self): PlotDataItemList = [] for i in range(0, self.numofplot): PlotDataItemList.append( self.p1.plot(pen=self.colorStyle[i], name=self.name[i])) return PlotDataItemList def setCalPlot(self): CalPlotDataItemList = [] for i in range(0, 3): CalPlotDataItemList.append( self.p1.plot(pen=self.colorStyle[i + 3], name=self.calName[i])) return CalPlotDataItemList def setData(self, dataX, dataYDict): #create numpy arrays #make the numbers large to show that the xrange shows data from 10000 to all the way 0 self.dataX = dataX self.dataY = dataYDict ticks = dict(enumerate(dataX)) '''set X-value of P2 and P1''' self.date_axis.setTicks( [list(ticks.items())[::160], list(ticks.items())[::1]]) self.date_axis1.setTicks( [list(ticks.items())[::160], list(ticks.items())[::1]]) '''set Y-value and Plot''' for dataY, PlotDataItem in zip(dataYDict.values(), self.PlotDataItemList): PlotDataItem.setData(y=dataY) #do not show label #self.p1.getAxis('bottom').showLabel(False) # self.date_axis.linkToView(self.p1.vb) '''set overview data''' self.p2_plot.setData(x=list(ticks.keys()), y=dataYDict[self.name[0]], pen="w") #set data view range self.p2.vb.setLimits(xMin=0, xMax=len(dataYDict[self.name[0]])) self.region.sigRegionChanged.connect(self.update) # print (self.p2.vb.viewRange()) # print (self.p1.vb) # self.p1.sigRangeChanged.connect(self.updateRegion) self.region.setRegion([0, len(dataYDict[self.name[0]])]) #calculte Data avg10 = self.moving_average(dataYDict[self.name[0]], 10) self.CalPlotDataItemList[0].setData(y=avg10) #make Max and Min are invisiable by default self.minRadioButton.setChecked(False) self.maxRadioButton.setChecked(False) def toggleFunc(self, rdb, name): plotItem = None for plot in self.PlotDataItemList: if plot.name() == name: plotItem = plot for plot in self.CalPlotDataItemList: if plot.name() == name: plotItem = plot if rdb.isChecked(): self.p1.addItem(plotItem) else: self.p1.removeItem(plotItem) def setDir(self, dir): self.dir = dir def Analysis(self): d = Dialog(self.dataY, self.dataX, self.dir, self.stockInfo) d.exec() def ret_GraphicsLayoutWidget(self): return self.gridContainer @staticmethod def removeEvent(self): return def moving_average(self, data, days): result = [] NanList = [np.nan for i in range(0, days - 1)] data = data[:] for _ in range(len(data) - days + 1): result.append(round(sum(data[-days:]) / days, 2)) data.pop() result = result[::-1] return NanList + result
class Viewer: """ abstraction layer on viewer that dispatches visualisation to specific components and implements widgets for DataSet functionality """ def __init__(self, dataset): self.dataset = dataset self.napari_viewer = None self.plots = None self.blik_widget = None def show(self, napari_viewer=None, **kwargs): self.ensure_ready(napari_viewer=napari_viewer) for db in self.dataset: db.init_depictor(**kwargs) if self.dataset.volumes: self.show_volume(list(self.dataset.volumes.keys())[0]) if self.dataset.plots: self.plots.show() @property def shown(self): if self.napari_viewer and self.volume_selector: return self.dataset.volumes[self.volume_selector.currentText()] return None def ensure_ready(self, napari_viewer=None): if napari_viewer is not None: self._init_viewer(napari_viewer) self._init_plots() self._init_blik_widget() self._hook_keybindings() # check if viewer exists and is still open try: self.napari_viewer.window.qt_viewer.actions() except (AttributeError, RuntimeError): self._init_viewer() self._init_plots() self._init_blik_widget() self._hook_keybindings() def _init_viewer(self, napari_viewer=None): if napari_viewer is not None: self.napari_viewer = napari_viewer else: self.napari_viewer = napari.Viewer(title='napari - Blik') self.napari_viewer.scale_bar.unit = '0.1nm' self.napari_viewer.scale_bar.visible = True # TODO: workaround until layer issues are fixed (napari #2110) self.napari_viewer.window.qt_viewer.destroyed.connect(self.dataset.purge_gui) def _init_plots(self): self.plots = GraphicsLayoutWidget() self._plots_napari_widget = self.napari_viewer.window.add_dock_widget(self.plots, name='Blik - Plots', area='bottom') # use napari hide and show methods self.plots.show = self._plots_napari_widget.show self.plots.hide = self._plots_napari_widget.hide self.plots.hide() def _init_blik_widget(self): self.blik_widget = QWidget() layout = QVBoxLayout() self.blik_widget.setLayout(layout) self.volume_selector = QComboBox(self.blik_widget) self.volume_selector.addItems(self.dataset.volumes.keys()) self.volume_selector.currentTextChanged.connect(self.show_volume) layout.addWidget(self.volume_selector) self.plots_toggler = QPushButton('Show / Hide Plots') self.plots_toggler.clicked.connect(self.toggle_plots) layout.addWidget(self.plots_toggler) self._blik_napari_widget = self.napari_viewer.window.add_dock_widget(self.blik_widget, name='Blik', area='left') # use napari hide and show methods self.blik_widget.show = self._blik_napari_widget.show self.blik_widget.hide = self._blik_napari_widget.hide def _hook_keybindings(self): self.napari_viewer.bind_key('PageUp', self.previous_volume) self.napari_viewer.bind_key('PageDown', self.next_volume) def update_blik_widget(self): if self.blik_widget is not None: current_text = self.volume_selector.currentText() with block_signals(self.volume_selector): self.volume_selector.clear() self.volume_selector.addItems(self.dataset.volumes.keys()) self.volume_selector.setCurrentText(current_text) self.show() def clear_shown(self): for layer in self.napari_viewer.layers.copy(): if layer in self.dataset.napari_layers: self.napari_viewer.layers.remove(layer) self.plots.clear() def show_volume(self, volume): if volume is None: return self.volume_selector.setCurrentText(volume) datablocks = self.dataset.omni + self.dataset.volumes[volume] layers = [] plots = [] for db in datablocks: for dep in db.depictors: if hasattr(dep, 'layers'): if not dep.layers: dep.depict() layers.extend(dep.layers) elif hasattr(dep, 'plot'): if not dep.plot.curves: dep.depict() plots.append(dep.plot) layers = sorted(layers, key=lambda l: isinstance(l, napari.layers.Image), reverse=True) self.clear_shown() self.napari_viewer.layers.extend(layers) for plt in plots: self.plots.addItem(plt) def previous_volume(self, viewer=None): idx = self.volume_selector.currentIndex() previous_idx = (idx - 1) % self.volume_selector.count() self.volume_selector.setCurrentIndex(previous_idx) def next_volume(self, viewer=None): idx = self.volume_selector.currentIndex() next_idx = (idx + 1) % self.volume_selector.count() self.volume_selector.setCurrentIndex(next_idx) def toggle_plots(self): if self.plots.isVisible(): self.plots.hide() else: self.plots.show()
class Dialog(QtGui.QDialog): def __init__(self, dataY=None, dataX=None, dir=None, stockInfo=None, parent=None): super(Dialog, self).__init__(parent) self.resize(800, 800) self.gridContainer = None self.gridContainer = QtWidgets.QWidget(self) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHeightForWidth( self.gridContainer.sizePolicy().hasHeightForWidth()) self.gridContainer.setSizePolicy(sizePolicy) self.gridContainer.setFocusPolicy(QtCore.Qt.NoFocus) self.gridContainer.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.gridContainer.setObjectName("Graphgrid") self.gridContainer.layout = QtWidgets.QGridLayout() self.dir = dir self.stockInfo = stockInfo self.dataX = dataX self.dataY = dataY self.rasingRange = [] self.failingRange = [] # setup Plot self.graph = None self.PlotDataItemList = None self.PlotLocalMaximaData = None self.PlotLocalMinimaData = None self.p1 = None self.colorStyle = ['r', 'g', 'y', 'b', 'w', 'o'] self.setupGraph() self.gridContainer.layout.addWidget(self.graph, 0, 0, 10, 10) self.TableDAT = Concat_Table_With_IDX(self.dir, self.stockInfo.code) print(self.dir, self.stockInfo.code) self.initUI() self.proxy = pg.SignalProxy(self.p1.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) def initUI(self): #check box self.AVGRadioButton = QtWidgets.QCheckBox("AVG") self.AVGRadioButton.setObjectName("avgRadioButton") self.gridContainer.layout.addWidget(self.AVGRadioButton, 0, 11, 1, 1) self.AVGRadioButton.setStyleSheet("color: rgb(0, 255, 0);") self.AVGRadioButton.toggled.connect( lambda: self.toggleFunc(self.AVGRadioButton, 'avg')) self.AVGRadioButton.click() self.LPFRadioButton = QtWidgets.QCheckBox("LPF") self.LPFRadioButton.setObjectName("avgRadioButton") self.gridContainer.layout.addWidget(self.LPFRadioButton, 1, 11, 1, 1) self.LPFRadioButton.setStyleSheet("color: rgb(255,0,0);") self.LPFRadioButton.toggled.connect( lambda: self.toggleFunc(self.LPFRadioButton, 'lpf')) self.LPFRadioButton.click() #set Data self.PlotDataItemList = self.setPlot() self.setData(self.dataX, self.dataY) #set avg slide self.avgSld = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) self.avgSld.setRange(5, 120) self.avgSld.setFocusPolicy(QtCore.Qt.NoFocus) # it seems not work #self.avgSld.setPageStep(5) # self.avgSld.setTickInterval(5) # self.avgSld.setSingleStep(5) self.avgSld.valueChanged.connect(self.updateAvg) self.gridContainer.layout.addWidget(self.avgSld, 11, 1, 1, 2) self.avg = functionAnalysis.moving_average(self.dataY['close'], self.avgSld.value()) self.AvgEdit = QtWidgets.QLabel('5日線') self.AvgEdit.setObjectName("avg") self.gridContainer.layout.addWidget(self.AvgEdit, 11, 0, 1, 1) self.OrderLabel = QtWidgets.QLabel('Order') self.OrderLabel.setObjectName("orderLabel") self.gridContainer.layout.addWidget(self.OrderLabel, 12, 0, 1, 1) self.OrderEdit = QtWidgets.QLineEdit("2") self.OrderEdit.setObjectName("orderEdit") self.gridContainer.layout.addWidget(self.OrderEdit, 12, 1, 1, 1) self.CutoffEdit = QtWidgets.QLabel("Cutoff : 2") self.CutoffEdit.setObjectName("Cutoff") self.gridContainer.layout.addWidget(self.CutoffEdit, 12, 3, 1, 1) self.cutoffSld = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) self.cutoffSld.setRange(2, 60) self.cutoffSld.setFocusPolicy(QtCore.Qt.NoFocus) self.smoothData = functionAnalysis.butterworth_filter( self.dataY['close'], self.cutoffSld.value(), len(self.dataY['close']) / 2, 2) self.cutoffSld.valueChanged.connect(self.updateLPF) self.cutoffSld.setValue(5) self.gridContainer.layout.addWidget(self.cutoffSld, 12, 4, 1, 10) self.PlotDataItemList[1].setData(y=self.avg) self.PlotDataItemList[2].setData(y=self.smoothData) self.gridContainer.setLayout(self.gridContainer.layout) #make AVG is invisiable by default self.AVGRadioButton.setChecked(False) self.tableView = QtWidgets.QTableView() self.TableDAT = self.TableDAT.round(2) #Temporary, we delete 1st level colums, for showing columns in TableView self.TableDAT.columns = self.TableDAT.columns.droplevel(0) self.model = PandaModel.DataFrameModel(self.TableDAT) self.tableView.setModel(self.model) #Resize cell size to self.tableView.resizeColumnsToContents() self.gridContainer.layout.addWidget(self.tableView, 13, 0, 1, 10) def setupGraph(self): self.date_axis = TimeAxisItem(orientation='bottom') self.graph = GraphicsLayoutWidget() self.graph.setObjectName("Graph UI") self.date_axis = TimeAxisItem(orientation='bottom') self.label = pg.LabelItem(justify='right') self.graph.addItem(self.label) self.p1 = self.graph.addPlot(row=1, col=0, axisItems={'bottom': self.date_axis}) #p1 zoom in/out, move viewbox can auto scaling Y, Important! self.p1.vb.sigXRangeChanged.connect(self.setYRange) self.PlotDataItemList = self.setPlot() self.PlotLocalMaximaData = pg.ScatterPlotItem(pen='r', brush='b', symbol='d', pxMode=True, size=10) self.PlotLocalMinimaData = pg.ScatterPlotItem(pen='g', brush='b', symbol='t', pxMode=True, size=10) # self.p1.addItem(self.PlotLocalMaximaData) # self.p1.addItem(self.PlotLocalMinimaData) # def showEvent(self, event): # geom = self.frameGeometry() # geom.moveCenter(QtGui.QCursor.pos()) # self.setGeometry(geom) # super(Dialog, self).showEvent(event) def setPlot(self): PlotDataItemList = [] self.name = ['close', 'avg', 'lpf'] for i in range(0, 3): PlotDataItemList.append( self.p1.plot(pen=self.colorStyle[i], name=self.name[i])) return PlotDataItemList def setYRange(self, plotitem): plotitem.enableAutoRange(axis='y') plotitem.setAutoVisible(y=True) def setData(self, dataX, dataY): ticks = dict(enumerate(dataX)) '''set X-value''' self.date_axis.setTicks( [list(ticks.items())[::120], list(ticks.items())[::1]]) self.PlotDataItemList[0].setData(y=dataY['close']) def updateAvg(self, value): self.AvgEdit.setText(str(value) + '日線') self.avg = functionAnalysis.moving_average(self.dataY['close'], self.avgSld.value()) self.PlotDataItemList[1].setData(y=self.avg) def updateLPF(self, value): self.CutoffEdit.setText("Cutoff : " + str(value)) self.smoothData = functionAnalysis.butterworth_filter( self.dataY['close'], self.cutoffSld.value(), len(self.dataY['close']) / 2, int(self.OrderEdit.text())) self.PlotDataItemList[2].setData(y=self.smoothData) maxidx, minidx = functionAnalysis.getlocalMaxMin( self.smoothData, True, True) #Split Range sortRange = [] lastRoundIdx = 0 for idxMax, idxMin in zip(maxidx, minidx): if idxMax > idxMin: sortRange.append([lastRoundIdx, idxMin]) sortRange.append([idxMin, idxMax]) lastRoundIdx = idxMax else: sortRange.append([lastRoundIdx, idxMax]) sortRange.append([idxMax, idxMin]) lastRoundIdx = idxMin sortRange.append([ maxidx[-1], -1 ]) if maxidx[-1] > minidx[-1] else sortRange.append([minidx[-1], -1]) self.PlotLocalMaximaData.setData( x=maxidx, y=[self.dataY['close'][i] for i in maxidx]) self.PlotLocalMinimaData.setData( x=minidx, y=[self.dataY['close'][i] for i in minidx]) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Escape: self.hide() event.accept() else: super(Dialog, self).keyPressEvent(event) def toggleFunc(self, rdb, name): plotItem = None for plot in self.PlotDataItemList: if plot.name() == name: plotItem = plot if rdb.isChecked(): self.p1.addItem(plotItem) else: self.p1.removeItem(plotItem) def mouseMoved(self, evt): pos = evt[ 0] ## using signal proxy turns original arguments into a tuple if self.p1.sceneBoundingRect().contains(pos): mousePoint = self.p1.vb.mapSceneToView(pos) index = int(mousePoint.x()) if index > 0 and index < len(self.dataX): idx = int(mousePoint.x()) self.label.setText( "<span style='font-size: 12pt'>x=%s, <span style='color: red'>收盤價=%0.1f</span>" % (self.dataX[idx], self.dataY['close'][idx]))