def update_limits(self, update_profile=True): with delay_callback(self, 'v_min', 'v_max'): if update_profile: self.update_profile(update_limits=False) if self._profile_cache is not None and len(self._profile_cache[1]) > 0: self.v_min = nanmin(self._profile_cache[1]) self.v_max = nanmax(self._profile_cache[1])
def python_export_histogram_layer(layer, *args): if len(layer.mpl_artists) == 0 or not layer.enabled or not layer.visible: return [], None script = "" imports = ["import numpy as np"] x = layer.layer[layer._viewer_state.x_att] x_min = nanmin(x) x_max = nanmax(x) hist_x_min = layer._viewer_state.hist_x_min hist_x_max = layer._viewer_state.hist_x_max script += "# Get main data values\n" script += "x = layer_data['{0}']\n\n".format( layer._viewer_state.x_att.label) script += "# Set up histogram bins\n" script += "hist_n_bin = {0}\n".format(layer._viewer_state.hist_n_bin) if abs((x_min - hist_x_min) / (hist_x_max - hist_x_min)) < 0.001: script += "hist_x_min = np.nanmin(x)\n" else: script += "hist_x_min = {0}\n".format(hist_x_min) if abs((x_max - hist_x_max) / (hist_x_max - hist_x_min)) < 0.001: script += "hist_x_max = np.nanmax(x)\n" else: script += "hist_x_max = {0}\n".format(hist_x_max) options = dict(alpha=layer.state.alpha, color=layer.state.color, zorder=layer.state.zorder, edgecolor='none') if layer._viewer_state.x_log: script += "bins = np.logspace(np.log10(hist_x_min), np.log10(hist_x_max), hist_n_bin)\n" options['bins'] = code('bins') else: options['range'] = code('[hist_x_min, hist_x_max]') options['bins'] = code('hist_n_bin') if layer._viewer_state.normalize: if MATPLOTLIB_GE_30: options['density'] = True else: options['normed'] = True if layer._viewer_state.cumulative: options['cumulative'] = True script += "\nx = x[(x >= hist_x_min) & (x <= hist_x_max)]\n" script += "\nax.hist(x, {0})".format(serialize_options(options)) return imports, script.strip()
def _calculate_profile(self): x, y = self.state.profile if x is None or y is None: self.disable_invalid_attributes(self._viewer_state.x_att) return else: self.enable() self._visible_data = x, y # Update the data values. if len(x) > 0: # Normalize profile values to the [0:1] range based on limits if self._viewer_state.normalize: y = self.state.normalize_values(y) self.plot_artist.set_data(x, y) self.plot_artist.set_visible(True) else: # We need to do this otherwise we get issues on Windows when # passing an empty list to plot_artist self.plot_artist.set_visible(False) if len(x) == 0: return # TODO: the following was copy/pasted from the histogram viewer, maybe # we can find a way to avoid duplication? # We have to do the following to make sure that we reset the y_max as # needed. We can't simply reset based on the maximum for this layer # because other layers might have other values, and we also can't do: # # self._viewer_state.y_max = max(self._viewer_state.y_max, result[0].max()) # # because this would never allow y_max to get smaller. if not self._viewer_state.normalize: self.state._y_min = nanmin(y) self.state._y_max = nanmax(y) * 1.2 largest_y_max = max( getattr(layer, '_y_max', 0) for layer in self._viewer_state.layers) if largest_y_max != self._viewer_state.y_max: self._viewer_state.y_max = largest_y_max smallest_y_min = min( getattr(layer, '_y_min', np.inf) for layer in self._viewer_state.layers) if smallest_y_min != self._viewer_state.y_min: self._viewer_state.y_min = smallest_y_min self.redraw()
def _calculate_profile_postthread(self): # It's possible for this method to get called but for the state to have # been updated in the mean time to have a histogram that raises an # exception (for example an IncompatibleAttribute). If any errors happen # here, we simply ignore them since _calculate_histogram_error will get # called directly. try: visible_data = self.state.profile except Exception: return if visible_data is None: return self.enable() x, y = visible_data # Update the data values. if len(x) > 0: self.state.update_limits() # Normalize profile values to the [0:1] range based on limits if self._viewer_state.normalize: y = self.state.normalize_values(y) with self.line_mark.hold_sync(): self.line_mark.x = x self.line_mark.y = y else: with self.line_mark.hold_sync(): self.line_mark.x = [0.] self.line_mark.y = [0.] if not self._viewer_state.normalize and len(y) > 0: y_min = nanmin(y) y_max = nanmax(y) y_range = y_max - y_min self.state._y_min = y_min - y_range * 0.1 self.state._y_max = y_max + y_range * 0.1 largest_y_max = max( getattr(layer, '_y_max', 0) for layer in self._viewer_state.layers) if largest_y_max != self._viewer_state.y_max: self._viewer_state.y_max = largest_y_max smallest_y_min = min( getattr(layer, '_y_min', np.inf) for layer in self._viewer_state.layers) if smallest_y_min != self._viewer_state.y_min: self._viewer_state.y_min = smallest_y_min self.redraw()
def python_export_histogram_layer(layer, *args): if len(layer.mpl_artists) == 0 or not layer.enabled or not layer.visible: return [], None script = "" imports = ["import numpy as np"] x = layer.layer[layer._viewer_state.x_att] x_min = nanmin(x) x_max = nanmax(x) hist_x_min = layer._viewer_state.hist_x_min hist_x_max = layer._viewer_state.hist_x_max script += "# Get main data values\n" script += "x = layer_data['{0}']\n\n".format(layer._viewer_state.x_att.label) script += "# Set up histogram bins\n" script += "hist_n_bin = {0}\n".format(layer._viewer_state.hist_n_bin) if abs((x_min - hist_x_min) / (hist_x_max - hist_x_min)) < 0.001: script += "hist_x_min = np.nanmin(x)\n" else: script += "hist_x_min = {0}\n".format(hist_x_min) if abs((x_max - hist_x_max) / (hist_x_max - hist_x_min)) < 0.001: script += "hist_x_max = np.nanmax(x)\n" else: script += "hist_x_max = {0}\n".format(hist_x_max) options = dict(alpha=layer.state.alpha, color=layer.state.color, zorder=layer.state.zorder, edgecolor='none') if layer._viewer_state.x_log: script += "bins = np.logspace(np.log10(hist_x_min), np.log10(hist_x_max), hist_n_bin)\n" options['bins'] = code('bins') else: options['range'] = code('[hist_x_min, hist_x_max]') options['bins'] = code('hist_n_bin') if layer._viewer_state.normalize: options['normed'] = True if layer._viewer_state.cumulative: options['cumulative'] = True script += "\nx = x[(x >= hist_x_min) & (x <= hist_x_max)]\n" script += "\nax.hist(x, {0})".format(serialize_options(options)) return imports, script.strip()
def _set_limits(self): data = self.ui.component_selector.data cid = self.ui.component_selector.component vals = data[cid] wmin = self.ui.value_min wmax = self.ui.value_max wmin.setText(pretty_number(nanmin(vals))) wmax.setText(pretty_number(nanmax(vals)))
def visible_limits(artists, axis): """ Determines the data limits for the data in a set of artists. Ignores non-visible artists Assumes each artist as a get_data method wich returns a tuple of x,y Returns a tuple of min, max for the requested axis, or None if no data present Parameters ---------- artists : iterable An iterable collection of artists axis : int Which axis to compute. 0=xaxis, 1=yaxis """ data = [] for art in artists: if not art.visible: continue xy = art.get_data() assert isinstance(xy, tuple) val = xy[axis] if val.size > 0: data.append(xy[axis]) if len(data) == 0: return data = np.hstack(data) if data.size == 0: return data = data[np.isfinite(data)] if data.size == 0: return lo, hi = nanmin(data), nanmax(data) if not np.isfinite(lo): return return lo, hi
def visible_limits(artists, axis): """ Determines the data limits for the data in a set of artists. Ignores non-visible artists Assumes each artist as a get_data method which returns a tuple of x,y Returns a tuple of min, max for the requested axis, or None if no data present Parameters ---------- artists : iterable An iterable collection of artists axis : int Which axis to compute. 0=xaxis, 1=yaxis """ data = [] for art in artists: if not art.visible: continue xy = art.get_data() assert isinstance(xy, tuple) val = xy[axis] if val.size > 0: data.append(xy[axis]) if len(data) == 0: return data = np.hstack(data) if data.size == 0: return data = data[np.isfinite(data)] if data.size == 0: return lo, hi = nanmin(data), nanmax(data) if not np.isfinite(lo): return return lo, hi
def update_values(self, force=False, use_default_modifiers=False, **properties): if not force and not any(prop in properties for prop in ('attribute', 'n_bin')) or self.data is None: self.set() return comp = self.data_component if 'n_bin' in properties: self.set() if self._common_n_bin is not None and not comp.categorical: self._common_n_bin = properties['n_bin'] self._apply_common_n_bin() if 'attribute' in properties or force: if comp.categorical: n_bin = max(1, min(comp.categories.size, self._max_n_bin)) lower = -0.5 upper = lower + comp.categories.size else: if self._common_n_bin is None: n_bin = self._default_n_bin else: n_bin = self._common_n_bin values = self.data_values if comp.datetime: lower = values.min() upper = values.max() else: lower = nanmin(values) upper = nanmax(values) self.set(lower=lower, upper=upper, n_bin=n_bin)
def max(self, array): return 10.**(np.log10(nanmax(array)) * self.contrast)
def _update_data(self): # Layer artist has been cleared already if len(self.mpl_artists) == 0: return try: if not self.state.density_map: x = ensure_numerical( self.layer[self._viewer_state.x_att].ravel()) if x.dtype.kind == 'M': x = datetime64_to_mpl(x) except (IncompatibleAttribute, IndexError): # The following includes a call to self.clear() self.disable_invalid_attributes(self._viewer_state.x_att) return else: self.enable() try: if not self.state.density_map: y = ensure_numerical( self.layer[self._viewer_state.y_att].ravel()) if y.dtype.kind == 'M': y = datetime64_to_mpl(y) except (IncompatibleAttribute, IndexError): # The following includes a call to self.clear() self.disable_invalid_attributes(self._viewer_state.y_att) return else: self.enable() if self.state.markers_visible: if self.state.density_map: # We don't use x, y here because we actually make use of the # ability of the density artist to call a custom histogram # method which is defined on this class and does the data # access. self.plot_artist.set_data([], []) self.scatter_artist.set_offsets(np.zeros((0, 2))) else: if self.state.cmap_mode == 'Fixed' and self.state.size_mode == 'Fixed': # In this case we use Matplotlib's plot function because it has much # better performance than scatter. self.plot_artist.set_data(x, y) self.scatter_artist.set_offsets(np.zeros((0, 2))) else: self.plot_artist.set_data([], []) offsets = np.vstack((x, y)).transpose() self.scatter_artist.set_offsets(offsets) else: self.plot_artist.set_data([], []) self.scatter_artist.set_offsets(np.zeros((0, 2))) if self.state.line_visible: if self.state.cmap_mode == 'Fixed': self.line_collection.set_points(x, y, oversample=False) else: # In the case where we want to color the line, we need to over # sample the line by a factor of two so that we can assign the # correct colors to segments - if we didn't do this, then # segments on one side of a point would be a different color # from the other side. With oversampling, we can have half a # segment on either side of a point be the same color as a # point self.line_collection.set_points(x, y) else: self.line_collection.set_points([], []) for eartist in ravel_artists(self.errorbar_artist): try: eartist.remove() except ValueError: pass if self.vector_artist is not None: self.vector_artist.remove() self.vector_artist = None if self.state.vector_visible: if self.state.vx_att is not None and self.state.vy_att is not None: vx = ensure_numerical(self.layer[self.state.vx_att].ravel()) vy = ensure_numerical(self.layer[self.state.vy_att].ravel()) if self.state.vector_mode == 'Polar': ang = vx length = vy # assume ang is anti clockwise from the x axis vx = length * np.cos(np.radians(ang)) vy = length * np.sin(np.radians(ang)) else: vx = None vy = None if self.state.vector_arrowhead: hw = 3 hl = 5 else: hw = 1 hl = 0 vmax = nanmax(np.hypot(vx, vy)) self.vector_artist = self.axes.quiver( x, y, vx, vy, units='width', pivot=self.state.vector_origin, headwidth=hw, headlength=hl, scale_units='width', angles='xy', scale=10 / self.state.vector_scaling * vmax) self.mpl_artists[self.vector_index] = self.vector_artist if self.state.xerr_visible or self.state.yerr_visible: keep = ~np.isnan(x) & ~np.isnan(y) if self.state.xerr_visible and self.state.xerr_att is not None: xerr = ensure_numerical( self.layer[self.state.xerr_att].ravel()).copy() keep &= ~np.isnan(xerr) else: xerr = None if self.state.yerr_visible and self.state.yerr_att is not None: yerr = ensure_numerical( self.layer[self.state.yerr_att].ravel()).copy() keep &= ~np.isnan(yerr) else: yerr = None if xerr is not None: xerr = xerr[keep] if yerr is not None: yerr = yerr[keep] self._errorbar_keep = keep self.errorbar_artist = self.axes.errorbar(x[keep], y[keep], fmt='none', xerr=xerr, yerr=yerr) self.mpl_artists[self.errorbar_index] = self.errorbar_artist
def max(self, array): return 10. ** (np.log10(nanmax(array)) * self.contrast)
def _update_data(self): # Layer artist has been cleared already if len(self.mpl_artists) == 0: return try: if not self.state.density_map: x = ensure_numerical(self.layer[self._viewer_state.x_att].ravel()) except (IncompatibleAttribute, IndexError): # The following includes a call to self.clear() self.disable_invalid_attributes(self._viewer_state.x_att) return else: self.enable() try: if not self.state.density_map: y = ensure_numerical(self.layer[self._viewer_state.y_att].ravel()) except (IncompatibleAttribute, IndexError): # The following includes a call to self.clear() self.disable_invalid_attributes(self._viewer_state.y_att) return else: self.enable() if self.state.markers_visible: if self.state.density_map: # We don't use x, y here because we actually make use of the # ability of the density artist to call a custom histogram # method which is defined on this class and does the data # access. self.plot_artist.set_data([], []) self.scatter_artist.set_offsets(np.zeros((0, 2))) else: if self.state.cmap_mode == 'Fixed' and self.state.size_mode == 'Fixed': # In this case we use Matplotlib's plot function because it has much # better performance than scatter. self.plot_artist.set_data(x, y) self.scatter_artist.set_offsets(np.zeros((0, 2))) else: self.plot_artist.set_data([], []) offsets = np.vstack((x, y)).transpose() self.scatter_artist.set_offsets(offsets) else: self.plot_artist.set_data([], []) self.scatter_artist.set_offsets(np.zeros((0, 2))) if self.state.line_visible: if self.state.cmap_mode == 'Fixed': self.line_collection.set_points(x, y, oversample=False) else: # In the case where we want to color the line, we need to over # sample the line by a factor of two so that we can assign the # correct colors to segments - if we didn't do this, then # segments on one side of a point would be a different color # from the other side. With oversampling, we can have half a # segment on either side of a point be the same color as a # point self.line_collection.set_points(x, y) else: self.line_collection.set_points([], []) for eartist in ravel_artists(self.errorbar_artist): try: eartist.remove() except ValueError: pass if self.vector_artist is not None: self.vector_artist.remove() self.vector_artist = None if self.state.vector_visible: if self.state.vx_att is not None and self.state.vy_att is not None: vx = ensure_numerical(self.layer[self.state.vx_att].ravel()) vy = ensure_numerical(self.layer[self.state.vy_att].ravel()) if self.state.vector_mode == 'Polar': ang = vx length = vy # assume ang is anti clockwise from the x axis vx = length * np.cos(np.radians(ang)) vy = length * np.sin(np.radians(ang)) else: vx = None vy = None if self.state.vector_arrowhead: hw = 3 hl = 5 else: hw = 1 hl = 0 vmax = nanmax(np.hypot(vx, vy)) self.vector_artist = self.axes.quiver(x, y, vx, vy, units='width', pivot=self.state.vector_origin, headwidth=hw, headlength=hl, scale_units='width', angles='xy', scale=10 / self.state.vector_scaling * vmax) self.mpl_artists[self.vector_index] = self.vector_artist if self.state.xerr_visible or self.state.yerr_visible: if self.state.xerr_visible and self.state.xerr_att is not None: xerr = ensure_numerical(self.layer[self.state.xerr_att].ravel()) else: xerr = None if self.state.yerr_visible and self.state.yerr_att is not None: yerr = ensure_numerical(self.layer[self.state.yerr_att].ravel()) else: yerr = None self.errorbar_artist = self.axes.errorbar(x, y, fmt='none', xerr=xerr, yerr=yerr) self.mpl_artists[self.errorbar_index] = self.errorbar_artist
def facet_subsets(data_collection, cid, lo=None, hi=None, steps=5, prefix='', log=False): """ Create a series of subsets that partition the values of a particular attribute into several bins This creates `steps` new subset groups, adds them to the data collection, and returns the list of newly created subset groups. Parameters ---------- data : :class:`~glue.core.data_collection.DataCollection` The DataCollection object to use cid : :class:`~glue.core.component_id.ComponentID` The ComponentID to facet on lo : float, optional The lower bound for the faceting. Defaults to minimum value in data hi : float, optional The upper bound for the faceting. Defaults to maximum value in data steps : int, optional The number of subsets to create. Defaults to 5 prefix : str, optional If present, the new subset labels will begin with `prefix` log : bool, optional If `True`, space divisions logarithmically. Default is `False` Returns ------- subset_groups : iterable List of :class:`~glue.core.subset_group.SubsetGroup` instances added to `data` Examples -------- :: facet_subset(data, data.id['mass'], lo=0, hi=10, steps=2) creates 2 new subsets. The first represents the constraint 0 <= mass < 5. The second represents 5 <= mass <= 10:: facet_subset(data, data.id['mass'], lo=10, hi=0, steps=2) Creates 2 new subsets. The first represents the constraint 10 >= x > 5 The second represents 5 >= mass >= 0:: facet_subset(data, data.id['mass'], lo=0, hi=10, steps=2, prefix='m') Labels the subsets ``m_1`` and ``m_2``. Note that the last range is inclusive on both sides. For example, if ``lo`` is 0 and ``hi`` is 5, and ``steps`` is 5, then the intervals for the subsets are [0,1), [1,2), [2,3), [3,4), and [4,5]. """ from glue.core.exceptions import IncompatibleAttribute if lo is None or hi is None: for data in data_collection: try: vals = data[cid] break except IncompatibleAttribute: continue else: raise ValueError("Cannot infer data limits for ComponentID %s" % cid) if lo is None: lo = nanmin(vals) if hi is None: hi = nanmax(vals) reverse = lo > hi if log: rng = np.logspace(np.log10(lo), np.log10(hi), steps + 1) else: rng = np.linspace(lo, hi, steps + 1) states = [] labels = [] for i in range(steps): # The if i < steps - 1 clauses are needed because the last interval # has to be inclusive on both sides. if reverse: if i < steps - 1: states.append((cid <= rng[i]) & (cid > rng[i + 1])) labels.append(prefix + '{0}<{1}<={2}'.format(rng[i + 1], cid, rng[i])) else: states.append((cid <= rng[i]) & (cid >= rng[i + 1])) labels.append(prefix + '{0}<={1}<={2}'.format(rng[i + 1], cid, rng[i])) else: if i < steps - 1: states.append((cid >= rng[i]) & (cid < rng[i + 1])) labels.append(prefix + '{0}<={1}<{2}'.format(rng[i], cid, rng[i + 1])) else: states.append((cid >= rng[i]) & (cid <= rng[i + 1])) labels.append(prefix + '{0}<={1}<={2}'.format(rng[i], cid, rng[i + 1])) result = [] for lbl, s in zip(labels, states): sg = data_collection.new_subset_group(label=lbl, subset_state=s) result.append(sg) return result
def _update_profile(self, *event): if self.viewer_state is None or self.viewer_state.x_att is None or self.attribute is None: self._profile = None, None return # Check what pixel axis in the current dataset x_att corresponds to pix_cid = is_convertible_to_single_pixel_cid(self.layer, self.viewer_state.x_att) if pix_cid is None: self._profile = None, None return # If we get here, then x_att does correspond to a single pixel axis in # the cube, so we now prepare a list of axes to collapse over. axes = tuple(i for i in range(self.layer.ndim) if i != pix_cid.axis) # We now get the y values for the data # TODO: in future we should optimize the case where the mask is much # smaller than the data to just average the relevant 'spaxels' in the # data rather than collapsing the whole cube. try: if isinstance(self.layer, Data): data = self.layer data_values = data[self.attribute] else: data = self.layer.data if isinstance(self.layer.subset_state, SliceSubsetState): data_values = self.layer.subset_state.to_array( self.layer.data, self.attribute) else: # We need to force a copy *and* convert to float just in case data_values = np.array(data[self.attribute], dtype=float) mask = self.layer.to_mask() if np.sum(mask) == 0: self._profile = [], [] return data_values[~mask] = np.nan except IncompatibleAttribute: self._profile = None, None return # Collapse along all dimensions except x_att if self.layer.ndim > 1: with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) profile_values = self.viewer_state.function(data_values, axis=axes) else: profile_values = data_values # Finally, we get the coordinate values for the requested axis axis_view = [0] * data.ndim axis_view[pix_cid.axis] = slice(None) axis_values = data[self.viewer_state.x_att, axis_view] with delay_callback(self, 'v_min', 'v_max'): self._profile = axis_values, profile_values self.v_min = nanmin(profile_values) self.v_max = nanmax(profile_values)
def facet_subsets(data_collection, cid, lo=None, hi=None, steps=5, prefix='', log=False): """ Create a series of subsets that partition the values of a particular attribute into several bins This creates `steps` new subet groups, adds them to the data collection, and returns the list of newly created subset groups. Parameters ---------- data : :class:`~glue.core.data_collection.DataCollection` The DataCollection object to use cid : :class:`~glue.core.component_id.ComponentID` The ComponentID to facet on lo : float, optional The lower bound for the faceting. Defaults to minimum value in data hi : float, optional The upper bound for the faceting. Defaults to maximum value in data steps : int, optional The number of subsets to create. Defaults to 5 prefix : str, optional If present, the new subset labels will begin with `prefix` log : bool, optional If `True`, space divisions logarithmically. Default is `False` Returns ------- subset_groups : iterable List of :class:`~glue.core.subset_group.SubsetGroup` instances added to `data` Examples -------- :: facet_subset(data, data.id['mass'], lo=0, hi=10, steps=2) creates 2 new subsets. The first represents the constraint 0 <= mass < 5. The second represents 5 <= mass <= 10:: facet_subset(data, data.id['mass'], lo=10, hi=0, steps=2) Creates 2 new subsets. The first represents the constraint 10 >= x > 5 The second represents 5 >= mass >= 0:: facet_subset(data, data.id['mass'], lo=0, hi=10, steps=2, prefix='m') Labels the subsets ``m_1`` and ``m_2``. Note that the last range is inclusive on both sides. For example, if ``lo`` is 0 and ``hi`` is 5, and ``steps`` is 5, then the intervals for the subsets are [0,1), [1,2), [2,3), [3,4), and [4,5]. """ from glue.core.exceptions import IncompatibleAttribute if lo is None or hi is None: for data in data_collection: try: vals = data[cid] break except IncompatibleAttribute: continue else: raise ValueError("Cannot infer data limits for ComponentID %s" % cid) if lo is None: lo = nanmin(vals) if hi is None: hi = nanmax(vals) reverse = lo > hi if log: rng = np.logspace(np.log10(lo), np.log10(hi), steps + 1) else: rng = np.linspace(lo, hi, steps + 1) states = [] labels = [] for i in range(steps): # The if i < steps - 1 clauses are needed because the last interval # has to be inclusive on both sides. if reverse: if i < steps - 1: states.append((cid <= rng[i]) & (cid > rng[i + 1])) labels.append(prefix + '{0}<{1}<={2}'.format(rng[i + 1], cid, rng[i])) else: states.append((cid <= rng[i]) & (cid >= rng[i + 1])) labels.append(prefix + '{0}<={1}<={2}'.format(rng[i + 1], cid, rng[i])) else: if i < steps - 1: states.append((cid >= rng[i]) & (cid < rng[i + 1])) labels.append(prefix + '{0}<={1}<{2}'.format(rng[i], cid, rng[i + 1])) else: states.append((cid >= rng[i]) & (cid <= rng[i + 1])) labels.append(prefix + '{0}<={1}<={2}'.format(rng[i], cid, rng[i + 1])) result = [] for lbl, s in zip(labels, states): sg = data_collection.new_subset_group(label=lbl, subset_state=s) result.append(sg) return result
def _calculate_profile_postthread(self): self.notify_end_computation() # It's possible for this method to get called but for the state to have # been updated in the mean time to have a histogram that raises an # exception (for example an IncompatibleAttribute). If any errors happen # here, we simply ignore them since _calculate_histogram_error will get # called directly. try: visible_data = self.state.profile except Exception: return if visible_data is None: return self.enable() x, y = visible_data # Update the data values. if len(x) > 0: self.state.update_limits() # Normalize profile values to the [0:1] range based on limits if self._viewer_state.normalize: y = self.state.normalize_values(y) self.plot_artist.set_data(x, y) self.plot_artist.set_visible(self.state.visible) else: # We need to do this otherwise we get issues on Windows when # passing an empty list to plot_artist self.plot_artist.set_visible(False) # TODO: the following was copy/pasted from the histogram viewer, maybe # we can find a way to avoid duplication? # We have to do the following to make sure that we reset the y_max as # needed. We can't simply reset based on the maximum for this layer # because other layers might have other values, and we also can't do: # # self._viewer_state.y_max = max(self._viewer_state.y_max, result[0].max()) # # because this would never allow y_max to get smaller. if not self._viewer_state.normalize and len(y) > 0: y_min = nanmin(y) y_max = nanmax(y) y_range = y_max - y_min self.state._y_min = y_min - y_range * 0.1 self.state._y_max = y_max + y_range * 0.1 largest_y_max = max( getattr(layer, '_y_max', 0) for layer in self._viewer_state.layers) if largest_y_max != self._viewer_state.y_max: self._viewer_state.y_max = largest_y_max smallest_y_min = min( getattr(layer, '_y_min', np.inf) for layer in self._viewer_state.layers) if smallest_y_min != self._viewer_state.y_min: self._viewer_state.y_min = smallest_y_min self.redraw()
def _update_data(self, changed): # Layer artist has been cleared already if len(self.mpl_artists) == 0: return try: x = self.layer[self._viewer_state.x_att].ravel() except (IncompatibleAttribute, IndexError): # The following includes a call to self.clear() self.disable_invalid_attributes(self._viewer_state.x_att) return else: self.enable() try: y = self.layer[self._viewer_state.y_att].ravel() except (IncompatibleAttribute, IndexError): # The following includes a call to self.clear() self.disable_invalid_attributes(self._viewer_state.y_att) return else: self.enable() if self.state.markers_visible: if self.state.density_map: self.density_artist.set_xy(x, y) self.plot_artist.set_data([], []) self.scatter_artist.set_offsets(np.zeros((0, 2))) else: self.density_artist.set_xy([], []) self.density_artist.set_c(None) if self.state.cmap_mode == 'Fixed' and self.state.size_mode == 'Fixed': # In this case we use Matplotlib's plot function because it has much # better performance than scatter. self.plot_artist.set_data(x, y) self.scatter_artist.set_offsets(np.zeros((0, 2))) else: self.plot_artist.set_data([], []) offsets = np.vstack((x, y)).transpose() self.scatter_artist.set_offsets(offsets) else: self.plot_artist.set_data([], []) self.scatter_artist.set_offsets(np.zeros((0, 2))) self.density_artist.set_xy([], []) if self.state.line_visible: if self.state.cmap_mode == 'Fixed': self.line_collection.set_points(x, y, oversample=False) else: # In the case where we want to color the line, we need to over # sample the line by a factor of two so that we can assign the # correct colors to segments - if we didn't do this, then # segments on one side of a point would be a different color # from the other side. With oversampling, we can have half a # segment on either side of a point be the same color as a # point self.line_collection.set_points(x, y) else: self.line_collection.set_points([], []) for eartist in ravel_artists(self.errorbar_artist): try: eartist.remove() except ValueError: pass if self.vector_artist is not None: self.vector_artist.remove() self.vector_artist = None if self.state.vector_visible: if self.state.vx_att is not None and self.state.vy_att is not None: vx = self.layer[self.state.vx_att].ravel() vy = self.layer[self.state.vy_att].ravel() if self.state.vector_mode == 'Polar': ang = vx length = vy # assume ang is anti clockwise from the x axis vx = length * np.cos(np.radians(ang)) vy = length * np.sin(np.radians(ang)) else: vx = None vy = None if self.state.vector_arrowhead: hw = 3 hl = 5 else: hw = 1 hl = 0 vmax = nanmax(np.hypot(vx, vy)) self.vector_artist = self.axes.quiver( x, y, vx, vy, units='width', pivot=self.state.vector_origin, headwidth=hw, headlength=hl, scale_units='width', angles='xy', scale=10 / self.state.vector_scaling * vmax) self.mpl_artists[self.vector_index] = self.vector_artist if self.state.xerr_visible or self.state.yerr_visible: if self.state.xerr_visible and self.state.xerr_att is not None: xerr = self.layer[self.state.xerr_att].ravel() else: xerr = None if self.state.yerr_visible and self.state.yerr_att is not None: yerr = self.layer[self.state.yerr_att].ravel() else: yerr = None self.errorbar_artist = self.axes.errorbar(x, y, fmt='none', xerr=xerr, yerr=yerr) self.mpl_artists[self.errorbar_index] = self.errorbar_artist
def _calculate_profile_postthread(self): # If the worker has started running again, we should stop at this point # since this function will get called again. if QT_INSTALLED and self._worker.running: return self.notify_end_computation() # It's possible for this method to get called but for the state to have # been updated in the mean time to have a histogram that raises an # exception (for example an IncompatibleAttribute). If any errors happen # here, we simply ignore them since _calculate_histogram_error will get # called directly. try: visible_data = self.state.profile except Exception: return if visible_data is None: return self.enable() x, y = visible_data # Update the data values. if len(x) > 0: self.state.update_limits() # Normalize profile values to the [0:1] range based on limits if self._viewer_state.normalize: y = self.state.normalize_values(y) self.plot_artist.set_data(x, y) self.plot_artist.set_visible(self.state.visible) else: # We need to do this otherwise we get issues on Windows when # passing an empty list to plot_artist self.plot_artist.set_visible(False) # TODO: the following was copy/pasted from the histogram viewer, maybe # we can find a way to avoid duplication? # We have to do the following to make sure that we reset the y_max as # needed. We can't simply reset based on the maximum for this layer # because other layers might have other values, and we also can't do: # # self._viewer_state.y_max = max(self._viewer_state.y_max, result[0].max()) # # because this would never allow y_max to get smaller. if not self._viewer_state.normalize and len(y) > 0: y_min = nanmin(y) y_max = nanmax(y) y_range = y_max - y_min self.state._y_min = y_min - y_range * 0.1 self.state._y_max = y_max + y_range * 0.1 largest_y_max = max(getattr(layer, '_y_max', 0) for layer in self._viewer_state.layers) if largest_y_max != self._viewer_state.y_max: self._viewer_state.y_max = largest_y_max smallest_y_min = min(getattr(layer, '_y_min', np.inf) for layer in self._viewer_state.layers) if smallest_y_min != self._viewer_state.y_min: self._viewer_state.y_min = smallest_y_min self.redraw()