From 081387488d4a65df4322880b74bf87a1711e252c Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:38:31 +0100 Subject: [PATCH 01/15] fix: Use absolute imports explicitly Fixes the mypy issue referenced in `alt.__init__.py` --- altair/__init__.py | 13 ++++--------- altair/vegalite/v5/__init__.py | 2 +- tests/vegalite/v5/test_api.py | 4 +++- tests/vegalite/v5/test_params.py | 4 +++- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/altair/__init__.py b/altair/__init__.py index bd81f0209..53115f5bb 100644 --- a/altair/__init__.py +++ b/altair/__init__.py @@ -1,12 +1,6 @@ # ruff: noqa __version__ = "5.4.0dev" -from typing import Any - -# Necessary as mypy would see expr as the module alt.expr although due to how -# the imports are set up it is expr in the alt.expr module -expr: Any - # The content of __all__ is automatically written by # tools/update_init_file.py. Do not modify directly. @@ -617,11 +611,12 @@ def __dir__(): return __all__ -from .vegalite import * -from .jupyter import JupyterChart +from altair.vegalite import * +from altair.jupyter import JupyterChart +from altair.expr import expr def load_ipython_extension(ipython): - from ._magics import vegalite + from altair._magics import vegalite ipython.register_magic_function(vegalite, "cell") diff --git a/altair/vegalite/v5/__init__.py b/altair/vegalite/v5/__init__.py index f921d662e..2801df33c 100644 --- a/altair/vegalite/v5/__init__.py +++ b/altair/vegalite/v5/__init__.py @@ -2,7 +2,7 @@ from .schema import * from .api import * -from ...expr import datum, expr +from altair.expr.core import datum from .display import VegaLite, renderers from .compiler import vegalite_compilers diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py index a7197e0b1..811db9759 100644 --- a/tests/vegalite/v5/test_api.py +++ b/tests/vegalite/v5/test_api.py @@ -275,12 +275,14 @@ def test_selection_to_dict(): def test_selection_expression(): + from altair.expr.core import Expression + selection = alt.selection_point(fields=["value"]) assert isinstance(selection.value, alt.SelectionExpression) assert selection.value.to_dict() == {"expr": f"{selection.name}.value"} - assert isinstance(selection["value"], alt.expr.Expression) + assert isinstance(selection["value"], Expression) assert selection["value"].to_dict() == f"{selection.name}['value']" magic_attr = "__magic__" diff --git a/tests/vegalite/v5/test_params.py b/tests/vegalite/v5/test_params.py index d9f61b8c9..b88d3fb36 100644 --- a/tests/vegalite/v5/test_params.py +++ b/tests/vegalite/v5/test_params.py @@ -117,13 +117,15 @@ def test_parameter_naming(): def test_selection_expression(): + from altair.expr.core import Expression + data = pd.DataFrame([{"a": "A", "b": 28}]) sel = alt.selection_point(fields=["b"]) se = sel.b | 300 assert isinstance(se, alt.SelectionExpression) - assert isinstance(se.expr, alt.expr.core.Expression) + assert isinstance(se.expr, Expression) c = ( alt.Chart(data) From bcb8f1d9376675ee412b74553ad7b57c7a68677f Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:41:44 +0100 Subject: [PATCH 02/15] refactor: remove dynamic globals manipulation The results of `_populate_namespace` were only visible in a `IPython` environment --- altair/expr/consts.py | 15 --------------- altair/expr/core.py | 5 ++--- altair/expr/funcs.py | 30 ------------------------------ 3 files changed, 2 insertions(+), 48 deletions(-) diff --git a/altair/expr/consts.py b/altair/expr/consts.py index 4e778c069..b90c93d4f 100644 --- a/altair/expr/consts.py +++ b/altair/expr/consts.py @@ -1,7 +1,5 @@ from __future__ import annotations -from .core import ConstExpression - CONST_LISTING = { "NaN": "not a number (same as JavaScript literal NaN)", @@ -14,16 +12,3 @@ "SQRT2": "the square root of 2 (alias to Math.SQRT1_2)", "PI": "the transcendental number pi (alias to Math.PI)", } - -NAME_MAP: dict[str, str] = {} - - -def _populate_namespace(): - globals_ = globals() - for name, doc in CONST_LISTING.items(): - py_name = NAME_MAP.get(name, name) - globals_[py_name] = ConstExpression(name, doc) - yield py_name - - -__all__ = list(_populate_namespace()) diff --git a/altair/expr/core.py b/altair/expr/core.py index 986501178..5815598fe 100644 --- a/altair/expr/core.py +++ b/altair/expr/core.py @@ -210,9 +210,8 @@ def __repr__(self): class ConstExpression(Expression): - def __init__(self, name, doc) -> None: - self.__doc__ = f"""{name}: {doc}""" - super().__init__(name=name, doc=doc) + def __init__(self, name) -> None: + super().__init__(name=name) def __repr__(self) -> str: return str(self.name) diff --git a/altair/expr/funcs.py b/altair/expr/funcs.py index e252a1b36..d8a96d465 100644 --- a/altair/expr/funcs.py +++ b/altair/expr/funcs.py @@ -1,10 +1,4 @@ from __future__ import annotations -from typing import TYPE_CHECKING -from .core import FunctionExpression - -if TYPE_CHECKING: - from typing import Iterator - FUNCTION_LISTING = { "isArray": r"Returns true if _value_ is an array, false otherwise.", @@ -171,27 +165,3 @@ # This maps vega expression function names to the Python name NAME_MAP = {"if": "if_"} - - -class ExprFunc: - def __init__(self, name, doc) -> None: - self.name = name - self.doc = doc - self.__doc__ = f"""{name}(*args)\n {doc}""" - - def __call__(self, *args) -> FunctionExpression: - return FunctionExpression(self.name, args) - - def __repr__(self) -> str: - return f"" - - -def _populate_namespace() -> Iterator[str]: - globals_ = globals() - for name, doc in FUNCTION_LISTING.items(): - py_name = NAME_MAP.get(name, name) - globals_[py_name] = ExprFunc(name, doc) - yield py_name - - -__all__ = list(_populate_namespace()) From ec3293851497c90ff4cc60e23c84f63e98b099c7 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:39:59 +0100 Subject: [PATCH 03/15] feat: Reimplement `expr` as a class w/ identicial semantics as the singleton - all `ConstExpression`s are now read-only properties stored in a metaclass - `expr` subclasses `ExprRef`, replacing the `__call__` w/ `__new__` - No instances of `expr` are ever created - All `FunctionExpression`s are now classmethods - Each `Expression` can now be referenced in autocomplete, without needing `IPython`/`Jupyter` - Docstrings have been reformatted to be compatible with `sphinx` - Broken links in docstrings fixed (e.g. for d3) - class-level doc for `expr`, with references & doctest --- altair/expr/__init__.py | 1185 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 1173 insertions(+), 12 deletions(-) diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py index 64916fe2b..54e43b790 100644 --- a/altair/expr/__init__.py +++ b/altair/expr/__init__.py @@ -1,20 +1,1181 @@ """Tools for creating transform & filter expressions with a python syntax""" -# ruff: noqa -from typing import Any +from __future__ import annotations -from .core import datum, Expression -from .funcs import * -from .consts import * -from ..vegalite.v5.schema.core import ExprRef as _ExprRef +import sys +from altair.expr.core import ConstExpression, FunctionExpression +from altair.vegalite.v5.schema.core import ExprRef as _ExprRef -class _ExprType: - def __init__(self, expr): - vars(self).update(expr) +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override - def __call__(self, expr, **kwargs): - return _ExprRef(expr, **kwargs) +class _ConstExpressionType(type): + @property + def NaN(cls) -> ConstExpression: + "not a number (same as JavaScript literal NaN)" + return ConstExpression("NaN") -expr: Any = _ExprType(globals()) + @property + def LN10(cls) -> ConstExpression: + """the natural log of 10 (alias to Math.LN10)""" + return ConstExpression("LN10") + + @property + def E(cls) -> ConstExpression: + """the transcendental number e (alias to Math.E)""" + return ConstExpression("E") + + @property + def LOG10E(cls) -> ConstExpression: + """the base 10 logarithm e (alias to Math.LOG10E)""" + return ConstExpression("LOG10E") + + @property + def LOG2E(cls) -> ConstExpression: + """the base 2 logarithm of e (alias to Math.LOG2E)""" + return ConstExpression("LOG2E") + + @property + def SQRT1_2(cls) -> ConstExpression: + """the square root of 0.5 (alias to Math.SQRT1_2)""" + return ConstExpression("SQRT1_2") + + @property + def LN2(cls) -> ConstExpression: + """the natural log of 2 (alias to Math.LN2)""" + return ConstExpression("LN2") + + @property + def SQRT2(cls) -> ConstExpression: + """the square root of 2 (alias to Math.SQRT1_2)""" + return ConstExpression("SQRT2") + + @property + def PI(cls) -> ConstExpression: + """the transcendental number pi (alias to Math.PI)""" + return ConstExpression("PI") + + +class expr(_ExprRef, metaclass=_ConstExpressionType): + r"""Utility providing *constants* and *classmethods* to construct expressions. + + `Expressions`_ can used to write basic formulas that enable custom interactions. + + Alternatively, an `inline expression`_ may be defined via :class:`expr()`. + + Parameters + ---------- + expr: str + A `vega expression`_ string. + + Returns + ------- + ``ExprRef`` + + .. _Expressions: + https://altair-viz.github.io/user_guide/interactions.html#expressions + .. _inline expression: + https://altair-viz.github.io/user_guide/interactions.html#inline-expressions + .. _vega expression: + https://vega.github.io/vega/docs/expressions/ + + Examples + -------- + >>> import altair as alt + >>> bind_range = alt.binding_range(min=100, max=300, name='Slider value: ') + >>> param_width = alt.param(bind=bind_range) + >>> param_color = alt.param(expr=alt.expr.if_(param_width < 200, 'red', 'black')) + >>> y = alt.Y("yval").axis(titleColor=param_color) + """ + + @override + def __new__(cls: type[_ExprRef], expr: str) -> _ExprRef: # type: ignore[misc] + # NOTE: `mypy<=1.10.1` is not consistent with typing spec + # https://github.com/python/mypy/issues/1020 + # https://docs.python.org/3/reference/datamodel.html#object.__new__ + # https://typing.readthedocs.io/en/latest/spec/constructors.html#new-method + return _ExprRef(expr=expr) + + @classmethod + def if_(cls, *args) -> FunctionExpression: + """If *test* is truthy, returns *thenValue*. Otherwise, returns *elseValue*. + + The *if* function is equivalent to the ternary operator `a ? b : c`.""" + return FunctionExpression("if", args) + + @classmethod + def isArray(cls, *args) -> FunctionExpression: + """Returns true if *value* is an array, false otherwise.""" + return FunctionExpression("isArray", args) + + @classmethod + def isBoolean(cls, *args) -> FunctionExpression: + """Returns true if *value* is a boolean (`true` or `false`), false otherwise.""" + return FunctionExpression("isBoolean", args) + + @classmethod + def isDate(cls, *args) -> FunctionExpression: + """Returns true if *value* is a Date object, false otherwise. + + This method will return false for timestamp numbers or date-formatted strings; it recognizes Date objects only.""" + return FunctionExpression("isDate", args) + + @classmethod + def isDefined(cls, *args) -> FunctionExpression: + """Returns true if *value* is a defined value, false if *value* equals `undefined`. + + This method will return true for `null` and `NaN` values.""" + return FunctionExpression("isDefined", args) + + @classmethod + def isNumber(cls, *args) -> FunctionExpression: + """Returns true if *value* is a number, false otherwise. + + `NaN` and `Infinity` are considered numbers.""" + return FunctionExpression("isNumber", args) + + @classmethod + def isObject(cls, *args) -> FunctionExpression: + """Returns true if *value* is an object (including arrays and Dates), false otherwise.""" + return FunctionExpression("isObject", args) + + @classmethod + def isRegExp(cls, *args) -> FunctionExpression: + """Returns true if *value* is a RegExp (regular expression) object, false otherwise.""" + return FunctionExpression("isRegExp", args) + + @classmethod + def isString(cls, *args) -> FunctionExpression: + """Returns true if *value* is a string, false otherwise.""" + return FunctionExpression("isString", args) + + @classmethod + def isValid(cls, *args) -> FunctionExpression: + """Returns true if *value* is not `null`, `undefined`, or `NaN`, false otherwise.""" + return FunctionExpression("isValid", args) + + @classmethod + def toBoolean(cls, *args) -> FunctionExpression: + """Coerces the input *value* to a string. + + Null values and empty strings are mapped to `null`.""" + return FunctionExpression("toBoolean", args) + + @classmethod + def toDate(cls, *args) -> FunctionExpression: + """Coerces the input *value* to a Date instance. + + Null values and empty strings are mapped to `null`. + If an optional *parser* function is provided, it is used to perform date parsing, otherwise `Date.parse` is used. + Be aware that `Date.parse` has different implementations across browsers!""" + return FunctionExpression("toDate", args) + + @classmethod + def toNumber(cls, *args) -> FunctionExpression: + """Coerces the input *value* to a number. + + Null values and empty strings are mapped to `null`.""" + return FunctionExpression("toNumber", args) + + @classmethod + def toString(cls, *args) -> FunctionExpression: + """Coerces the input *value* to a string. + + Null values and empty strings are mapped to `null`.""" + return FunctionExpression("toString", args) + + @classmethod + def isNaN(cls, *args) -> FunctionExpression: + """Returns true if *value* is not a number. + + Same as JavaScript's `isNaN`.""" + return FunctionExpression("isNaN", args) + + @classmethod + def isFinite(cls, *args) -> FunctionExpression: + """Returns true if *value* is a finite number. + + Same as JavaScript's `isFinite`.""" + return FunctionExpression("isFinite", args) + + @classmethod + def abs(cls, *args) -> FunctionExpression: + """Returns the absolute value of *value*. + + Same as JavaScript's `Math.abs`.""" + return FunctionExpression("abs", args) + + @classmethod + def acos(cls, *args) -> FunctionExpression: + """Trigonometric arccosine. + + Same as JavaScript's `Math.acos`.""" + return FunctionExpression("acos", args) + + @classmethod + def asin(cls, *args) -> FunctionExpression: + """Trigonometric arcsine. + + Same as JavaScript's `Math.asin`.""" + return FunctionExpression("asin", args) + + @classmethod + def atan(cls, *args) -> FunctionExpression: + """Trigonometric arctangent. + + Same as JavaScript's `Math.atan`.""" + return FunctionExpression("atan", args) + + @classmethod + def atan2(cls, *args) -> FunctionExpression: + """Returns the arctangent of *dy / dx*. + + Same as JavaScript's `Math.atan2`.""" + return FunctionExpression("atan2", args) + + @classmethod + def ceil(cls, *args) -> FunctionExpression: + """Rounds *value* to the nearest integer of equal or greater value. + + Same as JavaScript's `Math.ceil`.""" + return FunctionExpression("ceil", args) + + @classmethod + def clamp(cls, *args) -> FunctionExpression: + """Restricts *value* to be between the specified *min* and *max*.""" + return FunctionExpression("clamp", args) + + @classmethod + def cos(cls, *args) -> FunctionExpression: + """Trigonometric cosine. + + Same as JavaScript's `Math.cos`.""" + return FunctionExpression("cos", args) + + @classmethod + def exp(cls, *args) -> FunctionExpression: + """Returns the value of *e* raised to the provided *exponent*. + + Same as JavaScript's `Math.exp`.""" + return FunctionExpression("exp", args) + + @classmethod + def floor(cls, *args) -> FunctionExpression: + """Rounds *value* to the nearest integer of equal or lower value. + + Same as JavaScript's `Math.floor`.""" + return FunctionExpression("floor", args) + + @classmethod + def hypot(cls, *args) -> FunctionExpression: + """Returns the square root of the sum of squares of its arguments. + + Same as JavaScript's `Math.hypot`.""" + return FunctionExpression("hypot", args) + + @classmethod + def log(cls, *args) -> FunctionExpression: + """Returns the natural logarithm of *value*. + + Same as JavaScript's `Math.log`.""" + return FunctionExpression("log", args) + + @classmethod + def max(cls, *args) -> FunctionExpression: + """Returns the maximum argument value. + + Same as JavaScript's `Math.max`.""" + return FunctionExpression("max", args) + + @classmethod + def min(cls, *args) -> FunctionExpression: + """Returns the minimum argument value. + + Same as JavaScript's `Math.min`.""" + return FunctionExpression("min", args) + + @classmethod + def pow(cls, *args) -> FunctionExpression: + """Returns *value* raised to the given *exponent*. + + Same as JavaScript's `Math.pow`.""" + return FunctionExpression("pow", args) + + @classmethod + def random(cls, *args) -> FunctionExpression: + """Returns a pseudo-random number in the range [0,1). + + Same as JavaScript's `Math.random`.""" + return FunctionExpression("random", args) + + @classmethod + def round(cls, *args) -> FunctionExpression: + """Rounds *value* to the nearest integer. + + Same as JavaScript's `Math.round`.""" + return FunctionExpression("round", args) + + @classmethod + def sin(cls, *args) -> FunctionExpression: + """Trigonometric sine. + + Same as JavaScript's `Math.sin`.""" + return FunctionExpression("sin", args) + + @classmethod + def sqrt(cls, *args) -> FunctionExpression: + """Square root function. + + Same as JavaScript's `Math.sqrt`.""" + return FunctionExpression("sqrt", args) + + @classmethod + def tan(cls, *args) -> FunctionExpression: + """Trigonometric tangent. + + Same as JavaScript's `Math.tan`.""" + return FunctionExpression("tan", args) + + @classmethod + def sampleNormal(cls, *args) -> FunctionExpression: + """Returns a sample from a univariate `normal (Gaussian) probability distribution `__ with specified *mean* and standard deviation *stdev*. + + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + return FunctionExpression("sampleNormal", args) + + @classmethod + def cumulativeNormal(cls, *args) -> FunctionExpression: + """Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a normal distribution with specified *mean* and standard deviation *stdev*. + + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + return FunctionExpression("cumulativeNormal", args) + + @classmethod + def densityNormal(cls, *args) -> FunctionExpression: + """Returns the value of the `probability density function `__ at the given input domain *value*, for a normal distribution with specified *mean* and standard deviation *stdev*. + + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + return FunctionExpression("densityNormal", args) + + @classmethod + def quantileNormal(cls, *args) -> FunctionExpression: + """Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a normal distribution with specified *mean* and standard deviation *stdev*. + + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + return FunctionExpression("quantileNormal", args) + + @classmethod + def sampleLogNormal(cls, *args) -> FunctionExpression: + """Returns a sample from a univariate `log-normal probability distribution `__ with specified log *mean* and log standard deviation *stdev*. + + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + return FunctionExpression("sampleLogNormal", args) + + @classmethod + def cumulativeLogNormal(cls, *args) -> FunctionExpression: + """Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. + + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + return FunctionExpression("cumulativeLogNormal", args) + + @classmethod + def densityLogNormal(cls, *args) -> FunctionExpression: + """Returns the value of the `probability density function `__ at the given input domain *value*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. + + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + return FunctionExpression("densityLogNormal", args) + + @classmethod + def quantileLogNormal(cls, *args) -> FunctionExpression: + """Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. + + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + return FunctionExpression("quantileLogNormal", args) + + @classmethod + def sampleUniform(cls, *args) -> FunctionExpression: + """Returns a sample from a univariate `continuous uniform probability distribution `__ over the interval *[min, max]*. + + If unspecified, *min* defaults to `0` and *max* defaults to `1`. + If only one argument is provided, it is interpreted as the *max* value.""" + return FunctionExpression("sampleUniform", args) + + @classmethod + def cumulativeUniform(cls, *args) -> FunctionExpression: + """Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a uniform distribution over the interval *[min, max]*. + + If unspecified, *min* defaults to `0` and *max* defaults to `1`. + If only one argument is provided, it is interpreted as the *max* value.""" + return FunctionExpression("cumulativeUniform", args) + + @classmethod + def densityUniform(cls, *args) -> FunctionExpression: + """Returns the value of the `probability density function `__ at the given input domain *value*, for a uniform distribution over the interval *[min, max]*. + + If unspecified, *min* defaults to `0` and *max* defaults to `1`. + If only one argument is provided, it is interpreted as the *max* value.""" + return FunctionExpression("densityUniform", args) + + @classmethod + def quantileUniform(cls, *args) -> FunctionExpression: + """Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a uniform distribution over the interval *[min, max]*. + + If unspecified, *min* defaults to `0` and *max* defaults to `1`. + If only one argument is provided, it is interpreted as the *max* value.""" + return FunctionExpression("quantileUniform", args) + + @classmethod + def now(cls, *args) -> FunctionExpression: + """Returns the timestamp for the current time.""" + return FunctionExpression("now", args) + + @classmethod + def datetime(cls, *args) -> FunctionExpression: + """Returns a new `Date` instance. + + The *month* is 0-based, such that `1` represents February.""" + return FunctionExpression("datetime", args) + + @classmethod + def date(cls, *args) -> FunctionExpression: + """Returns the day of the month for the given *datetime* value, in local time.""" + return FunctionExpression("date", args) + + @classmethod + def day(cls, *args) -> FunctionExpression: + """Returns the day of the week for the given *datetime* value, in local time.""" + return FunctionExpression("day", args) + + @classmethod + def dayofyear(cls, *args) -> FunctionExpression: + """Returns the one-based day of the year for the given *datetime* value, in local time.""" + return FunctionExpression("dayofyear", args) + + @classmethod + def year(cls, *args) -> FunctionExpression: + """Returns the year for the given *datetime* value, in local time.""" + return FunctionExpression("year", args) + + @classmethod + def quarter(cls, *args) -> FunctionExpression: + """Returns the quarter of the year (0-3) for the given *datetime* value, in local time.""" + return FunctionExpression("quarter", args) + + @classmethod + def month(cls, *args) -> FunctionExpression: + """Returns the (zero-based) month for the given *datetime* value, in local time.""" + return FunctionExpression("month", args) + + @classmethod + def week(cls, *args) -> FunctionExpression: + """Returns the week number of the year for the given *datetime*, in local time. + + This function assumes Sunday-based weeks. + Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._.""" + return FunctionExpression("week", args) + + @classmethod + def hours(cls, *args) -> FunctionExpression: + """Returns the hours component for the given *datetime* value, in local time.""" + return FunctionExpression("hours", args) + + @classmethod + def minutes(cls, *args) -> FunctionExpression: + """Returns the minutes component for the given *datetime* value, in local time.""" + return FunctionExpression("minutes", args) + + @classmethod + def seconds(cls, *args) -> FunctionExpression: + """Returns the seconds component for the given *datetime* value, in local time.""" + return FunctionExpression("seconds", args) + + @classmethod + def milliseconds(cls, *args) -> FunctionExpression: + """Returns the milliseconds component for the given *datetime* value, in local time.""" + return FunctionExpression("milliseconds", args) + + @classmethod + def time(cls, *args) -> FunctionExpression: + """Returns the epoch-based timestamp for the given *datetime* value.""" + return FunctionExpression("time", args) + + @classmethod + def timezoneoffset(cls, *args) -> FunctionExpression: + """Returns the timezone offset from the local timezone to UTC for the given *datetime* value.""" + return FunctionExpression("timezoneoffset", args) + + @classmethod + def timeOffset(cls, *args) -> FunctionExpression: + """Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in the local timezone. + + The optional *step* argument indicates the number of time unit steps to offset by (default 1).""" + return FunctionExpression("timeOffset", args) + + @classmethod + def timeSequence(cls, *args) -> FunctionExpression: + """Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in the local timezone. + + The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).""" + return FunctionExpression("timeSequence", args) + + @classmethod + def utc(cls, *args) -> FunctionExpression: + """Returns a timestamp for the given UTC date. The *month* is 0-based, such that `1` represents February.""" + return FunctionExpression("utc", args) + + @classmethod + def utcdate(cls, *args) -> FunctionExpression: + """Returns the day of the month for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcdate", args) + + @classmethod + def utcday(cls, *args) -> FunctionExpression: + """Returns the day of the week for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcday", args) + + @classmethod + def utcdayofyear(cls, *args) -> FunctionExpression: + """Returns the one-based day of the year for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcdayofyear", args) + + @classmethod + def utcyear(cls, *args) -> FunctionExpression: + """Returns the year for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcyear", args) + + @classmethod + def utcquarter(cls, *args) -> FunctionExpression: + """Returns the quarter of the year (0-3) for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcquarter", args) + + @classmethod + def utcmonth(cls, *args) -> FunctionExpression: + """Returns the (zero-based) month for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcmonth", args) + + @classmethod + def utcweek(cls, *args) -> FunctionExpression: + """Returns the week number of the year for the given *datetime*, in UTC time. + + This function assumes Sunday-based weeks. + Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._.""" + return FunctionExpression("utcweek", args) + + @classmethod + def utchours(cls, *args) -> FunctionExpression: + """Returns the hours component for the given *datetime* value, in UTC time.""" + return FunctionExpression("utchours", args) + + @classmethod + def utcminutes(cls, *args) -> FunctionExpression: + """Returns the minutes component for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcminutes", args) + + @classmethod + def utcseconds(cls, *args) -> FunctionExpression: + """Returns the seconds component for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcseconds", args) + + @classmethod + def utcmilliseconds(cls, *args) -> FunctionExpression: + """Returns the milliseconds component for the given *datetime* value, in UTC time.""" + return FunctionExpression("utcmilliseconds", args) + + @classmethod + def utcOffset(cls, *args) -> FunctionExpression: + """Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in UTC time. + + The optional *step* argument indicates the number of time unit steps to offset by (default 1).""" + return FunctionExpression("utcOffset", args) + + @classmethod + def utcSequence(cls, *args) -> FunctionExpression: + """Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in UTC time. + + The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).""" + return FunctionExpression("utcSequence", args) + + @classmethod + def extent(cls, *args) -> FunctionExpression: + """Returns a new _[min, max]_ array with the minimum and maximum values of the input array, ignoring `null`, `undefined`, and `NaN` values.""" + return FunctionExpression("extent", args) + + @classmethod + def clampRange(cls, *args) -> FunctionExpression: + """Clamps a two-element *range* array in a span-preserving manner. + + If the span of the input *range* is less than _(max - min)_ and an endpoint exceeds either the *min* or *max* value, the range is translated such that the span is preserved and one endpoint touches the boundary of the _[min, max]_ range. + If the span exceeds _(max - min)_, the range _[min, max]_ is returned.""" + return FunctionExpression("clampRange", args) + + @classmethod + def indexof(cls, *args) -> FunctionExpression: + """Returns the first index of *value* in the input *array*, or the first index of *substring* in the input *string*.""" + return FunctionExpression("indexof", args) + + @classmethod + def inrange(cls, *args) -> FunctionExpression: + """Tests whether *value* lies within (or is equal to either) the first and last values of the *range* array.""" + return FunctionExpression("inrange", args) + + @classmethod + def join(cls, *args) -> FunctionExpression: + """Returns a new string by concatenating all of the elements of the input *array*, separated by commas or a specified *separator* string.""" + return FunctionExpression("join", args) + + @classmethod + def lastindexof(cls, *args) -> FunctionExpression: + """Returns the last index of *value* in the input *array*, or the last index of *substring* in the input *string*.""" + return FunctionExpression("lastindexof", args) + + @classmethod + def length(cls, *args) -> FunctionExpression: + """Returns the length of the input *array*, or the length of the input *string*.""" + return FunctionExpression("length", args) + + @classmethod + def lerp(cls, *args) -> FunctionExpression: + """Returns the linearly interpolated value between the first and last entries in the *array* for the provided interpolation *fraction* (typically between 0 and 1). + + For example, `lerp([0, 50], 0.5)` returns 25.""" + return FunctionExpression("lerp", args) + + @classmethod + def peek(cls, *args) -> FunctionExpression: + """Returns the last element in the input *array*. + + Similar to the built-in `Array.pop` method, except that it does not remove the last element. + This method is a convenient shorthand for `array[array.length - 1]`.""" + return FunctionExpression("peek", args) + + @classmethod + def pluck(cls, *args) -> FunctionExpression: + """Retrieves the value for the specified *field* from a given *array* of objects. + + The input *field* string may include nested properties (e.g., `foo.bar.bz`).""" + return FunctionExpression("pluck", args) + + @classmethod + def reverse(cls, *args) -> FunctionExpression: + """Returns a new array with elements in a reverse order of the input *array*. + + The first array element becomes the last, and the last array element becomes the first.""" + return FunctionExpression("reverse", args) + + @classmethod + def sequence(cls, *args) -> FunctionExpression: + """Returns an array containing an arithmetic sequence of numbers. + + If *step* is omitted, it defaults to 1. + If *start* is omitted, it defaults to 0. + The *stop* value is exclusive; it is not included in the result. + If *step* is positive, the last element is the largest _start + i * step_ less than _stop_; if *step* is negative, the last element is the smallest _start + i * step_ greater than *stop*. + If the returned array would contain an infinite number of values, an empty range is returned. + The arguments are not required to be integers.""" + return FunctionExpression("sequence", args) + + @classmethod + def slice(cls, *args) -> FunctionExpression: + """Returns a section of *array* between the *start* and *end* indices. + + If the *end* argument is negative, it is treated as an offset from the end of the array (_length(array) + end_).""" + return FunctionExpression("slice", args) + + @classmethod + def span(cls, *args) -> FunctionExpression: + """Returns the span of *array*: the difference between the last and first elements, or *array[array.length-1] - array[0]*. + + Or if input is a string: a section of *string* between the *start* and *end* indices. + If the *end* argument is negative, it is treated as an offset from the end of the string (*length(string) + end*).""" + return FunctionExpression("span", args) + + @classmethod + def lower(cls, *args) -> FunctionExpression: + """Transforms *string* to lower-case letters.""" + return FunctionExpression("lower", args) + + @classmethod + def pad(cls, *args) -> FunctionExpression: + """Pads a *string* value with repeated instances of a *character* up to a specified *length*. + + If *character* is not specified, a space (' ') is used. + By default, padding is added to the end of a string. + An optional *align* parameter specifies if padding should be added to the `'left'` (beginning), `'center'`, or `'right'` (end) of the input string.""" + return FunctionExpression("pad", args) + + @classmethod + def parseFloat(cls, *args) -> FunctionExpression: + """Parses the input *string* to a floating-point value. + + Same as JavaScript's `parseFloat`.""" + return FunctionExpression("parseFloat", args) + + @classmethod + def parseInt(cls, *args) -> FunctionExpression: + """Parses the input *string* to an integer value. + + Same as JavaScript's `parseInt`.""" + return FunctionExpression("parseInt", args) + + @classmethod + def replace(cls, *args) -> FunctionExpression: + """Returns a new string with some or all matches of *pattern* replaced by a *replacement* string. + + The *pattern* can be a string or a regular expression. + If *pattern* is a string, only the first instance will be replaced. + Same as `JavaScript's String.replace `__.""" + return FunctionExpression("replace", args) + + @classmethod + def split(cls, *args) -> FunctionExpression: + """Returns an array of tokens created by splitting the input *string* according to a provided *separator* pattern. + + The result can optionally be constrained to return at most *limit* tokens.""" + return FunctionExpression("split", args) + + @classmethod + def substring(cls, *args) -> FunctionExpression: + """Returns a section of *string* between the *start* and *end* indices.""" + return FunctionExpression("substring", args) + + @classmethod + def trim(cls, *args) -> FunctionExpression: + """Returns a trimmed string with preceding and trailing whitespace removed.""" + return FunctionExpression("trim", args) + + @classmethod + def truncate(cls, *args) -> FunctionExpression: + r"""Truncates an input *string* to a target *length*. + + The optional *align* argument indicates what part of the string should be truncated: `'left'` (the beginning), `'center'`, or `'right'` (the end). + By default, the `'right'` end of the string is truncated. + The optional *ellipsis* argument indicates the string to use to indicate truncated content; by default the ellipsis character `...` (`\\u2026`) is used.""" + return FunctionExpression("truncate", args) + + @classmethod + def upper(cls, *args) -> FunctionExpression: + """Transforms *string* to upper-case letters.""" + return FunctionExpression("upper", args) + + @classmethod + def merge(cls, *args) -> FunctionExpression: + """Merges the input objects *object1*, *object2*, etc into a new output object. + Inputs are visited in sequential order, such that key values from later arguments can overwrite those from earlier arguments. + + Example: `merge({a:1, b:2}, {a:3}) -> {a:3, b:2}`.""" + return FunctionExpression("merge", args) + + @classmethod + def dayFormat(cls, *args) -> FunctionExpression: + """Formats a (0-6) *weekday* number as a full week day name, according to the current locale. + + For example: `dayFormat(0) -> \"Sunday\"`.""" + return FunctionExpression("dayFormat", args) + + @classmethod + def dayAbbrevFormat(cls, *args) -> FunctionExpression: + """Formats a (0-6) *weekday* number as an abbreviated week day name, according to the current locale. + + For example: `dayAbbrevFormat(0) -> \"Sun\"`.""" + return FunctionExpression("dayAbbrevFormat", args) + + @classmethod + def format(cls, *args) -> FunctionExpression: + """Formats a numeric *value* as a string. + + The *specifier* must be a valid `d3-format specifier `__ (e.g., `format(value, ',.2f')`.""" + return FunctionExpression("format", args) + + @classmethod + def monthFormat(cls, *args) -> FunctionExpression: + """Formats a (zero-based) *month* number as a full month name, according to the current locale. + + For example: `monthFormat(0) -> \"January\"`.""" + return FunctionExpression("monthFormat", args) + + @classmethod + def monthAbbrevFormat(cls, *args) -> FunctionExpression: + """Formats a (zero-based) *month* number as an abbreviated month name, according to the current locale. + + For example: `monthAbbrevFormat(0) -> \"Jan\"`.""" + return FunctionExpression("monthAbbrevFormat", args) + + @classmethod + def timeUnitSpecifier(cls, *args) -> FunctionExpression: + """Returns a time format specifier string for the given time `unit `__. + + The optional *specifiers* object provides a set of specifier sub-strings for customizing the format; + for more, see the `timeUnitSpecifier API documentation `__. + + The resulting specifier string can then be used as input to the `timeFormat `__ or + `utcFormat `__ functions, or as the *format* parameter of an axis or legend. + + For example: `timeFormat(date, timeUnitSpecifier('year'))` or `timeFormat(date, timeUnitSpecifier(['hours', 'minutes']))`.""" + return FunctionExpression("timeUnitSpecifier", args) + + @classmethod + def timeFormat(cls, *args) -> FunctionExpression: + """Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to the local time. + + The *specifier* must be a valid `d3-time-format specifier `__. + For example: `timeFormat(timestamp, '%A')`.""" + return FunctionExpression("timeFormat", args) + + @classmethod + def timeParse(cls, *args) -> FunctionExpression: + """Parses a *string* value to a Date object, according to the local time. + + The *specifier* must be a valid `d3-time-format specifier `__. + For example: `timeParse('June 30, 2015', '%B %d, %Y')`.""" + return FunctionExpression("timeParse", args) + + @classmethod + def utcFormat(cls, *args) -> FunctionExpression: + """Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to `UTC `__ time. + + The *specifier* must be a valid `d3-time-format specifier `__. + For example: `utcFormat(timestamp, '%A')`.""" + return FunctionExpression("utcFormat", args) + + @classmethod + def utcParse(cls, *args) -> FunctionExpression: + """Parses a *string* value to a Date object, according to `UTC `__ time. + + The *specifier* must be a valid `d3-time-format specifier `__. + For example: `utcParse('June 30, 2015', '%B %d, %Y')`.""" + return FunctionExpression("utcParse", args) + + @classmethod + def regexp(cls, *args) -> FunctionExpression: + """Creates a regular expression instance from an input *pattern* string and optional *flags*. + + Same as `JavaScript's `RegExp` `__.""" + return FunctionExpression("regexp", args) + + @classmethod + def test(cls, *args) -> FunctionExpression: + """Evaluates a regular expression *regexp* against the input *string*, returning `true` if the string matches the pattern, `false` otherwise. + + For example: `test(/\\d{3}/, \"32-21-9483\") -> true`.""" + return FunctionExpression("test", args) + + @classmethod + def rgb(cls, *args) -> FunctionExpression: + """Constructs a new `RGB `__ color. + + If *r*, *g* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. + If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the RGB color space. Uses `d3-color's rgb function `__.""" + return FunctionExpression("rgb", args) + + @classmethod + def hsl(cls, *args) -> FunctionExpression: + """Constructs a new `HSL `__ color. + + If *h*, *s* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. + If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HSL color space. + Uses `d3-color's hsl function `__.""" + return FunctionExpression("hsl", args) + + @classmethod + def lab(cls, *args) -> FunctionExpression: + """Constructs a new `CIE LAB `__ color. + + If *l*, *a* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. + If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the LAB color space. + Uses `d3-color's lab function `__.""" + return FunctionExpression("lab", args) + + @classmethod + def hcl(cls, *args) -> FunctionExpression: + """Constructs a new `HCL `__ (hue, chroma, luminance) color. + + If *h*, *c* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. + If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HCL color space. + Uses `d3-color's hcl function `__.""" + return FunctionExpression("hcl", args) + + @classmethod + def luminance(cls, *args) -> FunctionExpression: + """Returns the luminance for the given color *specifier* (compatible with `d3-color's rgb function `__. + + The luminance is calculated according to the `W3C Web Content Accessibility Guidelines `__.""" + return FunctionExpression("luminance", args) + + @classmethod + def contrast(cls, *args) -> FunctionExpression: + """Returns the contrast ratio between the input color specifiers as a float between 1 and 21. + + The contrast is calculated according to the `W3C Web Content Accessibility Guidelines `__.""" + return FunctionExpression("contrast", args) + + @classmethod + def item(cls, *args) -> FunctionExpression: + """Returns the current scenegraph item that is the target of the event.""" + return FunctionExpression("item", args) + + @classmethod + def group(cls, *args) -> FunctionExpression: + """Returns the scenegraph group mark item in which the current event has occurred. + + If no arguments are provided, the immediate parent group is returned. + If a group name is provided, the matching ancestor group item is returned.""" + return FunctionExpression("group", args) + + @classmethod + def xy(cls, *args) -> FunctionExpression: + """Returns the x- and y-coordinates for the current event as a two-element array. + + If no arguments are provided, the top-level coordinate space of the view is used. + If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.""" + return FunctionExpression("xy", args) + + @classmethod + def x(cls, *args) -> FunctionExpression: + """Returns the x coordinate for the current event. + + If no arguments are provided, the top-level coordinate space of the view is used. + If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.""" + return FunctionExpression("x", args) + + @classmethod + def y(cls, *args) -> FunctionExpression: + """Returns the y coordinate for the current event. + + If no arguments are provided, the top-level coordinate space of the view is used. + If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.""" + return FunctionExpression("y", args) + + @classmethod + def pinchDistance(cls, *args) -> FunctionExpression: + """Returns the pixel distance between the first two touch points of a multi-touch event.""" + return FunctionExpression("pinchDistance", args) + + @classmethod + def pinchAngle(cls, *args) -> FunctionExpression: + """Returns the angle of the line connecting the first two touch points of a multi-touch event.""" + return FunctionExpression("pinchAngle", args) + + @classmethod + def inScope(cls, *args) -> FunctionExpression: + """Returns true if the given scenegraph *item* is a descendant of the group mark in which the event handler was defined, false otherwise.""" + return FunctionExpression("inScope", args) + + @classmethod + def data(cls, *args) -> FunctionExpression: + """Returns the array of data objects for the Vega data set with the given *name*. + + If the data set is not found, returns an empty array.""" + return FunctionExpression("data", args) + + @classmethod + def indata(cls, *args) -> FunctionExpression: + """Tests if the data set with a given *name* contains a datum with a *field* value that matches the input *value*. + + For example: `indata('table', 'category', value)`.""" + return FunctionExpression("indata", args) + + @classmethod + def scale(cls, *args) -> FunctionExpression: + """Applies the named scale transform (or projection) to the specified *value*. + + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.""" + return FunctionExpression("scale", args) + + @classmethod + def invert(cls, *args) -> FunctionExpression: + """Inverts the named scale transform (or projection) for the specified *value*. + + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.""" + return FunctionExpression("invert", args) + + @classmethod + def copy(cls, *args) -> FunctionExpression: # type: ignore[override] + """Returns a copy (a new cloned instance) of the named scale transform of projection, or `undefined` if no scale or projection is found. + + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.""" + # error: Signature of "copy" incompatible with supertype "SchemaBase" [override] + # note: def copy(self, deep: bool | Iterable[Any] = ..., ignore: list[str] | None = ...) -> expr + # NOTE: Not relevant as `expr() -> ExprRef` + # this method is only accesible via `expr.copy()` + return FunctionExpression("copy", args) + + @classmethod + def domain(cls, *args) -> FunctionExpression: + """Returns the scale domain array for the named scale transform, or an empty array if the scale is not found. + + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.""" + return FunctionExpression("domain", args) + + @classmethod + def range(cls, *args) -> FunctionExpression: + """Returns the scale range array for the named scale transform, or an empty array if the scale is not found. + + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.""" + return FunctionExpression("range", args) + + @classmethod + def bandwidth(cls, *args) -> FunctionExpression: + """Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale. + + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.""" + return FunctionExpression("bandwidth", args) + + @classmethod + def bandspace(cls, *args) -> FunctionExpression: + """Returns the number of steps needed within a band scale, based on the *count* of domain elements and the inner and outer padding values. + + While normally calculated within the scale itself, this function can be helpful for determining the size of a chart's layout.""" + return FunctionExpression("bandspace", args) + + @classmethod + def gradient(cls, *args) -> FunctionExpression: + """Returns a linear color gradient for the *scale* (whose range must be a `continuous color scheme `__ and starting and ending points *p0* and *p1*, each an *[x, y]* array. + + The points *p0* and *p1* should be expressed in normalized coordinates in the domain [0, 1], relative to the bounds of the item being colored. + + If unspecified, *p0* defaults to `[0, 0]` and *p1* defaults to `[1, 0]`, for a horizontal gradient that spans the full bounds of an item. + The optional *count* argument indicates a desired target number of sample points to take from the color scale.""" + return FunctionExpression("gradient", args) + + @classmethod + def panLinear(cls, *args) -> FunctionExpression: + """Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + return FunctionExpression("panLinear", args) + + @classmethod + def panLog(cls, *args) -> FunctionExpression: + """Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + return FunctionExpression("panLog", args) + + @classmethod + def panPow(cls, *args) -> FunctionExpression: + """Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + return FunctionExpression("panPow", args) + + @classmethod + def panSymlog(cls, *args) -> FunctionExpression: + """Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + return FunctionExpression("panSymlog", args) + + @classmethod + def zoomLinear(cls, *args) -> FunctionExpression: + """Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + return FunctionExpression("zoomLinear", args) + + @classmethod + def zoomLog(cls, *args) -> FunctionExpression: + """Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + return FunctionExpression("zoomLog", args) + + @classmethod + def zoomPow(cls, *args) -> FunctionExpression: + """Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + return FunctionExpression("zoomPow", args) + + @classmethod + def zoomSymlog(cls, *args) -> FunctionExpression: + """Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + return FunctionExpression("zoomSymlog", args) + + @classmethod + def geoArea(cls, *args) -> FunctionExpression: + """Returns the projected planar area (typically in square pixels) of a GeoJSON *feature* according to the named *projection*. + + If the *projection* argument is `null`, computes the spherical area in steradians using unprojected longitude, latitude coordinates. + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. + Uses d3-geo's `geoArea `__ and `path.area `__ methods.""" + return FunctionExpression("geoArea", args) + + @classmethod + def geoBounds(cls, *args) -> FunctionExpression: + """Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*. + + The bounding box is represented by a two-dimensional array: [[_x0_, _y0_], [_x1_, _y1_]], where *x0* is the minimum x-coordinate, *y0* is the minimum y-coordinate, *x1* is the maximum x-coordinate, and *y1* is the maximum y-coordinate. + If the *projection* argument is `null`, computes the spherical bounding box using unprojected longitude, latitude coordinates. + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. + Uses d3-geo's `geoBounds `__ and `path.bounds `__ methods.""" + return FunctionExpression("geoBounds", args) + + @classmethod + def geoCentroid(cls, *args) -> FunctionExpression: + """Returns the projected planar centroid (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*. + + If the *projection* argument is `null`, computes the spherical centroid using unprojected longitude, latitude coordinates. + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. + Uses d3-geo's `geoCentroid `__ and `path.centroid `__ methods.""" + return FunctionExpression("geoCentroid", args) + + @classmethod + def treePath(cls, *args) -> FunctionExpression: + """For the hierarchy data set with the given *name*, returns the shortest path through from the *source* node id to the *target* node id. + + The path starts at the *source* node, ascends to the least common ancestor of the *source* node and the *target* node, and then descends to the *target* node.""" + return FunctionExpression("treePath", args) + + @classmethod + def treeAncestors(cls, *args) -> FunctionExpression: + """For the hierarchy data set with the given *name*, returns the array of ancestors nodes, starting with the input *node*, then followed by each parent up to the root.""" + return FunctionExpression("treeAncestors", args) + + @classmethod + def containerSize(cls, *args) -> FunctionExpression: + """Returns the current CSS box size (`[el.clientWidth, el.clientHeight]`) of the parent DOM element that contains the Vega view. + + If there is no container element, returns `[undefined, undefined]`.""" + return FunctionExpression("containerSize", args) + + @classmethod + def screen(cls, *args) -> FunctionExpression: + """Returns the `window.screen `__ object, or `{}` if Vega is not running in a browser environment.""" + return FunctionExpression("screen", args) + + @classmethod + def windowSize(cls, *args) -> FunctionExpression: + """Returns the current window size (`[window.innerWidth, window.innerHeight]`) or `[undefined, undefined]` if Vega is not running in a browser environment.""" + return FunctionExpression("windowSize", args) + + @classmethod + def warn(cls, *args) -> FunctionExpression: + """Logs a warning message and returns the last argument. + + For the message to appear in the console, the visualization view must have the appropriate logging level set.""" + return FunctionExpression("warn", args) + + @classmethod + def info(cls, *args) -> FunctionExpression: + """Logs an informative message and returns the last argument. + + For the message to appear in the console, the visualization view must have the appropriate logging level set.""" + return FunctionExpression("info", args) + + @classmethod + def debug(cls, *args) -> FunctionExpression: + """Logs a debugging message and returns the last argument. + + For the message to appear in the console, the visualization view must have the appropriate logging level set.""" + return FunctionExpression("debug", args) + + +_ExprType = expr +# NOTE: Compatibility alias for previous type of `alt.expr`. +# `_ExprType` was not referenced in any internal imports/tests. From b634913028f54c85973f53b6285189844fcd3cc5 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:41:56 +0100 Subject: [PATCH 04/15] fix: use absolute imports in `test_expr` Previously relied on the dynamic globals --- tests/expr/test_expr.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index 49d574176..f8c201acc 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -6,6 +6,9 @@ from altair import datum from altair import ExprRef from jsonschema.exceptions import ValidationError +from altair.expr.funcs import NAME_MAP +from altair.expr.funcs import FUNCTION_LISTING +from altair.expr.consts import CONST_LISTING def test_unary_operations(): From 02fccf556f7ef954ac0272f21cec1979ec6bdd63 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:44:03 +0100 Subject: [PATCH 05/15] test(perf): rewrite `expr` tests to run in parallel Previously 2 tests, now 170+ --- tests/expr/test_expr.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index f8c201acc..bdd0b632f 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -62,22 +62,25 @@ def test_abs(): assert repr(z) == "abs(datum.xxx)" -def test_expr_funcs(): +@pytest.mark.parametrize( + ("veganame", "methodname"), + {nm: (NAME_MAP.get(nm, nm)) for nm in FUNCTION_LISTING}.items(), +) +def test_expr_funcs(veganame: str, methodname: str): """test all functions defined in expr.funcs""" - name_map = {val: key for key, val in expr.funcs.NAME_MAP.items()} - for funcname in expr.funcs.__all__: - func = getattr(expr, funcname) - z = func(datum.xxx) - assert repr(z) == f"{name_map.get(funcname, funcname)}(datum.xxx)" + func = getattr(expr, methodname) + z = func(datum.xxx) + assert repr(z) == f"{veganame}(datum.xxx)" -def test_expr_consts(): +@pytest.mark.parametrize("constname", CONST_LISTING) +def test_expr_consts(constname: str): """Test all constants defined in expr.consts""" - name_map = {val: key for key, val in expr.consts.NAME_MAP.items()} - for constname in expr.consts.__all__: - const = getattr(expr, constname) - z = const * datum.xxx - assert repr(z) == f"({name_map.get(constname, constname)} * datum.xxx)" + + const = getattr(expr, constname) + z = const * datum.xxx + assert repr(z) == f"({constname} * datum.xxx)" + def test_json_reprs(): From 77a2073def060a9231e28b8a72aa8246ee183041 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:45:27 +0100 Subject: [PATCH 06/15] test: confirm `expr` constants stay constant --- tests/expr/test_expr.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index bdd0b632f..032f633a7 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -82,6 +82,12 @@ def test_expr_consts(constname: str): assert repr(z) == f"({constname} * datum.xxx)" +@pytest.mark.parametrize("constname", CONST_LISTING) +def test_expr_consts_immutable(constname: str): + """Ensure e.g `alt.expr.PI = 2` is prevented.""" + with pytest.raises(AttributeError, match=f"property {constname!r}.+has no setter"): + setattr(expr, constname, 2) + def test_json_reprs(): """Test JSON representations of special values""" From fe0961650e8dd0083353a578c0df7c6c1f735483 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:47:41 +0100 Subject: [PATCH 07/15] chore(typing): add ignores not flagged by mypy --- tests/expr/test_expr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index 032f633a7..b2e5a03ad 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -139,7 +139,7 @@ def test_expression_function_nostring(): # expr() can only work with str otherwise # should raise a SchemaValidationError with pytest.raises(ValidationError): - expr(2 * 2) + expr(2 * 2) # pyright: ignore with pytest.raises(ValidationError): - expr(["foo", "bah"]) + expr(["foo", "bah"]) # pyright: ignore From 8a80357d527ff875545b832992299fd1e772ec84 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:53:04 +0100 Subject: [PATCH 08/15] docs: adds `alt.expr` to API Reference Uses the section heading proposed in https://github.com/vega/altair/pull/3427#discussion_r1659965947 Expands on https://github.com/vega/altair/issues/2904 --- doc/user_guide/api.rst | 10 ++++++++++ tools/generate_api_docs.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst index 08013023f..157eefc3d 100644 --- a/doc/user_guide/api.rst +++ b/doc/user_guide/api.rst @@ -591,3 +591,13 @@ Low-Level Schema Wrappers WindowFieldDef WindowOnlyOp WindowTransform + +API Utility Classes +------------------- +.. currentmodule:: altair + +.. autosummary:: + :toctree: generated/api-cls/ + :nosignatures: + + expr diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py index 2f923d8c2..b97bf5283 100644 --- a/tools/generate_api_docs.py +++ b/tools/generate_api_docs.py @@ -64,6 +64,16 @@ :nosignatures: {lowlevel_wrappers} + +API Utility Classes +------------------- +.. currentmodule:: altair + +.. autosummary:: + :toctree: generated/api-cls/ + :nosignatures: + + {api_classes} """ @@ -104,6 +114,10 @@ def api_functions() -> list[str]: return sorted(altair_api_functions) +def api_classes() -> list[str]: + return ["expr"] + + def lowlevel_wrappers() -> list[str]: objects = sorted(iter_objects(alt.schema.core, restrict_to_subclass=alt.SchemaBase)) # type: ignore[attr-defined] # The names of these two classes are also used for classes in alt.channels. Due to @@ -124,6 +138,7 @@ def write_api_file() -> None: api_functions=sep.join(api_functions()), encoding_wrappers=sep.join(encoding_wrappers()), lowlevel_wrappers=sep.join(lowlevel_wrappers()), + api_classes=sep.join(api_classes()), ), encoding="utf-8", ) From 2b7a2b3980ef8d0158688b74862c2e2851b85987 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:06:51 +0100 Subject: [PATCH 09/15] test: ensure `test_expr_consts_immutable` pattern works for all versions --- tests/expr/test_expr.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index b2e5a03ad..52eee6c19 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -1,5 +1,5 @@ import operator - +import sys import pytest from altair import expr @@ -85,7 +85,13 @@ def test_expr_consts(constname: str): @pytest.mark.parametrize("constname", CONST_LISTING) def test_expr_consts_immutable(constname: str): """Ensure e.g `alt.expr.PI = 2` is prevented.""" - with pytest.raises(AttributeError, match=f"property {constname!r}.+has no setter"): + if sys.version_info >= (3, 11): + pattern = f"property {constname!r}.+has no setter" + elif sys.version_info >= (3, 10): + pattern = f"can't set attribute {constname!r}" + else: + pattern = "can't set attribute" + with pytest.raises(AttributeError, match=pattern): setattr(expr, constname, 2) From 34430a07043c97787f4d3dce8257d23f71594e3c Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Wed, 10 Jul 2024 09:35:29 +0100 Subject: [PATCH 10/15] docs: fix typos, regex artifacts, apply some `ruff` `D` rules --- altair/expr/__init__.py | 719 ++++++++++++++++++++++++++-------------- 1 file changed, 478 insertions(+), 241 deletions(-) diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py index 54e43b790..b07720f9f 100644 --- a/altair/expr/__init__.py +++ b/altair/expr/__init__.py @@ -1,4 +1,4 @@ -"""Tools for creating transform & filter expressions with a python syntax""" +"""Tools for creating transform & filter expressions with a python syntax.""" from __future__ import annotations @@ -16,54 +16,55 @@ class _ConstExpressionType(type): @property def NaN(cls) -> ConstExpression: - "not a number (same as JavaScript literal NaN)" + """Not a number (same as JavaScript literal NaN).""" return ConstExpression("NaN") @property def LN10(cls) -> ConstExpression: - """the natural log of 10 (alias to Math.LN10)""" + """The natural log of 10 (alias to Math.LN10).""" return ConstExpression("LN10") @property def E(cls) -> ConstExpression: - """the transcendental number e (alias to Math.E)""" + """The transcendental number e (alias to Math.E).""" return ConstExpression("E") @property def LOG10E(cls) -> ConstExpression: - """the base 10 logarithm e (alias to Math.LOG10E)""" + """The base 10 logarithm e (alias to Math.LOG10E).""" return ConstExpression("LOG10E") @property def LOG2E(cls) -> ConstExpression: - """the base 2 logarithm of e (alias to Math.LOG2E)""" + """The base 2 logarithm of e (alias to Math.LOG2E).""" return ConstExpression("LOG2E") @property def SQRT1_2(cls) -> ConstExpression: - """the square root of 0.5 (alias to Math.SQRT1_2)""" + """The square root of 0.5 (alias to Math.SQRT1_2).""" return ConstExpression("SQRT1_2") @property def LN2(cls) -> ConstExpression: - """the natural log of 2 (alias to Math.LN2)""" + """The natural log of 2 (alias to Math.LN2).""" return ConstExpression("LN2") @property def SQRT2(cls) -> ConstExpression: - """the square root of 2 (alias to Math.SQRT1_2)""" + """The square root of 2 (alias to Math.SQRT1_2).""" return ConstExpression("SQRT2") @property def PI(cls) -> ConstExpression: - """the transcendental number pi (alias to Math.PI)""" + """The transcendental number pi (alias to Math.PI).""" return ConstExpression("PI") class expr(_ExprRef, metaclass=_ConstExpressionType): - r"""Utility providing *constants* and *classmethods* to construct expressions. + r""" + Utility providing *constants* and *classmethods* to construct expressions. - `Expressions`_ can used to write basic formulas that enable custom interactions. + `Expressions`_ can be used to write basic formulas that enable custom interactions. Alternatively, an `inline expression`_ may be defined via :class:`expr()`. @@ -86,9 +87,11 @@ class expr(_ExprRef, metaclass=_ConstExpressionType): Examples -------- >>> import altair as alt - >>> bind_range = alt.binding_range(min=100, max=300, name='Slider value: ') + >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value: ") >>> param_width = alt.param(bind=bind_range) - >>> param_color = alt.param(expr=alt.expr.if_(param_width < 200, 'red', 'black')) + >>> param_color = alt.param( + ... expr=alt.expr.if_(param_width < 200, "red", "black") + ... ) >>> y = alt.Y("yval").axis(titleColor=param_color) """ @@ -102,9 +105,11 @@ def __new__(cls: type[_ExprRef], expr: str) -> _ExprRef: # type: ignore[misc] @classmethod def if_(cls, *args) -> FunctionExpression: - """If *test* is truthy, returns *thenValue*. Otherwise, returns *elseValue*. + """ + If *test* is truthy, returns *thenValue*. Otherwise, returns *elseValue*. - The *if* function is equivalent to the ternary operator `a ? b : c`.""" + The *if* function is equivalent to the ternary operator `a ? b : c`. + """ return FunctionExpression("if", args) @classmethod @@ -119,23 +124,29 @@ def isBoolean(cls, *args) -> FunctionExpression: @classmethod def isDate(cls, *args) -> FunctionExpression: - """Returns true if *value* is a Date object, false otherwise. + """ + Returns true if *value* is a Date object, false otherwise. - This method will return false for timestamp numbers or date-formatted strings; it recognizes Date objects only.""" + This method will return false for timestamp numbers or date-formatted strings; it recognizes Date objects only. + """ return FunctionExpression("isDate", args) @classmethod def isDefined(cls, *args) -> FunctionExpression: - """Returns true if *value* is a defined value, false if *value* equals `undefined`. + """ + Returns true if *value* is a defined value, false if *value* equals `undefined`. - This method will return true for `null` and `NaN` values.""" + This method will return true for `null` and `NaN` values. + """ return FunctionExpression("isDefined", args) @classmethod def isNumber(cls, *args) -> FunctionExpression: - """Returns true if *value* is a number, false otherwise. + """ + Returns true if *value* is a number, false otherwise. - `NaN` and `Infinity` are considered numbers.""" + `NaN` and `Infinity` are considered numbers. + """ return FunctionExpression("isNumber", args) @classmethod @@ -160,88 +171,112 @@ def isValid(cls, *args) -> FunctionExpression: @classmethod def toBoolean(cls, *args) -> FunctionExpression: - """Coerces the input *value* to a string. + """ + Coerces the input *value* to a string. - Null values and empty strings are mapped to `null`.""" + Null values and empty strings are mapped to `null`. + """ return FunctionExpression("toBoolean", args) @classmethod def toDate(cls, *args) -> FunctionExpression: - """Coerces the input *value* to a Date instance. + """ + Coerces the input *value* to a Date instance. Null values and empty strings are mapped to `null`. If an optional *parser* function is provided, it is used to perform date parsing, otherwise `Date.parse` is used. - Be aware that `Date.parse` has different implementations across browsers!""" + Be aware that `Date.parse` has different implementations across browsers! + """ return FunctionExpression("toDate", args) @classmethod def toNumber(cls, *args) -> FunctionExpression: - """Coerces the input *value* to a number. + """ + Coerces the input *value* to a number. - Null values and empty strings are mapped to `null`.""" + Null values and empty strings are mapped to `null`. + """ return FunctionExpression("toNumber", args) @classmethod def toString(cls, *args) -> FunctionExpression: - """Coerces the input *value* to a string. + """ + Coerces the input *value* to a string. - Null values and empty strings are mapped to `null`.""" + Null values and empty strings are mapped to `null`. + """ return FunctionExpression("toString", args) @classmethod def isNaN(cls, *args) -> FunctionExpression: - """Returns true if *value* is not a number. + """ + Returns true if *value* is not a number. - Same as JavaScript's `isNaN`.""" + Same as JavaScript's `isNaN`. + """ return FunctionExpression("isNaN", args) @classmethod def isFinite(cls, *args) -> FunctionExpression: - """Returns true if *value* is a finite number. + """ + Returns true if *value* is a finite number. - Same as JavaScript's `isFinite`.""" + Same as JavaScript's `isFinite`. + """ return FunctionExpression("isFinite", args) @classmethod def abs(cls, *args) -> FunctionExpression: - """Returns the absolute value of *value*. + """ + Returns the absolute value of *value*. - Same as JavaScript's `Math.abs`.""" + Same as JavaScript's `Math.abs`. + """ return FunctionExpression("abs", args) @classmethod def acos(cls, *args) -> FunctionExpression: - """Trigonometric arccosine. + """ + Trigonometric arccosine. - Same as JavaScript's `Math.acos`.""" + Same as JavaScript's `Math.acos`. + """ return FunctionExpression("acos", args) @classmethod def asin(cls, *args) -> FunctionExpression: - """Trigonometric arcsine. + """ + Trigonometric arcsine. - Same as JavaScript's `Math.asin`.""" + Same as JavaScript's `Math.asin`. + """ return FunctionExpression("asin", args) @classmethod def atan(cls, *args) -> FunctionExpression: - """Trigonometric arctangent. + """ + Trigonometric arctangent. - Same as JavaScript's `Math.atan`.""" + Same as JavaScript's `Math.atan`. + """ return FunctionExpression("atan", args) @classmethod def atan2(cls, *args) -> FunctionExpression: - """Returns the arctangent of *dy / dx*. + """ + Returns the arctangent of *dy / dx*. - Same as JavaScript's `Math.atan2`.""" + Same as JavaScript's `Math.atan2`. + """ return FunctionExpression("atan2", args) @classmethod def ceil(cls, *args) -> FunctionExpression: - """Rounds *value* to the nearest integer of equal or greater value. + """ + Rounds *value* to the nearest integer of equal or greater value. - Same as JavaScript's `Math.ceil`.""" + Same as JavaScript's `Math.ceil`. + """ return FunctionExpression("ceil", args) @classmethod @@ -251,181 +286,231 @@ def clamp(cls, *args) -> FunctionExpression: @classmethod def cos(cls, *args) -> FunctionExpression: - """Trigonometric cosine. + """ + Trigonometric cosine. - Same as JavaScript's `Math.cos`.""" + Same as JavaScript's `Math.cos`. + """ return FunctionExpression("cos", args) @classmethod def exp(cls, *args) -> FunctionExpression: - """Returns the value of *e* raised to the provided *exponent*. + """ + Returns the value of *e* raised to the provided *exponent*. - Same as JavaScript's `Math.exp`.""" + Same as JavaScript's `Math.exp`. + """ return FunctionExpression("exp", args) @classmethod def floor(cls, *args) -> FunctionExpression: - """Rounds *value* to the nearest integer of equal or lower value. + """ + Rounds *value* to the nearest integer of equal or lower value. - Same as JavaScript's `Math.floor`.""" + Same as JavaScript's `Math.floor`. + """ return FunctionExpression("floor", args) @classmethod def hypot(cls, *args) -> FunctionExpression: - """Returns the square root of the sum of squares of its arguments. + """ + Returns the square root of the sum of squares of its arguments. - Same as JavaScript's `Math.hypot`.""" + Same as JavaScript's `Math.hypot`. + """ return FunctionExpression("hypot", args) @classmethod def log(cls, *args) -> FunctionExpression: - """Returns the natural logarithm of *value*. + """ + Returns the natural logarithm of *value*. - Same as JavaScript's `Math.log`.""" + Same as JavaScript's `Math.log`. + """ return FunctionExpression("log", args) @classmethod def max(cls, *args) -> FunctionExpression: - """Returns the maximum argument value. + """ + Returns the maximum argument value. - Same as JavaScript's `Math.max`.""" + Same as JavaScript's `Math.max`. + """ return FunctionExpression("max", args) @classmethod def min(cls, *args) -> FunctionExpression: - """Returns the minimum argument value. + """ + Returns the minimum argument value. - Same as JavaScript's `Math.min`.""" + Same as JavaScript's `Math.min`. + """ return FunctionExpression("min", args) @classmethod def pow(cls, *args) -> FunctionExpression: - """Returns *value* raised to the given *exponent*. + """ + Returns *value* raised to the given *exponent*. - Same as JavaScript's `Math.pow`.""" + Same as JavaScript's `Math.pow`. + """ return FunctionExpression("pow", args) @classmethod def random(cls, *args) -> FunctionExpression: - """Returns a pseudo-random number in the range [0,1). + """ + Returns a pseudo-random number in the range `[0, 1]`. - Same as JavaScript's `Math.random`.""" + Same as JavaScript's `Math.random`. + """ return FunctionExpression("random", args) @classmethod def round(cls, *args) -> FunctionExpression: - """Rounds *value* to the nearest integer. + """ + Rounds *value* to the nearest integer. - Same as JavaScript's `Math.round`.""" + Same as JavaScript's `Math.round`. + """ return FunctionExpression("round", args) @classmethod def sin(cls, *args) -> FunctionExpression: - """Trigonometric sine. + """ + Trigonometric sine. - Same as JavaScript's `Math.sin`.""" + Same as JavaScript's `Math.sin`. + """ return FunctionExpression("sin", args) @classmethod def sqrt(cls, *args) -> FunctionExpression: - """Square root function. + """ + Square root function. - Same as JavaScript's `Math.sqrt`.""" + Same as JavaScript's `Math.sqrt`. + """ return FunctionExpression("sqrt", args) @classmethod def tan(cls, *args) -> FunctionExpression: - """Trigonometric tangent. + """ + Trigonometric tangent. - Same as JavaScript's `Math.tan`.""" + Same as JavaScript's `Math.tan`. + """ return FunctionExpression("tan", args) @classmethod def sampleNormal(cls, *args) -> FunctionExpression: - """Returns a sample from a univariate `normal (Gaussian) probability distribution `__ with specified *mean* and standard deviation *stdev*. + """ + Returns a sample from a univariate `normal (Gaussian) probability distribution `__ with specified *mean* and standard deviation *stdev*. - If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`. + """ return FunctionExpression("sampleNormal", args) @classmethod def cumulativeNormal(cls, *args) -> FunctionExpression: - """Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a normal distribution with specified *mean* and standard deviation *stdev*. + """ + Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a normal distribution with specified *mean* and standard deviation *stdev*. - If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`. + """ return FunctionExpression("cumulativeNormal", args) @classmethod def densityNormal(cls, *args) -> FunctionExpression: - """Returns the value of the `probability density function `__ at the given input domain *value*, for a normal distribution with specified *mean* and standard deviation *stdev*. + """ + Returns the value of the `probability density function `__ at the given input domain *value*, for a normal distribution with specified *mean* and standard deviation *stdev*. - If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`. + """ return FunctionExpression("densityNormal", args) @classmethod def quantileNormal(cls, *args) -> FunctionExpression: - """Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a normal distribution with specified *mean* and standard deviation *stdev*. + """ + Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a normal distribution with specified *mean* and standard deviation *stdev*. - If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.""" + If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`. + """ return FunctionExpression("quantileNormal", args) @classmethod def sampleLogNormal(cls, *args) -> FunctionExpression: - """Returns a sample from a univariate `log-normal probability distribution `__ with specified log *mean* and log standard deviation *stdev*. + """ + Returns a sample from a univariate `log-normal probability distribution `__ with specified log *mean* and log standard deviation *stdev*. - If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`. + """ return FunctionExpression("sampleLogNormal", args) @classmethod def cumulativeLogNormal(cls, *args) -> FunctionExpression: - """Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. + """ + Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. - If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`. + """ return FunctionExpression("cumulativeLogNormal", args) @classmethod def densityLogNormal(cls, *args) -> FunctionExpression: - """Returns the value of the `probability density function `__ at the given input domain *value*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. + """ + Returns the value of the `probability density function `__ at the given input domain *value*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. - If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`. + """ return FunctionExpression("densityLogNormal", args) @classmethod def quantileLogNormal(cls, *args) -> FunctionExpression: - """Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. + """ + Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*. - If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.""" + If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`. + """ return FunctionExpression("quantileLogNormal", args) @classmethod def sampleUniform(cls, *args) -> FunctionExpression: - """Returns a sample from a univariate `continuous uniform probability distribution `__ over the interval *[min, max]*. + """ + Returns a sample from a univariate `continuous uniform probability distribution `__ over the interval `[min, max]`. If unspecified, *min* defaults to `0` and *max* defaults to `1`. - If only one argument is provided, it is interpreted as the *max* value.""" + If only one argument is provided, it is interpreted as the *max* value. + """ return FunctionExpression("sampleUniform", args) @classmethod def cumulativeUniform(cls, *args) -> FunctionExpression: - """Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a uniform distribution over the interval *[min, max]*. + """ + Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a uniform distribution over the interval `[min, max]`. If unspecified, *min* defaults to `0` and *max* defaults to `1`. - If only one argument is provided, it is interpreted as the *max* value.""" + If only one argument is provided, it is interpreted as the *max* value. + """ return FunctionExpression("cumulativeUniform", args) @classmethod def densityUniform(cls, *args) -> FunctionExpression: - """Returns the value of the `probability density function `__ at the given input domain *value*, for a uniform distribution over the interval *[min, max]*. + """ + Returns the value of the `probability density function `__ at the given input domain *value*, for a uniform distribution over the interval `[min, max]`. If unspecified, *min* defaults to `0` and *max* defaults to `1`. - If only one argument is provided, it is interpreted as the *max* value.""" + If only one argument is provided, it is interpreted as the *max* value. + """ return FunctionExpression("densityUniform", args) @classmethod def quantileUniform(cls, *args) -> FunctionExpression: - """Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a uniform distribution over the interval *[min, max]*. + """ + Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a uniform distribution over the interval `[min, max]`. If unspecified, *min* defaults to `0` and *max* defaults to `1`. - If only one argument is provided, it is interpreted as the *max* value.""" + If only one argument is provided, it is interpreted as the *max* value. + """ return FunctionExpression("quantileUniform", args) @classmethod @@ -435,9 +520,11 @@ def now(cls, *args) -> FunctionExpression: @classmethod def datetime(cls, *args) -> FunctionExpression: - """Returns a new `Date` instance. + """ + Returns a new `Date` instance. - The *month* is 0-based, such that `1` represents February.""" + The *month* is 0-based, such that `1` represents February. + """ return FunctionExpression("datetime", args) @classmethod @@ -472,10 +559,14 @@ def month(cls, *args) -> FunctionExpression: @classmethod def week(cls, *args) -> FunctionExpression: - """Returns the week number of the year for the given *datetime*, in local time. + """ + Returns the week number of the year for the given *datetime*, in local time. This function assumes Sunday-based weeks. - Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._.""" + Days before the first Sunday of the year are considered to be in week 0, + the first Sunday of the year is the start of week 1, + the second Sunday week 2, etc. + """ return FunctionExpression("week", args) @classmethod @@ -510,16 +601,20 @@ def timezoneoffset(cls, *args) -> FunctionExpression: @classmethod def timeOffset(cls, *args) -> FunctionExpression: - """Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in the local timezone. + """ + Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in the local timezone. - The optional *step* argument indicates the number of time unit steps to offset by (default 1).""" + The optional *step* argument indicates the number of time unit steps to offset by (default 1). + """ return FunctionExpression("timeOffset", args) @classmethod def timeSequence(cls, *args) -> FunctionExpression: - """Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in the local timezone. + """ + Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in the local timezone. - The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).""" + The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1). + """ return FunctionExpression("timeSequence", args) @classmethod @@ -559,10 +654,14 @@ def utcmonth(cls, *args) -> FunctionExpression: @classmethod def utcweek(cls, *args) -> FunctionExpression: - """Returns the week number of the year for the given *datetime*, in UTC time. + """ + Returns the week number of the year for the given *datetime*, in UTC time. This function assumes Sunday-based weeks. - Days before the first Sunday of the year are considered to be in week 0, the first Sunday of the year is the start of week 1, the second Sunday week 2, _etc._.""" + Days before the first Sunday of the year are considered to be in week 0, + the first Sunday of the year is the start of week 1, + the second Sunday week 2, etc. + """ return FunctionExpression("utcweek", args) @classmethod @@ -587,29 +686,36 @@ def utcmilliseconds(cls, *args) -> FunctionExpression: @classmethod def utcOffset(cls, *args) -> FunctionExpression: - """Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in UTC time. + """ + Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in UTC time. - The optional *step* argument indicates the number of time unit steps to offset by (default 1).""" + The optional *step* argument indicates the number of time unit steps to offset by (default 1). + """ return FunctionExpression("utcOffset", args) @classmethod def utcSequence(cls, *args) -> FunctionExpression: - """Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in UTC time. + """ + Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in UTC time. - The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).""" + The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1). + """ return FunctionExpression("utcSequence", args) @classmethod def extent(cls, *args) -> FunctionExpression: - """Returns a new _[min, max]_ array with the minimum and maximum values of the input array, ignoring `null`, `undefined`, and `NaN` values.""" + """Returns a new `[min, max]` array with the minimum and maximum values of the input array, ignoring `null`, `undefined`, and `NaN` values.""" return FunctionExpression("extent", args) @classmethod def clampRange(cls, *args) -> FunctionExpression: - """Clamps a two-element *range* array in a span-preserving manner. + """ + Clamps a two-element *range* array in a span-preserving manner. - If the span of the input *range* is less than _(max - min)_ and an endpoint exceeds either the *min* or *max* value, the range is translated such that the span is preserved and one endpoint touches the boundary of the _[min, max]_ range. - If the span exceeds _(max - min)_, the range _[min, max]_ is returned.""" + If the span of the input *range* is less than `(max - min)` and an endpoint exceeds either the *min* or *max* value, + the range is translated such that the span is preserved and one endpoint touches the boundary of the `[min, max]` range. + If the span exceeds `(max - min)`, the range `[min, max]` is returned. + """ return FunctionExpression("clampRange", args) @classmethod @@ -639,58 +745,75 @@ def length(cls, *args) -> FunctionExpression: @classmethod def lerp(cls, *args) -> FunctionExpression: - """Returns the linearly interpolated value between the first and last entries in the *array* for the provided interpolation *fraction* (typically between 0 and 1). + """ + Returns the linearly interpolated value between the first and last entries in the *array* for the provided interpolation *fraction* (typically between 0 and 1). - For example, `lerp([0, 50], 0.5)` returns 25.""" + For example, `lerp([0, 50], 0.5)` returns 25. + """ return FunctionExpression("lerp", args) @classmethod def peek(cls, *args) -> FunctionExpression: - """Returns the last element in the input *array*. + """ + Returns the last element in the input *array*. Similar to the built-in `Array.pop` method, except that it does not remove the last element. - This method is a convenient shorthand for `array[array.length - 1]`.""" + This method is a convenient shorthand for `array[array.length - 1]`. + """ return FunctionExpression("peek", args) @classmethod def pluck(cls, *args) -> FunctionExpression: - """Retrieves the value for the specified *field* from a given *array* of objects. + """ + Retrieves the value for the specified *field* from a given *array* of objects. - The input *field* string may include nested properties (e.g., `foo.bar.bz`).""" + The input *field* string may include nested properties (e.g., `foo.bar.bz`). + """ return FunctionExpression("pluck", args) @classmethod def reverse(cls, *args) -> FunctionExpression: - """Returns a new array with elements in a reverse order of the input *array*. + """ + Returns a new array with elements in a reverse order of the input *array*. - The first array element becomes the last, and the last array element becomes the first.""" + The first array element becomes the last, and the last array element becomes the first. + """ return FunctionExpression("reverse", args) @classmethod def sequence(cls, *args) -> FunctionExpression: - """Returns an array containing an arithmetic sequence of numbers. + r""" + Returns an array containing an arithmetic sequence of numbers. If *step* is omitted, it defaults to 1. If *start* is omitted, it defaults to 0. + The *stop* value is exclusive; it is not included in the result. - If *step* is positive, the last element is the largest _start + i * step_ less than _stop_; if *step* is negative, the last element is the smallest _start + i * step_ greater than *stop*. + If *step* is positive, the last element is the largest `start + i * step` less than *stop*; + if *step* is negative, the last element is the smallest `start + i * step` greater than *stop*. + If the returned array would contain an infinite number of values, an empty range is returned. - The arguments are not required to be integers.""" + The arguments are not required to be integers. + """ return FunctionExpression("sequence", args) @classmethod def slice(cls, *args) -> FunctionExpression: - """Returns a section of *array* between the *start* and *end* indices. + """ + Returns a section of *array* between the *start* and *end* indices. - If the *end* argument is negative, it is treated as an offset from the end of the array (_length(array) + end_).""" + If the *end* argument is negative, it is treated as an offset from the end of the array `length(array) + end`. + """ return FunctionExpression("slice", args) @classmethod def span(cls, *args) -> FunctionExpression: - """Returns the span of *array*: the difference between the last and first elements, or *array[array.length-1] - array[0]*. + """ + Returns the span of *array*: the difference between the last and first elements, or `array[array.length-1] - array[0]`. Or if input is a string: a section of *string* between the *start* and *end* indices. - If the *end* argument is negative, it is treated as an offset from the end of the string (*length(string) + end*).""" + If the *end* argument is negative, it is treated as an offset from the end of the string `length(string) + end`. + """ return FunctionExpression("span", args) @classmethod @@ -700,41 +823,51 @@ def lower(cls, *args) -> FunctionExpression: @classmethod def pad(cls, *args) -> FunctionExpression: - """Pads a *string* value with repeated instances of a *character* up to a specified *length*. + """ + Pads a *string* value with repeated instances of a *character* up to a specified *length*. If *character* is not specified, a space (' ') is used. By default, padding is added to the end of a string. - An optional *align* parameter specifies if padding should be added to the `'left'` (beginning), `'center'`, or `'right'` (end) of the input string.""" + An optional *align* parameter specifies if padding should be added to the `'left'` (beginning), `'center'`, or `'right'` (end) of the input string. + """ return FunctionExpression("pad", args) @classmethod def parseFloat(cls, *args) -> FunctionExpression: - """Parses the input *string* to a floating-point value. + """ + Parses the input *string* to a floating-point value. - Same as JavaScript's `parseFloat`.""" + Same as JavaScript's `parseFloat`. + """ return FunctionExpression("parseFloat", args) @classmethod def parseInt(cls, *args) -> FunctionExpression: - """Parses the input *string* to an integer value. + """ + Parses the input *string* to an integer value. - Same as JavaScript's `parseInt`.""" + Same as JavaScript's `parseInt`. + """ return FunctionExpression("parseInt", args) @classmethod def replace(cls, *args) -> FunctionExpression: - """Returns a new string with some or all matches of *pattern* replaced by a *replacement* string. + """ + Returns a new string with some or all matches of *pattern* replaced by a *replacement* string. The *pattern* can be a string or a regular expression. If *pattern* is a string, only the first instance will be replaced. - Same as `JavaScript's String.replace `__.""" + Same as `JavaScript's String.replace `__. + """ return FunctionExpression("replace", args) @classmethod def split(cls, *args) -> FunctionExpression: - """Returns an array of tokens created by splitting the input *string* according to a provided *separator* pattern. + """ + Returns an array of tokens created by splitting the input *string* according to a provided *separator* pattern. - The result can optionally be constrained to return at most *limit* tokens.""" + The result can optionally be constrained to return at most *limit* tokens. + """ return FunctionExpression("split", args) @classmethod @@ -749,11 +882,14 @@ def trim(cls, *args) -> FunctionExpression: @classmethod def truncate(cls, *args) -> FunctionExpression: - r"""Truncates an input *string* to a target *length*. + r""" + Truncates an input *string* to a target *length*. The optional *align* argument indicates what part of the string should be truncated: `'left'` (the beginning), `'center'`, or `'right'` (the end). By default, the `'right'` end of the string is truncated. - The optional *ellipsis* argument indicates the string to use to indicate truncated content; by default the ellipsis character `...` (`\\u2026`) is used.""" + The optional *ellipsis* argument indicates the string to use to indicate truncated content; + by default the ellipsis character `...` (`\\u2026`) is used. + """ return FunctionExpression("truncate", args) @classmethod @@ -763,50 +899,63 @@ def upper(cls, *args) -> FunctionExpression: @classmethod def merge(cls, *args) -> FunctionExpression: - """Merges the input objects *object1*, *object2*, etc into a new output object. + """ + Merges the input objects *object1*, *object2*, etc into a new output object. Inputs are visited in sequential order, such that key values from later arguments can overwrite those from earlier arguments. - Example: `merge({a:1, b:2}, {a:3}) -> {a:3, b:2}`.""" + Example: `merge({a:1, b:2}, {a:3}) -> {a:3, b:2}`. + """ return FunctionExpression("merge", args) @classmethod def dayFormat(cls, *args) -> FunctionExpression: - """Formats a (0-6) *weekday* number as a full week day name, according to the current locale. + """ + Formats a (0-6) *weekday* number as a full week day name, according to the current locale. - For example: `dayFormat(0) -> \"Sunday\"`.""" + For example: `dayFormat(0) -> "Sunday"`. + """ return FunctionExpression("dayFormat", args) @classmethod def dayAbbrevFormat(cls, *args) -> FunctionExpression: - """Formats a (0-6) *weekday* number as an abbreviated week day name, according to the current locale. + """ + Formats a (0-6) *weekday* number as an abbreviated week day name, according to the current locale. - For example: `dayAbbrevFormat(0) -> \"Sun\"`.""" + For example: `dayAbbrevFormat(0) -> "Sun"`. + """ return FunctionExpression("dayAbbrevFormat", args) @classmethod def format(cls, *args) -> FunctionExpression: - """Formats a numeric *value* as a string. + """ + Formats a numeric *value* as a string. - The *specifier* must be a valid `d3-format specifier `__ (e.g., `format(value, ',.2f')`.""" + The *specifier* must be a valid `d3-format specifier `__ (e.g., `format(value, ',.2f')`. + """ return FunctionExpression("format", args) @classmethod def monthFormat(cls, *args) -> FunctionExpression: - """Formats a (zero-based) *month* number as a full month name, according to the current locale. + """ + Formats a (zero-based) *month* number as a full month name, according to the current locale. - For example: `monthFormat(0) -> \"January\"`.""" + For example: `monthFormat(0) -> "January"`. + """ return FunctionExpression("monthFormat", args) @classmethod def monthAbbrevFormat(cls, *args) -> FunctionExpression: - """Formats a (zero-based) *month* number as an abbreviated month name, according to the current locale. + """ + Formats a (zero-based) *month* number as an abbreviated month name, according to the current locale. - For example: `monthAbbrevFormat(0) -> \"Jan\"`.""" + For example: `monthAbbrevFormat(0) -> "Jan"`. + """ return FunctionExpression("monthAbbrevFormat", args) @classmethod def timeUnitSpecifier(cls, *args) -> FunctionExpression: - """Returns a time format specifier string for the given time `unit `__. + """ + Returns a time format specifier string for the given time `unit `__. The optional *specifiers* object provides a set of specifier sub-strings for customizing the format; for more, see the `timeUnitSpecifier API documentation `__. @@ -814,102 +963,127 @@ def timeUnitSpecifier(cls, *args) -> FunctionExpression: The resulting specifier string can then be used as input to the `timeFormat `__ or `utcFormat `__ functions, or as the *format* parameter of an axis or legend. - For example: `timeFormat(date, timeUnitSpecifier('year'))` or `timeFormat(date, timeUnitSpecifier(['hours', 'minutes']))`.""" + For example: `timeFormat(date, timeUnitSpecifier('year'))` or `timeFormat(date, timeUnitSpecifier(['hours', 'minutes']))`. + """ return FunctionExpression("timeUnitSpecifier", args) @classmethod def timeFormat(cls, *args) -> FunctionExpression: - """Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to the local time. + """ + Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to the local time. The *specifier* must be a valid `d3-time-format specifier `__. - For example: `timeFormat(timestamp, '%A')`.""" + For example: `timeFormat(timestamp, '%A')`. + """ return FunctionExpression("timeFormat", args) @classmethod def timeParse(cls, *args) -> FunctionExpression: - """Parses a *string* value to a Date object, according to the local time. + """ + Parses a *string* value to a Date object, according to the local time. The *specifier* must be a valid `d3-time-format specifier `__. - For example: `timeParse('June 30, 2015', '%B %d, %Y')`.""" + For example: `timeParse('June 30, 2015', '%B %d, %Y')`. + """ return FunctionExpression("timeParse", args) @classmethod def utcFormat(cls, *args) -> FunctionExpression: - """Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to `UTC `__ time. + """ + Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to `UTC `__ time. The *specifier* must be a valid `d3-time-format specifier `__. - For example: `utcFormat(timestamp, '%A')`.""" + For example: `utcFormat(timestamp, '%A')`. + """ return FunctionExpression("utcFormat", args) @classmethod def utcParse(cls, *args) -> FunctionExpression: - """Parses a *string* value to a Date object, according to `UTC `__ time. + """ + Parses a *string* value to a Date object, according to `UTC `__ time. The *specifier* must be a valid `d3-time-format specifier `__. - For example: `utcParse('June 30, 2015', '%B %d, %Y')`.""" + For example: `utcParse('June 30, 2015', '%B %d, %Y')`. + """ return FunctionExpression("utcParse", args) @classmethod def regexp(cls, *args) -> FunctionExpression: - """Creates a regular expression instance from an input *pattern* string and optional *flags*. + """ + Creates a regular expression instance from an input *pattern* string and optional *flags*. - Same as `JavaScript's `RegExp` `__.""" + Same as `JavaScript's `RegExp` `__. + """ return FunctionExpression("regexp", args) @classmethod def test(cls, *args) -> FunctionExpression: - """Evaluates a regular expression *regexp* against the input *string*, returning `true` if the string matches the pattern, `false` otherwise. + r""" + Evaluates a regular expression *regexp* against the input *string*, returning `true` if the string matches the pattern, `false` otherwise. - For example: `test(/\\d{3}/, \"32-21-9483\") -> true`.""" + For example: `test(\d{3}, "32-21-9483") -> true`. + """ return FunctionExpression("test", args) @classmethod def rgb(cls, *args) -> FunctionExpression: - """Constructs a new `RGB `__ color. + """ + Constructs a new `RGB `__ color. If *r*, *g* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. - If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the RGB color space. Uses `d3-color's rgb function `__.""" + If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the RGB color space. Uses `d3-color's rgb function `__. + """ return FunctionExpression("rgb", args) @classmethod def hsl(cls, *args) -> FunctionExpression: - """Constructs a new `HSL `__ color. + """ + Constructs a new `HSL `__ color. If *h*, *s* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HSL color space. - Uses `d3-color's hsl function `__.""" + Uses `d3-color's hsl function `__. + """ return FunctionExpression("hsl", args) @classmethod def lab(cls, *args) -> FunctionExpression: - """Constructs a new `CIE LAB `__ color. + """ + Constructs a new `CIE LAB `__ color. If *l*, *a* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the LAB color space. - Uses `d3-color's lab function `__.""" + Uses `d3-color's lab function `__. + """ return FunctionExpression("lab", args) @classmethod def hcl(cls, *args) -> FunctionExpression: - """Constructs a new `HCL `__ (hue, chroma, luminance) color. + """ + Constructs a new `HCL `__ (hue, chroma, luminance) color. If *h*, *c* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified. If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HCL color space. - Uses `d3-color's hcl function `__.""" + Uses `d3-color's hcl function `__. + """ return FunctionExpression("hcl", args) @classmethod def luminance(cls, *args) -> FunctionExpression: - """Returns the luminance for the given color *specifier* (compatible with `d3-color's rgb function `__. + """ + Returns the luminance for the given color *specifier* (compatible with `d3-color's rgb function `__. - The luminance is calculated according to the `W3C Web Content Accessibility Guidelines `__.""" + The luminance is calculated according to the `W3C Web Content Accessibility Guidelines `__. + """ return FunctionExpression("luminance", args) @classmethod def contrast(cls, *args) -> FunctionExpression: - """Returns the contrast ratio between the input color specifiers as a float between 1 and 21. + """ + Returns the contrast ratio between the input color specifiers as a float between 1 and 21. - The contrast is calculated according to the `W3C Web Content Accessibility Guidelines `__.""" + The contrast is calculated according to the `W3C Web Content Accessibility Guidelines `__. + """ return FunctionExpression("contrast", args) @classmethod @@ -919,34 +1093,42 @@ def item(cls, *args) -> FunctionExpression: @classmethod def group(cls, *args) -> FunctionExpression: - """Returns the scenegraph group mark item in which the current event has occurred. + """ + Returns the scenegraph group mark item in which the current event has occurred. If no arguments are provided, the immediate parent group is returned. - If a group name is provided, the matching ancestor group item is returned.""" + If a group name is provided, the matching ancestor group item is returned. + """ return FunctionExpression("group", args) @classmethod def xy(cls, *args) -> FunctionExpression: - """Returns the x- and y-coordinates for the current event as a two-element array. + """ + Returns the x- and y-coordinates for the current event as a two-element array. If no arguments are provided, the top-level coordinate space of the view is used. - If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.""" + If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used. + """ return FunctionExpression("xy", args) @classmethod def x(cls, *args) -> FunctionExpression: - """Returns the x coordinate for the current event. + """ + Returns the x coordinate for the current event. If no arguments are provided, the top-level coordinate space of the view is used. - If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.""" + If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used. + """ return FunctionExpression("x", args) @classmethod def y(cls, *args) -> FunctionExpression: - """Returns the y coordinate for the current event. + """ + Returns the y coordinate for the current event. If no arguments are provided, the top-level coordinate space of the view is used. - If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.""" + If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used. + """ return FunctionExpression("y", args) @classmethod @@ -966,37 +1148,47 @@ def inScope(cls, *args) -> FunctionExpression: @classmethod def data(cls, *args) -> FunctionExpression: - """Returns the array of data objects for the Vega data set with the given *name*. + """ + Returns the array of data objects for the Vega data set with the given *name*. - If the data set is not found, returns an empty array.""" + If the data set is not found, returns an empty array. + """ return FunctionExpression("data", args) @classmethod def indata(cls, *args) -> FunctionExpression: - """Tests if the data set with a given *name* contains a datum with a *field* value that matches the input *value*. + """ + Tests if the data set with a given *name* contains a datum with a *field* value that matches the input *value*. - For example: `indata('table', 'category', value)`.""" + For example: `indata('table', 'category', value)`. + """ return FunctionExpression("indata", args) @classmethod def scale(cls, *args) -> FunctionExpression: - """Applies the named scale transform (or projection) to the specified *value*. + """ + Applies the named scale transform (or projection) to the specified *value*. - The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.""" + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection. + """ return FunctionExpression("scale", args) @classmethod def invert(cls, *args) -> FunctionExpression: - """Inverts the named scale transform (or projection) for the specified *value*. + """ + Inverts the named scale transform (or projection) for the specified *value*. - The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.""" + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection. + """ return FunctionExpression("invert", args) @classmethod def copy(cls, *args) -> FunctionExpression: # type: ignore[override] - """Returns a copy (a new cloned instance) of the named scale transform of projection, or `undefined` if no scale or projection is found. + """ + Returns a copy (a new cloned instance) of the named scale transform of projection, or `undefined` if no scale or projection is found. - The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.""" + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection. + """ # error: Signature of "copy" incompatible with supertype "SchemaBase" [override] # note: def copy(self, deep: bool | Iterable[Any] = ..., ignore: list[str] | None = ...) -> expr # NOTE: Not relevant as `expr() -> ExprRef` @@ -1005,131 +1197,168 @@ def copy(cls, *args) -> FunctionExpression: # type: ignore[override] @classmethod def domain(cls, *args) -> FunctionExpression: - """Returns the scale domain array for the named scale transform, or an empty array if the scale is not found. + """ + Returns the scale domain array for the named scale transform, or an empty array if the scale is not found. - The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.""" + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale. + """ return FunctionExpression("domain", args) @classmethod def range(cls, *args) -> FunctionExpression: - """Returns the scale range array for the named scale transform, or an empty array if the scale is not found. + """ + Returns the scale range array for the named scale transform, or an empty array if the scale is not found. - The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.""" + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale. + """ return FunctionExpression("range", args) @classmethod def bandwidth(cls, *args) -> FunctionExpression: - """Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale. + """ + Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale. - The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.""" + The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale. + """ return FunctionExpression("bandwidth", args) @classmethod def bandspace(cls, *args) -> FunctionExpression: - """Returns the number of steps needed within a band scale, based on the *count* of domain elements and the inner and outer padding values. + """ + Returns the number of steps needed within a band scale, based on the *count* of domain elements and the inner and outer padding values. - While normally calculated within the scale itself, this function can be helpful for determining the size of a chart's layout.""" + While normally calculated within the scale itself, this function can be helpful for determining the size of a chart's layout. + """ return FunctionExpression("bandspace", args) @classmethod def gradient(cls, *args) -> FunctionExpression: - """Returns a linear color gradient for the *scale* (whose range must be a `continuous color scheme `__ and starting and ending points *p0* and *p1*, each an *[x, y]* array. + """ + Returns a linear color gradient for the *scale* (whose range must be a `continuous color scheme `__ and starting and ending points *p0* and *p1*, each an `[x, y]` array. - The points *p0* and *p1* should be expressed in normalized coordinates in the domain [0, 1], relative to the bounds of the item being colored. + The points *p0* and *p1* should be expressed in normalized coordinates in the domain `[0, 1]`, relative to the bounds of the item being colored. If unspecified, *p0* defaults to `[0, 0]` and *p1* defaults to `[1, 0]`, for a horizontal gradient that spans the full bounds of an item. - The optional *count* argument indicates a desired target number of sample points to take from the color scale.""" + The optional *count* argument indicates a desired target number of sample points to take from the color scale. + """ return FunctionExpression("gradient", args) @classmethod def panLinear(cls, *args) -> FunctionExpression: - """Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + """ + Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. - The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range. + """ return FunctionExpression("panLinear", args) @classmethod def panLog(cls, *args) -> FunctionExpression: - """Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + """ + Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. - The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range. + """ return FunctionExpression("panLog", args) @classmethod def panPow(cls, *args) -> FunctionExpression: - """Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + """ + Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. - The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range. + """ return FunctionExpression("panPow", args) @classmethod def panSymlog(cls, *args) -> FunctionExpression: - """Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. + """ + Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*. - The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.""" + The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range. + """ return FunctionExpression("panSymlog", args) @classmethod def zoomLinear(cls, *args) -> FunctionExpression: - """Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + """ + Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. - The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range. + """ return FunctionExpression("zoomLinear", args) @classmethod def zoomLog(cls, *args) -> FunctionExpression: - """Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + """ + Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. - The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range. + """ return FunctionExpression("zoomLog", args) @classmethod def zoomPow(cls, *args) -> FunctionExpression: - """Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + """ + Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. - The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range. + """ return FunctionExpression("zoomPow", args) @classmethod def zoomSymlog(cls, *args) -> FunctionExpression: - """Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. + """ + Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*. - The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.""" + The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range. + """ return FunctionExpression("zoomSymlog", args) @classmethod def geoArea(cls, *args) -> FunctionExpression: - """Returns the projected planar area (typically in square pixels) of a GeoJSON *feature* according to the named *projection*. + """ + Returns the projected planar area (typically in square pixels) of a GeoJSON *feature* according to the named *projection*. If the *projection* argument is `null`, computes the spherical area in steradians using unprojected longitude, latitude coordinates. The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. - Uses d3-geo's `geoArea `__ and `path.area `__ methods.""" + Uses d3-geo's `geoArea `__ and `path.area `__ methods. + """ return FunctionExpression("geoArea", args) @classmethod def geoBounds(cls, *args) -> FunctionExpression: - """Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*. + """ + Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*. + + The bounding box is represented by a two-dimensional array: `[[x0, y0], [x1, y1]]`, + where *x0* is the minimum x-coordinate, *y0* is the minimum y-coordinate, + *x1* is the maximum x-coordinate, and *y1* is the maximum y-coordinate. - The bounding box is represented by a two-dimensional array: [[_x0_, _y0_], [_x1_, _y1_]], where *x0* is the minimum x-coordinate, *y0* is the minimum y-coordinate, *x1* is the maximum x-coordinate, and *y1* is the maximum y-coordinate. If the *projection* argument is `null`, computes the spherical bounding box using unprojected longitude, latitude coordinates. The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. - Uses d3-geo's `geoBounds `__ and `path.bounds `__ methods.""" + Uses d3-geo's `geoBounds `__ and `path.bounds `__ methods. + """ return FunctionExpression("geoBounds", args) @classmethod def geoCentroid(cls, *args) -> FunctionExpression: - """Returns the projected planar centroid (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*. + """ + Returns the projected planar centroid (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*. If the *projection* argument is `null`, computes the spherical centroid using unprojected longitude, latitude coordinates. The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection. - Uses d3-geo's `geoCentroid `__ and `path.centroid `__ methods.""" + Uses d3-geo's `geoCentroid `__ and `path.centroid `__ methods. + """ return FunctionExpression("geoCentroid", args) @classmethod def treePath(cls, *args) -> FunctionExpression: - """For the hierarchy data set with the given *name*, returns the shortest path through from the *source* node id to the *target* node id. + """ + For the hierarchy data set with the given *name*, returns the shortest path through from the *source* node id to the *target* node id. - The path starts at the *source* node, ascends to the least common ancestor of the *source* node and the *target* node, and then descends to the *target* node.""" + The path starts at the *source* node, ascends to the least common ancestor of the *source* node and the *target* node, and then descends to the *target* node. + """ return FunctionExpression("treePath", args) @classmethod @@ -1139,9 +1368,11 @@ def treeAncestors(cls, *args) -> FunctionExpression: @classmethod def containerSize(cls, *args) -> FunctionExpression: - """Returns the current CSS box size (`[el.clientWidth, el.clientHeight]`) of the parent DOM element that contains the Vega view. + """ + Returns the current CSS box size (`[el.clientWidth, el.clientHeight]`) of the parent DOM element that contains the Vega view. - If there is no container element, returns `[undefined, undefined]`.""" + If there is no container element, returns `[undefined, undefined]`. + """ return FunctionExpression("containerSize", args) @classmethod @@ -1156,23 +1387,29 @@ def windowSize(cls, *args) -> FunctionExpression: @classmethod def warn(cls, *args) -> FunctionExpression: - """Logs a warning message and returns the last argument. + """ + Logs a warning message and returns the last argument. - For the message to appear in the console, the visualization view must have the appropriate logging level set.""" + For the message to appear in the console, the visualization view must have the appropriate logging level set. + """ return FunctionExpression("warn", args) @classmethod def info(cls, *args) -> FunctionExpression: - """Logs an informative message and returns the last argument. + """ + Logs an informative message and returns the last argument. - For the message to appear in the console, the visualization view must have the appropriate logging level set.""" + For the message to appear in the console, the visualization view must have the appropriate logging level set. + """ return FunctionExpression("info", args) @classmethod def debug(cls, *args) -> FunctionExpression: - """Logs a debugging message and returns the last argument. + """ + Logs a debugging message and returns the last argument. - For the message to appear in the console, the visualization view must have the appropriate logging level set.""" + For the message to appear in the console, the visualization view must have the appropriate logging level set. + """ return FunctionExpression("debug", args) From a33d075dc4c452cef6eccdaf94a894e6257d64a6 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:49:42 +0100 Subject: [PATCH 11/15] docs: add doc for `_ConstExpressionType` --- altair/expr/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py index b07720f9f..7884ab62a 100644 --- a/altair/expr/__init__.py +++ b/altair/expr/__init__.py @@ -14,6 +14,8 @@ class _ConstExpressionType(type): + """Metaclass providing read-only class properties for :class:`expr`.""" + @property def NaN(cls) -> ConstExpression: """Not a number (same as JavaScript literal NaN).""" From f4e7b65667ed93ff5db03da70e29a4bd3332280a Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:05:37 +0100 Subject: [PATCH 12/15] test: make `expr` doctest testable I left this out initially, but by adding static `Parameter` names it is now possible --- altair/expr/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py index 7884ab62a..be9282f88 100644 --- a/altair/expr/__init__.py +++ b/altair/expr/__init__.py @@ -89,12 +89,23 @@ class expr(_ExprRef, metaclass=_ConstExpressionType): Examples -------- >>> import altair as alt + >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value: ") - >>> param_width = alt.param(bind=bind_range) + >>> param_width = alt.param(bind=bind_range, name="param_width") >>> param_color = alt.param( - ... expr=alt.expr.if_(param_width < 200, "red", "black") + ... expr=alt.expr.if_(param_width < 200, "red", "black"), + ... name="param_color", ... ) >>> y = alt.Y("yval").axis(titleColor=param_color) + + >>> y + Y({ + axis: {'titleColor': Parameter('param_color', VariableParameter({ + expr: if((param_width < 200),'red','black'), + name: 'param_color' + }))}, + shorthand: 'yval' + }) """ @override From 08448d1a78cafec41d656c75f603a0f75ac3803e Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:38:07 +0100 Subject: [PATCH 13/15] fix: re-run `generate-schema-wrapper` https://github.com/vega/altair/actions/runs/10005909777/job/27657526013?pr=3466 --- doc/user_guide/api.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst index bc37825af..2fefb10b7 100644 --- a/doc/user_guide/api.rst +++ b/doc/user_guide/api.rst @@ -641,4 +641,3 @@ API Utility Classes When Then ChainedWhen - From 65b527a343b8a9771906f9aa0e96d1e3e90947c2 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:02:26 +0100 Subject: [PATCH 14/15] refactor: Remove `expr` test dependency on constants Fixes https://github.com/vega/altair/pull/3466#issuecomment-2239264620 --- tests/expr/test_expr.py | 53 +++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index 52eee6c19..8e61b3c20 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -1,14 +1,38 @@ +from __future__ import annotations + import operator import sys -import pytest +from inspect import classify_class_attrs, getmembers +from typing import Any, Iterator -from altair import expr -from altair import datum -from altair import ExprRef +import pytest from jsonschema.exceptions import ValidationError -from altair.expr.funcs import NAME_MAP -from altair.expr.funcs import FUNCTION_LISTING -from altair.expr.consts import CONST_LISTING + +from altair import ExprRef, datum, expr +from altair.expr import _ConstExpressionType + +# This maps vega expression function names to the Python name +VEGA_REMAP = {"if_": "if"} + + +def _is_property(obj: Any, /) -> bool: + return isinstance(obj, property) + + +def _get_classmethod_names(tp: type[Any], /) -> Iterator[str]: + for m in classify_class_attrs(tp): + if m.kind == "class method" and m.defining_class is tp: + yield m.name + + +def _remap_classmethod_names(tp: type[Any], /) -> Iterator[tuple[str, str]]: + for name in _get_classmethod_names(tp): + yield VEGA_REMAP.get(name, name), name + + +def _get_property_names(tp: type[Any], /) -> Iterator[str]: + for nm, _ in getmembers(tp, _is_property): + yield nm def test_unary_operations(): @@ -62,10 +86,7 @@ def test_abs(): assert repr(z) == "abs(datum.xxx)" -@pytest.mark.parametrize( - ("veganame", "methodname"), - {nm: (NAME_MAP.get(nm, nm)) for nm in FUNCTION_LISTING}.items(), -) +@pytest.mark.parametrize(("veganame", "methodname"), _remap_classmethod_names(expr)) def test_expr_funcs(veganame: str, methodname: str): """test all functions defined in expr.funcs""" func = getattr(expr, methodname) @@ -73,7 +94,10 @@ def test_expr_funcs(veganame: str, methodname: str): assert repr(z) == f"{veganame}(datum.xxx)" -@pytest.mark.parametrize("constname", CONST_LISTING) +@pytest.mark.parametrize( + "constname", + _get_property_names(_ConstExpressionType), +) def test_expr_consts(constname: str): """Test all constants defined in expr.consts""" @@ -82,7 +106,10 @@ def test_expr_consts(constname: str): assert repr(z) == f"({constname} * datum.xxx)" -@pytest.mark.parametrize("constname", CONST_LISTING) +@pytest.mark.parametrize( + "constname", + _get_property_names(_ConstExpressionType), +) def test_expr_consts_immutable(constname: str): """Ensure e.g `alt.expr.PI = 2` is prevented.""" if sys.version_info >= (3, 11): From ece27388cd6dd11437ac9d00365bd12e4f95d97f Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:14:30 +0100 Subject: [PATCH 15/15] style: remove trailing commas in `@pytest.mark.parametrize` --- tests/expr/test_expr.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index 8e61b3c20..248fe859d 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -94,10 +94,7 @@ def test_expr_funcs(veganame: str, methodname: str): assert repr(z) == f"{veganame}(datum.xxx)" -@pytest.mark.parametrize( - "constname", - _get_property_names(_ConstExpressionType), -) +@pytest.mark.parametrize("constname", _get_property_names(_ConstExpressionType)) def test_expr_consts(constname: str): """Test all constants defined in expr.consts""" @@ -106,10 +103,7 @@ def test_expr_consts(constname: str): assert repr(z) == f"({constname} * datum.xxx)" -@pytest.mark.parametrize( - "constname", - _get_property_names(_ConstExpressionType), -) +@pytest.mark.parametrize("constname", _get_property_names(_ConstExpressionType)) def test_expr_consts_immutable(constname: str): """Ensure e.g `alt.expr.PI = 2` is prevented.""" if sys.version_info >= (3, 11):