|
50 | 50 | :class:`LogLocator`
|
51 | 51 | Space ticks logarithmically from min to max.
|
52 | 52 |
|
| 53 | +:class:`InvLogLocator` |
| 54 | + Space ticks logarithmically from min to max, but from right to left. |
| 55 | +
|
53 | 56 | :class:`MultipleLocator`
|
54 | 57 | Ticks and range are a multiple of base; either integer or float.
|
55 | 58 |
|
|
138 | 141 | :class:`LogFormatterSciNotation`
|
139 | 142 | Format values for log axis using scientific notation.
|
140 | 143 |
|
| 144 | +:class:`InvLogFormatter` |
| 145 | + Formatter for inverted log axes. |
| 146 | +
|
141 | 147 | :class:`LogitFormatter`
|
142 | 148 | Probability formatter.
|
143 | 149 |
|
|
187 | 193 | 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter',
|
188 | 194 | 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter',
|
189 | 195 | 'LogFormatterExponent', 'LogFormatterMathtext',
|
190 |
| - 'IndexFormatter', 'LogFormatterSciNotation', |
| 196 | + 'IndexFormatter', 'LogFormatterSciNotation', 'InvLogFormatter', |
191 | 197 | 'LogitFormatter', 'EngFormatter', 'PercentFormatter',
|
192 | 198 | 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator',
|
193 |
| - 'LinearLocator', 'LogLocator', 'AutoLocator', |
| 199 | + 'LinearLocator', 'LogLocator', 'InvLogLocator', 'AutoLocator', |
194 | 200 | 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator',
|
195 | 201 | 'SymmetricalLogLocator', 'LogitLocator')
|
196 | 202 |
|
@@ -1150,6 +1156,95 @@ def _non_decade_format(self, sign_string, base, fx, usetex):
|
1150 | 1156 | (sign_string, coeff, base, exponent)))
|
1151 | 1157 |
|
1152 | 1158 |
|
| 1159 | +class InvLogFormatter(LogFormatter): |
| 1160 | + """ |
| 1161 | + Base class for formatting ticks on an inverted log scale. Being a |
| 1162 | + subclass of LogFormatter, it inherits most of its attributes, methods and |
| 1163 | + instantiation parameters (base, labelOnlyBase, minor_thresholds and |
| 1164 | + linthresh; see LogFormatter documentation for more on those). Note that |
| 1165 | + symlog mode does not work. |
| 1166 | +
|
| 1167 | + It may be instantiated directly, or subclassed. |
| 1168 | +
|
| 1169 | + Parameters |
| 1170 | + ---------- |
| 1171 | + inv_base : float, optional, default: 10. |
| 1172 | + Base of the logarithm used in all calculations for the inverse scale. |
| 1173 | +
|
| 1174 | + inv_factor : float, optional, default: 1. |
| 1175 | + Multiply the data on the inverted axis by this number. This can be |
| 1176 | + useful when one wants to compare data on a logarithmic axis ``x`` not |
| 1177 | + to the reciprocal axis ``1/x``, but rather to an axis ``inv_factor/x``. |
| 1178 | + This is common for instance in Fourier analysis where the inverse axis |
| 1179 | + could be ``2*np.pi/x`` (depending on the choice of frequency/scale |
| 1180 | + space coordinates). |
| 1181 | +
|
| 1182 | + Examples |
| 1183 | + -------- |
| 1184 | + To label a subset of minor ticks when the view limits span up |
| 1185 | + to 2 decades, and all of the ticks when zoomed in to 0.5 decades |
| 1186 | + or less, use ``minor_thresholds=(2, 0.5)``. |
| 1187 | +
|
| 1188 | + To label all minor ticks when the view limits span up to 1.5 |
| 1189 | + decades, use ``minor_thresholds=(1.5, 1.5)``. |
| 1190 | +
|
| 1191 | + """ |
| 1192 | + def __init__(self, inv_base=10.0, inv_factor=1., **kwargs): |
| 1193 | + """ |
| 1194 | + *base* kwarg is used to locate the decade tick, |
| 1195 | + which will be the only one to be labeled if *labelOnlyBase* |
| 1196 | + is ``False`` |
| 1197 | + """ |
| 1198 | + super(InvLogFormatter, self).__init__(**kwargs) |
| 1199 | + self.inv_base(inv_base) |
| 1200 | + self.inv_factor(inv_factor) |
| 1201 | + |
| 1202 | + def inv_base(self, inv_base): |
| 1203 | + """ |
| 1204 | + set the base of the log scaling (major tick every base**i, i integer) |
| 1205 | + for the inverse of the data - warning: should always match the |
| 1206 | + base used for :class:`InvLogLocator`. |
| 1207 | + """ |
| 1208 | + self._inv_base = inv_base + 0.0 |
| 1209 | + |
| 1210 | + def inv_factor(self, inv_factor): |
| 1211 | + """ |
| 1212 | + After inverting the data, it can be multiplied by this factor. This |
| 1213 | + way, any inverse logarithmic quantity can be computed from a |
| 1214 | + logarithmic input quantity - warning: should always match the base used |
| 1215 | + for :class:`InvLogLocator`. |
| 1216 | + """ |
| 1217 | + self._inv_factor = inv_factor + 0.0 |
| 1218 | + |
| 1219 | + def __call__(self, x, pos=None): |
| 1220 | + """ |
| 1221 | + Return the format for tick val `x`. |
| 1222 | + """ |
| 1223 | + ix = abs(self._inv_factor / x) |
| 1224 | + |
| 1225 | + if ix == 0.0: # Symlog |
| 1226 | + return '0' |
| 1227 | + |
| 1228 | + ib = self._inv_base |
| 1229 | + # only label the decades |
| 1230 | + fx = math.log(ix) / math.log(ib) |
| 1231 | + is_ix_decade = is_close_to_int(fx) |
| 1232 | + exponent = np.round(fx) if is_ix_decade else np.floor(fx) |
| 1233 | + coeff = np.round(ix / ib ** exponent) |
| 1234 | + |
| 1235 | + if self.labelOnlyBase and not is_ix_decade: |
| 1236 | + return '' |
| 1237 | + if self._sublabels is not None and coeff not in self._sublabels: |
| 1238 | + return '' |
| 1239 | + |
| 1240 | + vmin, vmax = self.axis.get_view_interval() |
| 1241 | + # N.B.: max and min swapped |
| 1242 | + ivmax = self._inv_factor / vmin |
| 1243 | + ivmin = self._inv_factor / vmax |
| 1244 | + s = self._num_to_string(ix, ivmin, ivmax) |
| 1245 | + return self.fix_minus(s) |
| 1246 | + |
| 1247 | + |
1153 | 1248 | class LogitFormatter(Formatter):
|
1154 | 1249 | """
|
1155 | 1250 | Probability formatter (using Math text).
|
@@ -2377,6 +2472,142 @@ def view_limits(self, vmin, vmax):
|
2377 | 2472 | return result
|
2378 | 2473 |
|
2379 | 2474 |
|
| 2475 | +class InvLogLocator(LogLocator): |
| 2476 | + """ |
| 2477 | + Determine the tick locations for inverse log axes. Being a subclass of |
| 2478 | + LogLocator, it shares most of its characteristics, except for two added |
| 2479 | + instantiation keyword arguments: inv_base and inv_factor. See the docstring |
| 2480 | + of InvLogFormatter for a description of these parameters. |
| 2481 | + """ |
| 2482 | + |
| 2483 | + def __init__(self, inv_base=10.0, inv_factor=1., **kwargs): |
| 2484 | + """ |
| 2485 | + place ticks on the location= inv_base**i*subs[j] |
| 2486 | + Set subs to None for autosub (for minor locator). |
| 2487 | + """ |
| 2488 | + super(InvLogLocator, self).__init__(**kwargs) |
| 2489 | + self.inv_base(inv_base) |
| 2490 | + self.inv_factor(inv_factor) |
| 2491 | + |
| 2492 | + def set_params(self, inv_base=None, inv_factor=None, **kwargs): |
| 2493 | + """Set parameters within this locator.""" |
| 2494 | + super(InvLogLocator, self).set_params(**kwargs) |
| 2495 | + if inv_base is not None: |
| 2496 | + self.inv_base = inv_base |
| 2497 | + if inv_factor is not None: |
| 2498 | + self.inv_factor = inv_factor |
| 2499 | + |
| 2500 | + def inv_base(self, inv_base): |
| 2501 | + """ |
| 2502 | + set the base of the log scaling (major tick every base**i, i integer) |
| 2503 | + for the inverse of the data |
| 2504 | + """ |
| 2505 | + self._inv_base = inv_base + 0.0 |
| 2506 | + |
| 2507 | + def inv_factor(self, inv_factor): |
| 2508 | + """ |
| 2509 | + After inverting the data, it can be multiplied by this factor. This |
| 2510 | + way, any inverse logarithmic quantity can be computed from a |
| 2511 | + logarithmic input quantity. |
| 2512 | + """ |
| 2513 | + self._inv_factor = inv_factor + 0.0 |
| 2514 | + |
| 2515 | + def __call__(self): |
| 2516 | + 'Return the locations of the ticks' |
| 2517 | + vmin, vmax = self.axis.get_view_interval() |
| 2518 | + return self.tick_values(vmin, vmax) |
| 2519 | + |
| 2520 | + def tick_values(self, vmin, vmax): |
| 2521 | + if self.numticks == 'auto': |
| 2522 | + if self.axis is not None: |
| 2523 | + numticks = np.clip(self.axis.get_tick_space(), 2, 9) |
| 2524 | + else: |
| 2525 | + numticks = 9 |
| 2526 | + else: |
| 2527 | + numticks = self.numticks |
| 2528 | + |
| 2529 | + ib = self._inv_base |
| 2530 | + # max and min swapped |
| 2531 | + ivmax = self._inv_factor / vmin |
| 2532 | + ivmin = self._inv_factor / vmax |
| 2533 | + # dummy axis has no axes attribute |
| 2534 | + if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar': |
| 2535 | + ivmax = math.ceil(math.log(ivmax) / math.log(ib)) |
| 2536 | + decades = np.arange(ivmax - self.numdecs, ivmax) |
| 2537 | + i_ticklocs = ib ** decades |
| 2538 | + |
| 2539 | + ticklocs = self._inv_factor / i_ticklocs |
| 2540 | + |
| 2541 | + return ticklocs |
| 2542 | + |
| 2543 | + if ivmax <= 0.0: |
| 2544 | + if self.axis is not None: |
| 2545 | + ivmax = self._inv_factor / self.axis.get_minpos() |
| 2546 | + |
| 2547 | + if ivmax <= 0.0 or not np.isfinite(ivmax): |
| 2548 | + raise ValueError( |
| 2549 | + "Data has no positive values, and therefore can not be " |
| 2550 | + "log-scaled.") |
| 2551 | + |
| 2552 | + ivmin = math.log(ivmin) / math.log(ib) |
| 2553 | + ivmax = math.log(ivmax) / math.log(ib) |
| 2554 | + |
| 2555 | + if ivmax < ivmin: |
| 2556 | + ivmin, ivmax = ivmax, ivmin |
| 2557 | + |
| 2558 | + numdec = math.floor(ivmax) - math.ceil(ivmin) |
| 2559 | + |
| 2560 | + if isinstance(self._subs, six.string_types): |
| 2561 | + _first = 2.0 if self._subs == 'auto' else 1.0 |
| 2562 | + if numdec > 10 or ib < 3: |
| 2563 | + if self._subs == 'auto': |
| 2564 | + return np.array([]) # no minor or major ticks |
| 2565 | + else: |
| 2566 | + subs = np.array([1.0]) # major ticks |
| 2567 | + else: |
| 2568 | + subs = np.arange(_first, ib) |
| 2569 | + else: |
| 2570 | + subs = self._subs |
| 2571 | + |
| 2572 | + # get decades between major ticks. |
| 2573 | + stride = 1 |
| 2574 | + if rcParams['_internal.classic_mode']: |
| 2575 | + # Leave the bug left over from the PY2-PY3 transition. |
| 2576 | + while numdec / stride + 1 > numticks: |
| 2577 | + stride += 1 |
| 2578 | + else: |
| 2579 | + while numdec // stride + 1 > numticks: |
| 2580 | + stride += 1 |
| 2581 | + |
| 2582 | + # Does subs include anything other than 1? |
| 2583 | + have_subs = len(subs) > 1 or (len(subs == 1) and subs[0] != 1.0) |
| 2584 | + |
| 2585 | + decades = np.arange(math.floor(ivmin) - stride, |
| 2586 | + math.ceil(ivmax) + 2 * stride, stride) |
| 2587 | + if hasattr(self, '_transform'): |
| 2588 | + i_ticklocs = self._transform.inverted().transform(decades) |
| 2589 | + if have_subs: |
| 2590 | + if stride == 1: |
| 2591 | + i_ticklocs = np.ravel(np.outer(subs, i_ticklocs)) |
| 2592 | + else: |
| 2593 | + # no ticklocs if we have more than one decade |
| 2594 | + # between major ticks. |
| 2595 | + i_ticklocs = [] |
| 2596 | + else: |
| 2597 | + if have_subs: |
| 2598 | + i_ticklocs = [] |
| 2599 | + if stride == 1: |
| 2600 | + for decadeStart in ib ** decades: |
| 2601 | + i_ticklocs.extend(subs * decadeStart) |
| 2602 | + else: |
| 2603 | + i_ticklocs = ib ** decades |
| 2604 | + |
| 2605 | + ticklocs = self._inv_factor / np.asarray(i_ticklocs) |
| 2606 | + |
| 2607 | + _log.debug('ticklocs %r', ticklocs) |
| 2608 | + return self.raise_if_exceeds(np.asarray(ticklocs)) |
| 2609 | + |
| 2610 | + |
2380 | 2611 | class LogitLocator(Locator):
|
2381 | 2612 | """
|
2382 | 2613 | Determine the tick locations for logit axes
|
|
0 commit comments