def _raw_ticks(self, vmin, vmax): if self._nbins == 'auto': if self.axis is not None: nbins = np.clip(self.axis.get_tick_space(), max(1, self._min_n_ticks - 1), 9) else: nbins = 9 else: nbins = self._nbins scale, offset = mticker.scale_range(vmin, vmax, nbins) _vmin = vmin - offset _vmax = vmax - offset raw_step = 1 / (1 / vmax - 1 / vmin) / nbins steps = self._extended_steps * scale if self._integer: # For steps > 1, keep only integer values. igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001) steps = steps[igood] self.numticks = len(self.other_axis.get_xticks()) ticks = 1 / np.linspace(1 / vmin, 1 / vmax, self.numticks) for step in steps: best_vmin = (_vmin // step) * step low = np.round(mticker.Base(step).le(_vmin - best_vmin) / step) high = np.round(mticker.Base(step).ge(_vmax - best_vmin) / step) return ticks
def linear_tics(self, vmin, vmax): nbins = self._ntics scale, offset = ticker.scale_range(vmin, vmax, nbins) vmin -= offset vmax -= offset raw_step = (vmax - vmin) / nbins scaled_raw_step = raw_step / scale for step in [1, 2, 5, 10]: if step < scaled_raw_step: continue step *= scale best_vmin = step * divmod(vmin, step)[0] best_vmax = best_vmin + step * nbins if (best_vmax >= vmax): break if self._trim: extra_bins = int(divmod((best_vmax - vmax), step)[0]) nbins -= extra_bins return (np.arange(nbins + 1) * step + best_vmin + offset)
def linear_tics(self, vmin, vmax): nbins = self._ntics scale, offset = ticker.scale_range(vmin, vmax, nbins) vmin -= offset vmax -= offset raw_step = (vmax-vmin)/nbins scaled_raw_step = raw_step/scale for step in [1,2,5,10]: if step < scaled_raw_step: continue step *= scale best_vmin = step*divmod(vmin, step)[0] best_vmax = best_vmin + step*nbins if (best_vmax >= vmax): break if self._trim: extra_bins = int(divmod((best_vmax - vmax), step)[0]) nbins -= extra_bins return (npy.arange(nbins+1) * step + best_vmin + offset)
def tick_values(self, vmin, vmax): # Max N locator will produce locations outside vmin, vmax, so even if pruned # there can be points very close to the actual bounds. Let's cut them out. # Also account for tick labels with aspect ratio > 3 (default often-violated heuristic) # - use better heuristic based on number of characters in label and typical font aspect ratio axes = self.axis.axes tick = self.axis._get_tick(True) rotation = tick._labelrotation[1] if isinstance(self.axis, YAxis): rotation += 90 ends = axes.transAxes.transform([[0, 0], [0, 1]]) length = ((ends[1][1] - ends[0][1]) / axes.figure.dpi) * 72 else: ends = axes.transAxes.transform([[0, 0], [1, 0]]) length = ((ends[1][0] - ends[0][0]) / axes.figure.dpi) * 72 size_ratio = tick.label1.get_size() / length cos_rotation = abs(math.cos(math.radians(rotation))) self._font_aspect = 0.65 * cos_rotation self._char_size_scale = size_ratio * (vmax - vmin) self._formatter = self.axis.major.formatter self._range = (vmin, vmax) # first guess if cos_rotation > 0.05: label_len = size_ratio * 1.5 * (vmax - vmin) label_space = label_len * 1.1 else: # text orthogonal to axis label_len = size_ratio * _min_label_len_chars * (vmax - vmin) label_space = label_len * 1.25 delta = label_len / 2 if self.bounded_prune else 0 nbins = int((vmax - vmin - 2 * delta) / label_space) + 1 if nbins > 4: # use more space for ticks nbins = int((vmax - vmin - 2 * delta) / ((1.5 if nbins > 6 else 1.3) * label_space)) + 1 min_n_ticks = min(nbins, 2) nbins = min(self._nbins if self._nbins != 'auto' else 9, nbins) # First get typical ticks so we can calculate actual label length while True: locs, _ = self._spaced_ticks(vmin + delta, vmax - delta, label_len, min_n_ticks, nbins, False) if len(locs) or min_n_ticks == 1: break if nbins == 2: min_n_ticks -= 1 nbins = max(min_n_ticks, 2) if cos_rotation > 0.05 and isinstance( self._formatter, ticker.ScalarFormatter) and len(locs) > 1: label_len = self._get_label_len(locs) locs = self._bounded_prune(locs, label_len) if len(locs) > 1: step = locs[1] - locs[0] # noinspection PyUnboundLocalVariable if len(locs) < max(3, nbins) or step < label_len * (1.1 if len(locs) < 4 else 1.5) \ or (locs[0] - vmin > min(step * 1.01, label_len * 1.5) or vmax - locs[-1] > min(step * 1.01, label_len * 1.5)): # check for long labels, labels that are too tightly spaced, or large tick-free gaps at axes ends delta = label_len / 2 if self.bounded_prune else 0 for fac in [1.5, 1.35, 1.1]: nbins = int( (vmax - vmin - 2 * delta) / (fac * max(2 * self._char_size_scale, label_len))) + 1 if nbins >= 4: break if self._nbins != 'auto': nbins = min(self._nbins, nbins) min_n_ticks = min(min_n_ticks, nbins) retry = True try_shorter = True locs = [] while min_n_ticks > 1: locs, good = self._spaced_ticks(vmin + delta, vmax - delta, label_len, min_n_ticks, nbins) if len(locs): if not good: new_len = self._get_label_len(locs) if not np.isclose(new_len, label_len): label_len = new_len delta = label_len / 2 if self.bounded_prune else 0 if retry: retry = False continue locs = self._bounded_prune(locs, label_len) elif min_n_ticks > 1 and try_shorter: # Original label length may be too long for good ticks which exist delta /= 2 label_len /= 2 try_shorter = False locs, _ = self._spaced_ticks(vmin + delta, vmax - delta, label_len, min_n_ticks, nbins) if len(locs): label_len = self._get_label_len(locs) delta = label_len / 2 if self.bounded_prune else 0 continue if min_n_ticks == 1 and len(locs) == 1 or len(locs) >= min_n_ticks > 1 \ and locs[1] - locs[0] > self._get_label_len(locs) * 1.1: break min_n_ticks -= 1 locs = [] if len(locs) <= 1 and size_ratio * self._font_aspect < 0.9: scale, offset = ticker.scale_range(vmin, vmax, 1) # Try to get any two points that will fit for sc in [scale, scale / 10.]: locs = [ round((vmin * 3 + vmax) / (4 * sc)) * sc, round((vmin + 3 * vmax) / (4 * sc)) * sc ] if locs[0] != locs[1] and locs[0] >= vmin and locs[ 1] <= vmax: if self._valid(locs): return locs # if no ticks, check for short integer number location in the range that may have been missed # because adding any other values would make label length much longer loc = round((vmin + vmax) / (2 * scale)) * scale if vmin < loc < vmax: locs = [loc] label_len = self._get_label_len(locs) return self._bounded_prune(locs, label_len) else: return self._bounded_prune(locs, label_len) return locs
def _spaced_ticks(self, vmin, vmax, _label_len, min_ticks, nbins, changing_lengths=True): scale, offset = ticker.scale_range(vmin, vmax, nbins) _vmin = vmin - offset _vmax = vmax - offset _range = _vmax - _vmin eps = _range * 1e-6 _full_range = self._range[1] - self._range[0] for sc in [100, 10, 1]: round_center = round( (_vmin + _vmax) / (2 * sc * scale)) * sc * scale if _vmin - eps <= round_center <= _vmax + eps: break label_len = _label_len * 1.1 raw_step = max(label_len, _range / ((nbins - 2) if nbins > 2 else 1)) raw_step1 = _range / max(1, (nbins - (0 if self.bounded_prune else 1))) best = [] best_score = -np.infty for step_ix, (_steps, _offsets) in enumerate( zip(self._step_groups, self._offsets)): steps = _steps * scale if step_ix and len(best) < 3: raw_step = max(raw_step, _range / 2) istep = min(len(steps) - 1, bisect_left(steps, raw_step)) if not istep: continue # This is an upper limit; move to smaller or half-phase steps if necessary. for off in [False, True]: if off and (len(best) > 2 or len(best) == 2 and (not round_center or step_ix > 1)): break for i in reversed(range(istep + 1)): if off and not _offsets[i]: continue step = steps[i] if step < label_len: break if step_ix and _vmin <= round_center <= _vmax: # For less nice steps, try to make them hit any round numbers in range best_vmin = round_center - ( (round_center - _vmin) // step) * step else: best_vmin = (_vmin // step) * step if off: # try half-offset steps, e.g. to get -x/2, x/2 as well as -x,0,x low = scale * _offsets[i] if best_vmin - low >= _vmin: best_vmin -= low else: best_vmin += low sc = 10**(math.log10(step) // 1) step_int = round(step / sc) low = _ge(_vmin - best_vmin, offset, step) high = _le(_vmax - best_vmin, offset, step) if min_ticks <= high - low + 1 <= nbins: ticks = np.arange(low, high + 1) * step + (best_vmin + offset) if off and round_center and changing_lengths: # If no nice number, see if we can shift points to get one if step > 2 * sc: for shift in [0, -1, 1, -2, 2]: if abs(shift * sc) >= step / 2: break shifted = ticks + shift * sc if any(np.round(shifted / sc / 10) * 10 == np.round(shifted / sc)) \ and self._valid(shifted): ticks = shifted big_step = step > raw_step1 and step > label_len * 1.5 no_more_ticks = min(3, len(ticks)) <= len(best) odd_gaps = min_ticks > 1 and ( (len(ticks) == 2 and step > _full_range * 0.7) or self.bounded_prune and ((ticks[0] - self._range[0] > max( min(_full_range / 3, step), label_len * 1.1) or self._range[1] - ticks[-1] > max( min(_full_range / 3, step), label_len * 1.1)) ) or not self.bounded_prune and len(ticks) == 3 and step > max(2 * label_len, _full_range / 3) and step_int > 1 and round(ticks[-1] / sc) % 10 > 0) close_ticks = step < label_len * 1.3 and len(ticks) > 2 if (big_step and odd_gaps or close_ticks) and no_more_ticks: continue if len( best ) and odd_gaps and step_ix or changing_lengths and not self._valid( ticks): continue too_few_points = ( len(ticks) < 3 and (nbins > (3 if step_ix else 4)) or (len(ticks) < max( 2, (nbins + 1) // 2))) and step > label_len * 1.5 _score = -1 * too_few_points - step_ix * 2 - close_ticks * 2 - odd_gaps * 1 if len(ticks) < 3 and big_step: _score -= 2 if off: _score -= 3 if step_int == 1.0 and not off: _score += 1 if 0. in steps: _score += 1 if _score <= best_score: continue if off and not step_ix or big_step and (not len(best) or len(ticks) < len(best)) \ or close_ticks or too_few_points or odd_gaps: # prefer spacing where some ticks nearish the ends and ticks not too close in centre best = ticks best_score = _score else: return ticks, True return best, False