def test_label_base_from_transform(self, x): s = Continuous(trans="log") a = PseudoAxis(s._setup(x, Coordinate())._matplotlib_scale) a.set_view_interval(10, 1000) label, = a.major.formatter.format_ticks([100]) assert r"10^{2}" in label
def test_log_tick_count(self, x): with pytest.raises(RuntimeError, match="`count` requires"): Continuous(trans="log").tick(count=4) s = Continuous(trans="log").tick(count=4, between=(1, 1000)) a = PseudoAxis(s._setup(x, Coordinate())._matplotlib_scale) a.set_view_interval(.5, 1050) assert_array_equal(a.major.locator(), [1, 10, 100, 1000])
def test_tick_upto(self, x): for n in [2, 5, 10]: s = Continuous().tick(upto=n).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) assert len(a.major.locator()) <= (n + 1)
def test_tick_count(self, x): n = 8 s = Continuous().tick(count=n).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) assert_array_equal(a.major.locator(), np.linspace(0, 1, n))
def setup_labels(self, x, *args, **kwargs): s = Continuous().label(*args, **kwargs)._setup(x, Coordinate()) a = PseudoAxis(s._matplotlib_scale) a.set_view_interval(0, 1) locs = a.major.locator() return a, locs
def test_tick_at(self, x): locs = [.2, .5, .9] s = Continuous().tick(at=locs).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) assert_array_equal(a.major.locator(), locs)
def test_tick_every(self, x): for d in [.05, .2, .5]: s = Continuous().tick(every=d).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) assert np.allclose(np.diff(a.major.locator()), d)
def test_log_tick_default(self, x): s = Continuous(trans="log")._setup(x, Coordinate()) a = PseudoAxis(s._matplotlib_scale) a.set_view_interval(.5, 1050) ticks = a.major.locator() assert np.allclose(np.diff(np.log10(ticks)), 1)
def test_tick_count_between(self, x): n = 5 lo, hi = .2, .7 s = Continuous().tick(count=n, between=(lo, hi)).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) assert_array_equal(a.major.locator(), np.linspace(lo, hi, n))
def test_tick_locator(self, x): locs = [.2, .5, .8] locator = mpl.ticker.FixedLocator(locs) s = Continuous().tick(locator).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) assert_array_equal(a.major.locator(), locs)
def test_tick_every_between(self, x): lo, hi = .2, .8 for d in [.05, .2, .5]: s = Continuous().tick(every=d, between=(lo, hi)).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) expected = np.arange(lo, hi + d, d) assert_array_equal(a.major.locator(), expected)
def test_tick_minor(self, x): n = 3 s = Continuous().tick(count=2, minor=n).setup(x, Coordinate()) a = PseudoAxis(s.matplotlib_scale) a.set_view_interval(0, 1) # I am not sure why matplotlib's minor ticks include the # largest major location but exclude the smalllest one ... expected = np.linspace(0, 1, n + 2)[1:] assert_array_equal(a.minor.locator(), expected)
def test_symlog_tick_default(self, x): s = Continuous(trans="symlog")._setup(x, Coordinate()) a = PseudoAxis(s._matplotlib_scale) a.set_view_interval(-1050, 1050) ticks = a.major.locator() assert ticks[0] == -ticks[-1] pos_ticks = np.sort(np.unique(np.abs(ticks))) assert np.allclose(np.diff(np.log10(pos_ticks[1:])), 1) assert pos_ticks[0] == 0
def infer_scale(self, arg: Any, data: Series) -> Scale: # TODO when inferring Continuous without data, verify type # TODO need to rethink the variable type system # (e.g. boolean, ordered categories as Ordinal, etc).. var_type = variable_type(data, boolean_type="categorical") if isinstance(arg, (dict, list)): return Nominal(arg) if isinstance(arg, tuple): if var_type == "categorical": # TODO It seems reasonable to allow a gradient mapping for nominal # scale but it also feels "technically" wrong. Should this infer # Ordinal with categorical data and, if so, verify orderedness? return Nominal(arg) return Continuous(arg) if callable(arg): return Continuous(arg) # TODO Do we accept str like "log", "pow", etc. for semantics? # TODO what about # - Temporal? (i.e. datetime) # - Boolean? if not isinstance(arg, str): msg = " ".join([ f"A single scale argument for {self.variable} variables must be", f"a string, dict, tuple, list, or callable, not {type(arg)}." ]) raise TypeError(msg) if arg in QUAL_PALETTES: return Nominal(arg) elif var_type == "numeric": return Continuous(arg) # TODO implement scales for date variables and any others. else: return Nominal(arg)
def test_label_type_checks(self): s = Continuous() with pytest.raises(TypeError, match="Label formatter must be"): s.label("{x}") with pytest.raises(TypeError, match="`like` must be"): s.label(like=2)
def infer_scale(self, arg: Any, data: Series) -> Scale: """Given data and a scaling argument, initialize appropriate scale class.""" # TODO infer continuous based on log/sqrt etc? if isinstance(arg, (list, dict)): return Nominal(arg) elif variable_type(data) == "categorical": return Nominal(arg) elif variable_type(data) == "datetime": return Temporal(arg) # TODO other variable types else: return Continuous(arg)
def default_scale(self, data: Series) -> Scale: """Given data, initialize appropriate scale class.""" # TODO allow variable_type to be "boolean" if that's a scale? # TODO how will this handle data with units that can be treated as numeric # if passed through a registered matplotlib converter? var_type = variable_type(data, boolean_type="numeric") if var_type == "numeric": return Continuous() elif var_type == "datetime": return Temporal() # TODO others # time-based (TimeStamp, TimeDelta, Period) # boolean scale? else: return Nominal()
def infer_scale(self, arg: Any, data: Series) -> Scale: """Given data and a scaling argument, initialize appropriate scale class.""" # TODO put these somewhere external for validation # TODO putting this here won't pick it up if subclasses define infer_scale # (e.g. color). How best to handle that? One option is to call super after # handling property-specific possibilities (e.g. for color check that the # arg is not a valid palette name) but that could get tricky. trans_args = ["log", "symlog", "logit", "pow", "sqrt"] if isinstance(arg, str): if any(arg.startswith(k) for k in trans_args): # TODO validate numeric type? That should happen centrally somewhere return Continuous(trans=arg) else: msg = f"Unknown magic arg for {self.variable} scale: '{arg}'." raise ValueError(msg) else: arg_type = type(arg).__name__ msg = f"Magic arg for {self.variable} scale must be str, not {arg_type}." raise TypeError(msg)
def test_interval_with_range_norm_and_transform(self, x): x = pd.Series([1, 10, 100]) # TODO param order? s = Continuous((2, 3), (10, 100), "log")._setup(x, IntervalProperty()) assert_array_equal(s(x), [1, 2, 3])
def test_interval_with_norm(self, x): s = Continuous(norm=(3, 7))._setup(x, IntervalProperty()) assert_array_equal(s(x), [-.5, 0, 1.5])
def test_interval_with_range(self, x): s = Continuous((1, 3))._setup(x, IntervalProperty()) assert_array_equal(s(x), [1, 1.5, 3])
def test_interval_defaults(self, x): s = Continuous()._setup(x, IntervalProperty()) assert_array_equal(s(x), [0, .25, 1])
def test_coordinate_transform_error(self, x): s = Continuous(trans="bad") with pytest.raises(ValueError, match="Unknown value provided"): s._setup(x, Coordinate())
def test_coordinate_transform_with_parameter(self, x): s = Continuous(trans="pow3")._setup(x, Coordinate()) assert_series_equal(s(x), np.power(x, 3))
def test_log_tick_every(self, x): with pytest.raises(RuntimeError, match="`every` not supported"): Continuous(trans="log").tick(every=2)
def test_coordinate_defaults(self, x): s = Continuous()._setup(x, Coordinate()) assert_series_equal(s(x), x)
def setup_ticks(self, x, *args, **kwargs): s = Continuous().tick(*args, **kwargs)._setup(x, Coordinate()) a = PseudoAxis(s._matplotlib_scale) a.set_view_interval(0, 1) return a
def test_color_defaults(self, x): cmap = color_palette("ch:", as_cmap=True) s = Continuous()._setup(x, Color()) assert_array_equal(s(x), cmap([0, .25, 1])[:, :3]) # FIXME RGBA
def test_color_named_values(self, x): cmap = color_palette("viridis", as_cmap=True) s = Continuous("viridis")._setup(x, Color()) assert_array_equal(s(x), cmap([0, .25, 1])[:, :3]) # FIXME RGBA
def test_coordinate_transform(self, x): s = Continuous(trans="log")._setup(x, Coordinate()) assert_series_equal(s(x), np.log10(x))