scene.viz reúne lo que convierte números en imágenes: espacios de coordenadas tipados, funciones y construcciones de cálculo, series de datos, valores reactivos, gráficos declarativos y campos vectoriales. Todo trabaja en coordenadas de datos: al mover o escalar el espacio, curvas, puntos y etiquetas lo acompañan. Las matrices están en Matrices.

import math
from gaanim import Axis, BLUE, Scene

scene = Scene(frame=(16, 9), background="#0f172a")
plane = scene.viz.cartesian_2d(Axis.linear(-6, 6).ticks(1), Axis.linear(-3, 3).ticks(1))
amplitude = scene.viz.parameter(1.0)
wave = plane.plot(lambda x, a: a * math.sin(x), inputs=[amplitude]).stroke(BLUE, 0.05)
scene.play([plane.animate.create(), wave.animate.create()], duration=1.0)
scene.play([amplitude.animate.set(2.5).duration(1.0)])
scene.render()
animation preview

Elige la herramienta según los datos:

  • Un espacio tipado (cartesian_2d, polar, complex, number_line) cuando las coordenadas tienen significado matemático continuo: funciones, cálculo, campos y geometría propia.
  • ChartSpec cuando cada fila de una tabla es una observación: marcas, codificaciones, escalas y transiciones con identidad estable.

Espacios cartesianos

Un plano con ejes, rejilla, marcas y números que conoce su escala. Todas las curvas y marcas que crea viven en sus coordenadas de datos.

FÁBRICAVisualization.cartesian_2dcartesian_2d(x: Axis, y: Axis, *, width: Optional[float] = None, height: Optional[float] = None, grid: bool = True, axes: bool = True, ticks: bool = True, numbers: bool = True, labels: bool = True, x_axis: Optional[bool] = None, y_axis: Optional[bool] = None, x_grid: Optional[bool] = None, y_grid: Optional[bool] = None, x_ticks: Optional[bool] = None, y_ticks: Optional[bool] = None, x_numbers: Optional[bool] = None, y_numbers: Optional[bool] = None, x_labels: Optional[bool] = None, y_labels: Optional[bool] = None) -> Cartesian2D
Devuelve un Cartesian2D (un CoordinateSpace). numbers controla el texto de las marcas y labels, los títulos de Axis.label. Una capa desactivada sigue disponible como un Drawable vacío, así que el conjunto de capas es estable para componer y animar. Los números negativos usan el signo menos tipográfico (−2). Los colores salen del tema (axes/axis, axes/grid, axes/numbers…).
Parámetros
NombreTipoPredeterminadoDescripción
x / yAxisobligatorioEjes con dominio, marcas y títulos (ver «Ejes»).
width / heightfloat | NoneNoneTamaño en unidades de escena; por defecto, relativo al área segura.
grid / axes / ticks / numbers / labelsboolTrueInterruptores globales de cada capa.
x_axis / y_axis, x_grid / y_grid, x_ticks / y_ticks, x_numbers / y_numbers, x_labels / y_labelsbool | NoneNoneSustituyen el interruptor global en un eje; None lo hereda. El global es axes y el de cada eje x_axis y y_axis, en singular.
Ejemplo
plane = scene.viz.cartesian_2d(
  Axis.linear(-4, 4).ticks(1).label("x"),
  Axis.linear(-2, 2).ticks(1).label("y"),
  grid=False, x_grid=True,
  numbers=False, y_numbers=True,
)
scene.play([plane.animate.write().duration(1.2)])
MÉTODOCoordinateSpace.plotplot(function: Callable[..., float], domain: Optional[tuple[float, float]] = None, *, samples: Optional[int] = None, tolerance: float = 0.0075, derivative: Optional[Callable[..., float]] = None, inputs: Sequence[Parameter | Variable | Computed | TimeInput] = ()) -> Drawable
Gráfica de y = f(x). Con inputs, la curva se regenera cuando cambia el valor resuelto por la línea de tiempo, también al hacer seek. Se muestrea con más detalle si el plano tiene vistas ampliadas con view_to.
Parámetros
NombreTipoPredeterminadoDescripción
functionCallable[..., float]obligatorioRecibe la coordenada x y después los valores de inputs.
domain(float, float) | NoneNoneIntervalo de x; sin él cubre la unión de todas las ventanas de view_to del plano.
samples / toleranceint | None / floatNone / 0.0075Muestras fijas, o muestreo adaptativo con esa tolerancia.
derivativeCallable | NoneNoneDerivada explícita con la misma firma; no hay diferenciación implícita.
inputsSequence[Parameter | Variable | Computed | TimeInput]()Dependencias reactivas en orden.
Ejemplo
plane = scene.viz.cartesian_2d(Axis.linear(-4, 4), Axis.linear(-2, 2))
curve = plane.plot(math.sin, domain=(-math.pi, math.pi)).stroke(BLUE, 0.04)
MÉTODOCoordinateSpace.parametricparametric(function: Callable[..., tuple[float, float]], domain: tuple[float, float], *, samples: Optional[int] = None, tolerance: float = 0.0075, inputs: Sequence[Parameter | Variable | Computed | TimeInput] = ()) -> Drawable
Curva paramétrica t → (x, y) sobre domain, con el mismo muestreo y las mismas inputs que plot.
Ejemplo
plane = scene.viz.cartesian_2d(Axis.linear(-3, 3), Axis.linear(-2, 2))
lissajous = plane.parametric(lambda t: (2 * math.sin(3 * t), 1.5 * math.sin(2 * t)), (0, math.tau))
MÉTODOCoordinateSpace.implicitimplicit(function: Callable[[float, float], float], *, resolution: tuple[int, int] = (96, 64)) -> Drawable
Curva de nivel cero de f(x, y), muestreada en una rejilla de resolution celdas.
Ejemplo
plane = scene.viz.cartesian_2d(Axis.linear(-3, 3), Axis.linear(-2, 2))
ellipse = plane.implicit(lambda x, y: x**2 / 4 + y**2 - 1)
MÉTODOCoordinateSpace.contourcontour(function: Callable[[float, float], float], levels: Sequence[float], *, resolution: tuple[int, int] = (96, 64)) -> Drawable
Curvas de nivel de f(x, y) para cada valor de levels.
Ejemplo
plane = scene.viz.cartesian_2d(Axis.linear(-3, 3), Axis.linear(-2, 2))
rings = plane.contour(lambda x, y: x * x + y * y, levels=[0.5, 1, 2, 3])
MÉTODOCoordinateSpace.layerlayer(name: Literal["grid", "major_grid", "minor_grid", "axis", "axes", "ticks", "numbers", "labels"]) -> Drawable
Devuelve una capa semántica como Drawable para darle estilo o animarla por separado: grid, major_grid, minor_grid, axis, axes, ticks, numbers o labels.
Ejemplo
plane = scene.viz.cartesian_2d(Axis.linear(-4, 4).ticks(1), Axis.linear(-2, 2).ticks(1))
plane.layer("grid").opacity(0.3)
scene.play([plane.layer("axes").animate.create(), plane.layer("numbers").animate.fade_in()])
MÉTODOCoordinateSpace.coord / CoordinateRef.place
Coordenada simbólica (x, y) del espacio. Pásala a Drawable.at_coordinate (o a CoordinateRef.place) para colocar un objeto como hijo del espacio, que seguirá sus cambios de vista.
Ejemplo
plane = scene.viz.cartesian_2d(Axis.linear(-4, 4), Axis.linear(-2, 2))
peak = scene.geometry.dot(0.1).fill(GOLD).at_coordinate(plane.coord(1, 1.5))
label = plane.coord(1, 1.8).place(scene.text("máximo", role="label"))
MÉTODOCoordinateSpace.data_to_scenedata_to_scene(x: float, y: float) -> PointRef
Devuelve un PointRef de escena en el dato (x, y) para objetos que no son hijos del espacio (anotaciones, flechas, conectores). Se resuelve en cada fotograma con la ventana de view_to vigente y con la posición, escala y rotación del plano. Datos no finitos o fuera de escala lanzan ValueError.
Ejemplo
peak = plane.data_to_scene(2, 2)
note = scene.text("máximo").follow(peak, offset=(0.6, 0.4))
arrow = scene.geometry.connector(note, peak)
scene.play([plane.animate.view_to((0, 4), (0, 3)).duration(1.2)])
MÉTODOCoordinateSpace.data_to_local / local_to_datadata_to_local(x, y) -> tuple[float, float] · local_to_data(x, y) -> tuple[float, float]
Convierten entre datos y coordenadas locales del espacio con la vista vigente en el cursor.
MÉTODOCoordinateSpace.view_toview_to(x_domain: tuple[float, float], y_domain: tuple[float, float]) -> CoordinateSpace
Cambia la ventana de datos en el cursor; plane.animate.view_to(...) la anima. El área de trazado no se mueve: ejes, rejillas, marcas y datos se recortan a ella, los títulos quedan fijos y los números y los marcadores de scatter_data conservan su tamaño. Los trazos mantienen su grosor. Los ejes con marcas automáticas regeneran marcas y rejilla con un fundido cruzado; los de paso fijo (.ticks(1)) lo conservan. Aplica los estilos de layer(...) antes del primer cambio de vista. Solo ejes lineales o temporales; otros lanzan ValueError.
Parámetros
NombreTipoPredeterminadoDescripción
x_domain / y_domain(float, float)obligatorioNueva ventana de datos; finita y creciente.
Ejemplo
import math
from gaanim import Axis, GOLD, Scene
scene = Scene(frame=(16, 9), background="#0f172a")
plane = scene.viz.cartesian_2d(Axis.linear(-6, 6), Axis.linear(-3, 3))
curve = plane.plot(lambda x: math.sin(2 * x)).stroke(GOLD, 0.05)
scene.play([plane.animate.create(), curve.animate.create()], duration=0.6)
scene.play([plane.animate.view_to((-1.5, 1.5), (-1.2, 1.2)).duration(1.2)])
scene.render()
animation preview

Las marcas de datos de un espacio cartesiano (plot, parametric, scatter_data, campos…) se recortan siempre al área de trazado, haya o no view_to. mark.no_clip() deja que una marca concreta sobresalga. No se recortan los objetos colocados con at_coordinate ni las marcas de scene.viz.chart.

PROPIEDADCoordinateSpace.animate / drawable / move_to / scale_to / rotate_to / CoordinateSpaceAnimation.create / write / fade_in / fade_out / move_to / scale_to / rotate_to / view_tospace.animate -> CoordinateSpaceAnimation · space.drawable() -> Drawable · space.move_to(x, y) · space.scale_to(factor) · space.rotate_to(radians) -> CoordinateSpace
Proxy CoordinateSpaceAnimation: create() traza las capas con trazos independientes de la resolución, write() construye a la vez ejes, rejillas, marcas, números y títulos, y también ofrece fade_in, fade_out, move_to, scale_to, rotate_to y view_to. move_to, scale_to y rotate_to del propio espacio devuelven el espacio para encadenar, y drawable() da el objeto raíz para layout y estilo.

Ejes

Axis describe dominio, escala, marcas, números, título, cruce y estilo de un eje. Es inmutable: cada método devuelve una copia.

MÉTODOAxis.linear / log / symlog / power / time / categorylinear(min, max) · log(min, max, base=10) · symlog(min, max, *, base=10, threshold=1) · power(min, max, exponent) · time(min_ts, max_ts) · category(values)
Constructores estáticos. Elige según el significado: linear para diferencias uniformes, log para órdenes de magnitud positivos, symlog para datos con signo alrededor de cero, power para potencias, time para marcas de tiempo en segundos y category para grupos discretos.
Ejemplo
frequency = Axis.log(0.1, 1000, base=10).ticks(10).label("frecuencia")
signed = Axis.symlog(-100, 100, threshold=1)
groups = Axis.category(["A", "B", "C"])
MÉTODOAxis.ticksticks(step: float) -> Axis
Marcas principales cada step unidades de datos. Un paso fijo se conserva en cualquier vista.
MÉTODOAxis.auto_ticksauto_ticks() -> Axis
Vuelve al paso automático, que se regenera con cada view_to.
MÉTODOAxis.minor_ticksminor_ticks(subdivisions: int) -> Axis
Divide cada intervalo principal en subdivisions partes con marcas menores.
MÉTODOAxis.numbersnumbers(format: Literal["auto", "fixed", "scientific", "percent", "fraction", "pi", "datetime"] = "auto", precision: int = 2, denominator: int = 4, pattern: Optional[str] = None) -> Axis
Formato de los números: auto, fixed, scientific, percent, fraction, pi o datetime. precision se aplica a fixed, scientific y percent; denominator, a fraction y pi; pattern, a datetime. fraction y pi redondean al múltiplo de 1/denominator más cercano y simplifican la fracción: con denominator=2, pi rotula π/2, π, 3π/2 y 2π, y fraction rotula 1/2, 1 y 3/2.
Ejemplo
angle = Axis.linear(0, 2 * math.pi).ticks(math.pi / 2).numbers("pi", denominator=2)
share = Axis.linear(0, 1).ticks(0.25).numbers("percent", precision=0)
MÉTODOAxis.labellabel(text: str, *, position: Literal["start", "center", "middle", "mid", "end", "top", "bottom"] = "end") -> Axis
Título del eje, separado de las marcas. position="end" (predeterminado) lo pone tras el extremo positivo y "start" tras el negativo; "center" (o "middle", "mid") centra x bajo sus números e y a la izquierda, girado 90°. En un eje vertical, "top" y "bottom" son alias de los extremos.
Ejemplo
y = Axis.linear(0, 1).label("valor relativo", position="center")
MÉTODOAxis.crossingcrossing(value: Literal["auto", "zero", "min", "max", "minimum", "maximum"] | float) -> Axis
Dónde cruza este eje al otro: "auto", "zero", "min", "max" o un valor de datos.
MÉTODOAxis.stylestyle(*, color: Optional[ColorLike] = None, width: Optional[float] = None, tick_length: Optional[float] = None, tick_width: Optional[float] = None, tick_color: Optional[ColorLike] = None, number_color: Optional[ColorLike] = None, label_color: Optional[ColorLike] = None) -> Axis
Sustituye solo las propiedades indicadas (color, width, tick_length, tick_width, tick_color, number_color, label_color); el resto se hereda del tema. Así, cambiar solo width conserva el color del tema.
Ejemplo
x = Axis.linear(-3, 3).ticks(1).style(color=WHITE, width=0.03)
PROPIEDADAxis.domaindomain: tuple[float, float]
Tupla (mínimo, máximo) del eje.

Construcciones de cálculo

Tangentes, secantes, áreas y sumas de Riemann sobre funciones del espacio.

MÉTODOCoordinateSpace.tangenttangent(function: Callable[[float], float], x: float, *, length: Optional[float] = None, dx: Optional[float] = None) -> Drawable
Recta tangente a function en x. length fija la longitud visible y dx el paso de la derivada numérica.
Ejemplo
from gaanim import Axis, BLUE, GOLD, Scene
scene = Scene(frame=(16, 9), background="#0f172a")
plane = scene.viz.cartesian_2d(Axis.linear(-1, 5).ticks(1), Axis.linear(-1, 3).ticks(1))
f = lambda x: 0.15 * x * x + 0.3
curve = plane.plot(f).stroke(BLUE, 0.04)
area = plane.riemann_sum(f, (0, 4), rectangles=8).fill(GOLD).opacity(0.6)
touch = plane.tangent(f, 3, length=3).stroke(GOLD, 0.04)
scene.play([plane.animate.create(), curve.animate.create()], duration=0.6)
scene.play([area.animate.fade_in(), touch.animate.create()], duration=0.8)
scene.render()
animation preview
MÉTODOCoordinateSpace.normalnormal(function: Callable[[float], float], x: float, *, length: Optional[float] = None, dx: Optional[float] = None) -> Drawable
Recta normal a function en x, con las mismas opciones que tangent.
MÉTODOCoordinateSpace.secantsecant(function: Callable[[float], float], x0: float, x1: float) -> Drawable
Recta secante que une los puntos de function en x0 y x1.
Ejemplo
chord = plane.secant(lambda x: 0.15 * x * x, 1, 4)
MÉTODOCoordinateSpace.area_underarea_under(function: Callable[[float], float], domain: tuple[float, float], *, samples: int = 160, baseline: float = 0.0) -> Drawable
Región rellena entre function y baseline sobre domain.
Ejemplo
area = plane.area_under(math.sin, (0, math.pi)).fill(BLUE).opacity(0.5)
MÉTODOCoordinateSpace.riemann_sumriemann_sum(function: Callable[[float], float], domain: tuple[float, float], *, rectangles: int = 12, method: Literal["left", "midpoint", "middle", "right"] = "midpoint", baseline: float = 0.0) -> Drawable
Rectángulos de una suma de Riemann: rectangles de ancho igual y altura evaluada según method (left, midpoint, middle o right).
MÉTODOCoordinateSpace.projectionsprojections(x: float, y: float) -> Drawable
Líneas de proyección desde el punto (x, y) hasta los dos ejes.
Ejemplo
guides = plane.projections(2.5, 1.8)

Series de datos

plot_data y scatter_data dibujan series (xs, ys) directamente en las coordenadas del espacio: si el plano se mueve o escala, la serie lo acompaña.

MÉTODOCoordinateSpace.plot_dataplot_data(xs: Sequence[Optional[float]], ys: Sequence[Optional[float]], *, step: bool = False, baseline: Optional[float] = None, policy: Literal["gap", "drop", "error"] = "gap", color: Optional[ColorLike] = None, width: Optional[float] = None) -> Drawable
Curva de datos medidos. Para recorrer muestras con el tiempo de la escena usa Parameter.drive_from_samples.
Parámetros
NombreTipoPredeterminadoDescripción
xs, ysSequence[float | None]obligatorioSeries de igual longitud; None es una muestra ausente.
stepboolFalseGráfica escalonada.
baselinefloat | NoneNoneCon un valor, rellena el área hasta esa línea base.
policystr"gap"Muestras no finitas: gap corta la línea, drop las omite y error lanza un error.
color / widthColor | None / float | NoneNoneSustituyen el trazo del tema.
Ejemplo
from gaanim import CYAN, Axis, Scene

scene = Scene(frame=(16, 9), background="#0f172a")
plane = scene.viz.cartesian_2d(
  Axis.linear(0, 30).ticks(5).label("tiempo (s)"),
  Axis.linear(-0.4, 0.4).ticks(0.2).label("aceleración (g)"),
  width=12,
  height=4.75,
)
curve = plane.plot_data(times, accel, color=CYAN, width=0.05)
scene.play([plane.animate.create().duration(0.85), curve.animate.create().duration(2.2)])
MÉTODOCoordinateSpace.scatter_datascatter_data(xs: Sequence[Optional[float]], ys: Sequence[Optional[float]], *, radius: float = 0.06, policy: Literal["gap", "drop", "error"] = "gap", color: Optional[ColorLike] = None) -> Drawable
Puntos de radio radius (unidades de escena) en las coordenadas de datos; view_to mueve los centros sin deformarlos. Úsalo para destacar muestras sobre una curva de plot_data.
Ejemplo
peaks = plane.scatter_data(peak_times, peak_values, radius=0.07, color=GOLD)
scene.play([peaks.animate.fade_in()])

Valores reactivos

Magnitudes que conducen otras propiedades. Rust resuelve un snapshot numérico estable para cada instante y llama después a las funciones Python declaradas, así que el seek es exacto.

Elige el tipo según su papel:

  • Parameter: magnitud invisible que conduce otras propiedades.
  • Variable: magnitud que además se muestra como etiqueta = valor.
  • Readout: muestra una cantidad derivada sin ser su fuente.
  • computed(...): escalar calculado a partir de otros.

Las funciones reciben primero las coordenadas y después los valores de inputs=[...], en el orden declarado. Pueden usar math, funciones auxiliares y control de flujo, pero deben ser puras y devolver un escalar finito. Los setters absolutos (move_to, rotate_to, scale_to, opacity y sus variantes 3D) aceptan estas fuentes directamente.

FÁBRICAVisualization.parameterparameter(initial: float) -> Parameter
Escalar invisible y animable. Conduce curvas reactivas, geometría (point_on_curve, always_redraw_arc…) y setters; cada objeto conducido necesita su propia animación de entrada. Valores no finitos lanzan ValueError.
Ejemplo
import math
from gaanim import Axis, Scene

scene = Scene(frame=(16, 9), background="#0f172a")
amplitude = scene.viz.parameter(1.0)
axes = scene.viz.cartesian_2d(Axis.linear(-4, 4), Axis.linear(-2, 2))
curve = axes.plot(lambda x, a: a * math.sin(x), inputs=[amplitude])
scene.play([axes.animate.create(), curve.animate.write(), amplitude.animate.set(2.0).duration(1.2)])
MÉTODOParameter.setset(value: float) -> None
Fija el valor al instante: el valor inicial al declarar o un corte reversible en el cursor después.
PROPIEDADParameter.animateanimate: Anim
Proxy puro: parameter.animate.set(value) devuelve un Anim con duración y easing configurables para scene.play.
PROPIEDADParameter.currentcurrent: float
Valor en el cursor de autoría (el espejo de lo último que fijaste o animaste), no el valor en un instante reproducido.
MÉTODOParameter.add_updater_fnadd_updater_fn(callback: Callable[[float, float, float], float], *, reset: Callable[[], None] | None = None, fixed_dt: float | None = None) -> Parameter
Conduce el escalar con callback(actual, dt, transcurrido) -> valor. Sin reset ni fixed_dt, calcula el valor a partir de transcurrido, como en el ejemplo: un valor que acumula dt vuelve al inicial al hacer seek o capturar fotogramas sueltos. Para simulaciones con estado, pasa reset y un fixed_dt positivo: Gaanim llama a reset() y repite los pasos fijos al hacer seek y al exportar. Las simulaciones de objetos de paso fijo se ejecutan antes, así que el callback ve su estado del mismo fotograma. Resultados no finitos lanzan ValueError.
Ejemplo
phase = scene.viz.parameter(0.0)
phase.add_updater_fn(lambda value, dt, elapsed: 2.0 * elapsed)
hand = scene.geometry.line((0, 0), scene.geometry.polar_point((0, 0), 1.5, phase))
scene.wait(1)
MÉTODOParameter.drive_from_samplesdrive_from_samples(times: Sequence[float], values: Sequence[float], *, interpolation: Literal["linear", "step"] = "linear", scale: float = 1.0, offset: float = 0.0) -> Parameter
Recorre una serie (times, values) de forma nativa: el valor es offset + scale * muestra como función pura del tiempo, sin callbacks. times es relativo al cursor donde haces la llamada; interpolation es "linear" o "step".
Ejemplo
level = scene.viz.parameter(0.0)
level.drive_from_samples([0, 0.5, 1.0, 1.5], [0, 1, 0.4, 0.8], scale=2.0)
readout = scene.viz.readout(level, label="$h$", unit="m")
scene.wait(1.5)
MÉTODOParameter.remove_updaterremove_updater() -> None
Quita el callback o la serie que conduce el parámetro.
FUNCIÓNcomputedcomputed(callback, *, inputs=()) -> Computed
Escalar determinista calculado a partir de inputs, que pueden incluir otros Computed. Las dependencias compartidas reutilizan su caché para el mismo snapshot. Sigue siendo escalar: no devuelve colores, texto ni geometría. Envolver un parámetro de otra escena no permite usarlo aquí.
Ejemplo
radius = scene.viz.parameter(1.0)
area = computed(lambda r: math.pi * r * r, inputs=[radius])
doubled = computed(lambda value: 2 * value, inputs=[area])
scene.viz.readout(doubled, label="2A")
PROPIEDADVisualization.timetime: TimeInput
Tiempo de la línea de tiempo como entrada reactiva explícita; es la misma fuente que scene.time.
Ejemplo
bob = scene.geometry.dot(0.15).fill(GOLD)
bob.move_to(0, computed(lambda t: math.sin(3 * t), inputs=[scene.viz.time]))
scene.wait(2)
FÁBRICAVisualization.variablevariable(initial: float, *, label: str, format: str = ".2f", prefix: str = "", suffix: str = "", unit: Optional[str] = None, font_size: Optional[float] = None, color: Optional[ColorLike] = None, invalid: str = "invalid", decimal_separator: str = ".", spacing: Optional[float] = None, reserve: Optional[float] = None, font: Optional[str] = None, weight: Optional[int] = None) -> Variable
Escalar animable que también se dibuja como etiqueta = valor unidad. Acepta las mismas operaciones que Parameter. Sus partes label, equals, number y unit son Drawable que se estilizan por separado; todos los términos usan font_size (0.48 por defecto) y comparten línea base. color pinta todo, también el número al actualizarse.
Parámetros
NombreTipoPredeterminadoDescripción
initialfloatobligatorioValor inicial.
labelstrobligatorioEtiqueta antes del signo igual; admite matemática $…$.
formatstr".2f"Especificación de formato de Python para un float, como en format(valor, spec): relleno y alineación, signo, z, #, 0, ancho, agrupación , o _, precisión y tipo f, F, e, E, g, G o %. "05.2f" muestra 00.07. Los tipos de enteros o texto (d, x, s…) y las especificaciones mal formadas lanzan ValueError; para enteros usa ".0f". El relleno con espacios no se ve, porque el número se alinea a la derecha por su tinta: para reservar sitio usa reserve.
prefix / suffix / unitstr / str / str | None"" / "" / NoneAfijos y unidad visibles.
decimal_separatorstr".""," muestra 3,14 y convierte la agrupación , en ..
font / weightstr | None / int | NoneNone / NoneFamilia y peso (1 a 1000) del número y de la etiqueta y la unidad en texto plano; una etiqueta $…$ conserva la fuente matemática. Sin ellos, el número usa la fuente matemática del tema. Un peso fuera de rango lanza ValueError.
Ejemplo
from gaanim import RED, Scene

scene = Scene(frame=(16, 9), background="#0f172a")
k = scene.viz.variable(10, label="$k$", format=".0f", color=RED)
scene.play([k.animate.create(), k.animate.set(100).duration(1.5)])
FÁBRICAVisualization.readoutreadout(source: _ReactiveScalar | Callable[..., float], *, inputs: Sequence[Parameter | Variable | Computed | TimeInput] = (), label: Optional[str] = None, format: str = ".2f", prefix: str = "", suffix: str = "", unit: Optional[str] = None, font_size: Optional[float] = None, color: Optional[ColorLike] = None, invalid: str = "invalid", decimal_separator: str = ".", spacing: Optional[float] = None, reserve: Optional[float] = None, font: Optional[str] = None, weight: Optional[int] = None) -> Readout
Lectura numérica reactiva con términos equiespaciados y alineados en la línea base. El número solo se regenera si cambia el texto formateado. Sin color usa el color de texto del tema. Dentro de un layout, su celda mide lo que el texto más ancho que mostrará: el de los valores a los que se fijan o animan sus parámetros (el percent de una encuesta llega a 100) y el de reserve. El número queda alineado a la derecha en ese espacio, así que sus vecinos no se mueven mientras cuenta. variable también acepta reserve.
Parámetros
NombreTipoPredeterminadoDescripción
sourcenúmero | Parameter | Variable | Computed | callableobligatorioEscalar o función pura cuyos argumentos son inputs.
inputsSequence()Dependencias en orden.
invalidstr"invalid"Texto para evaluaciones inválidas o no finitas.
label / format / prefix / suffix / unit / decimal_separator / font / weight——Igual que en variable; un separador que sea dígito, signo, espacio, e o % lanza ValueError.
spacingfloat | NoneNoneEspacio entre etiqueta, =, número y unidad, en unidades de escena. Por defecto, un espacio fino que crece con font_size (0.1 con el tamaño por defecto). prefix y suffix van pegados al número: suffix=" m" conserva su espacio.
reservefloat | NoneNoneValor cuyo texto cabe en la celda de un layout, además de los valores que toman sus parámetros. Úsalo con conteos en vivo sin máximo conocido, como votes (reserve=999). Un valor no finito lanza ValueError.
Ejemplo
import math
from gaanim import Scene

scene = Scene(frame=(16, 9), background="#0f172a")
radius = scene.viz.parameter(1.0)
area = scene.viz.readout(lambda r: math.pi * r**2, inputs=[radius], label="$A$", format=".2f", unit="m²")
scene.play([area.animate.create(), radius.animate.set(3.0).duration(1.5)])
PROPIEDADReadout.label / equals / number / unit / Variable.label / equals / number / unit / set / current / animate / add_updater_fn / remove_updaterlabel · equals · number · unit -> Drawable | None
Partes de Readout y Variable que se estilizan por separado (number siempre existe; las demás valen None si no se pidieron). Variable añade current, set, animate, add_updater_fn y remove_updater, con el mismo comportamiento que en Parameter.
Ejemplo
speed = scene.viz.readout(3.2, label="$v$", unit="m/s")
speed.number.fill(GOLD)
MÉTODOReadout.move_to / Variable.move_tomove_to(x, y=None, anchor: Anchor | TextAnchor | None = None) -> Readout
Coloca la lectura como cualquier objeto: sin anchor, su centro va a (x, y); con un Anchor, ese punto de su caja. Con un TextAnchor la coloca por su línea base, alineada con un Text: TextAnchor.BASELINE_LEFT deja fijo su comienzo aunque el número cambie de ancho. Con un TextAnchor hacen falta x e y, que pueden ser escalares reactivos.
Ejemplo
scene.text("Carga").move_to(-4, 0, TextAnchor.BASELINE_LEFT)
scene.viz.readout(12.5, label="$P$", unit="kN").move_to(-2, 0, TextAnchor.BASELINE_LEFT)

Contador rodante

Cifras que giran como un cuentakilómetros, para dinero, marcadores y cuentas regresivas.

FÁBRICAVisualization.rolling_numberrolling_number(value: float = 0.0, *, decimals: int = 0, min_digits: int = 1, pad: Literal["0", " "] = "0", group_separator: str = "", decimal_separator: str = ".", prefix: str = "", suffix: str = "", show_plus: bool = False, font_family: Optional[str] = None, weight: Optional[int] = None, font_size: float = 0.75, digit_spacing: float = 0.02, line_height: float = 1.25, mode: str = "odometer", direction: str = "up", color: Optional[ColorLike] = None) -> RollingNumber
Las cifras son contornos vectoriales de ancho fijo recortados en su celda. La geometría depende solo del valor actual, así que reproducción, seeks y exportación coinciden. El borde derecho queda anclado; min_digits reserva posiciones para que no crezca durante un acarreo. Con pad=" " esas posiciones quedan vacías: el contador ocupa siempre lo mismo, el prefijo no se desplaza y una frase con el valor final queda centrada para todos los valores. En modo continuous, las ruedas se asientan durante el primer y el último 15 % de cada animación del valor. Opciones inválidas lanzan ValueError; si un driver sale del rango, se muestra una raya.
Parámetros
NombreTipoPredeterminadoDescripción
valuefloat0.0Valor inicial finito; su magnitud por 10**decimals debe ser menor que 1e15.
decimals / min_digitsint0 / 1De 0 a 6 decimales y de 1 a 15 posiciones enteras reservadas; la suma no supera 15.
padstr"0"Relleno de las posiciones reservadas sin usar: "0" dibuja ceros iniciales y " " deja celdas vacías del mismo ancho, sin dibujar sus separadores de miles.
group_separator / decimal_separatorstr"" / "."Cero o un carácter, y un carácter distinto.
prefix / suffix / show_plusstr / str / bool"" / "" / FalseAfijos de una línea (256 bytes UTF-8 en total) y signo positivo visible.
font_family / weightstr | None / int | NoneNoneFamilia y peso (1 a 1000), resueltos como en scene.text; sin familia hereda la de cuerpo.
font_size / digit_spacing / line_heightfloat0.75 / 0.02 / 1.25Tamaño, espacio entre celdas y altura de la ventana como múltiplo de la altura de las cifras (al menos 1).
modestr"odometer"odometer arrastra las ruedas superiores durante la última unidad antes del acarreo; continuous gira cada rueda a la velocidad de su posición.
directionstr"up"up o down para magnitudes crecientes; al disminuir se invierte.
colorColor | NoneNoneColor de cifras, signo y afijos; sin él, el del tema.
Ejemplo
from gaanim import Easing, Scene

scene = Scene(frame=(16, 9), background="#0f172a")
counter = scene.viz.rolling_number(
    98, min_digits=4, prefix="$ ", group_separator=",",
).move_to(0, 0)
scene.play([counter.count_to(1250, duration=3).easing(Easing.SMOOTH)])
scene.play([counter.animate.set(500).duration(2)])
designs = scene.viz.rolling_number(0, min_digits=8, pad=" ", group_separator=" ").move_to(0, -1.5)
scene.play([designs.count_to(14_000_605, duration=3)])
counter.set(0)  # Corte reversible en el cursor actual.
scene.wait(0.5)
scene.render()
MÉTODORollingNumber.count_tocount_to(value: float, *, duration: float = 1.0, snap: bool = False) -> Anim
Animación de cuenta para scene.play, compatible con easing y retardo. El destino se usa tal cual: una fracción de la unidad mínima deja la rueda entre dos cifras. snap=True redondea al valor representable más cercano, así que termina asentado y current coincide con lo visible.
Ejemplo
mean = scene.viz.rolling_number(0, decimals=1)
scene.play([mean.count_to(61.7956, snap=True)])  # Termina en 61.8.
MÉTODORollingNumber.setset(value: float, *, snap: bool = False) -> RollingNumber
Fija el valor al instante (un corte reversible tras la declaración) y devuelve el contador. Acepta snap como count_to.
MÉTODORollingNumber.move_tomove_to(x: Any, y: Any = None, anchor: AnchorName | Anchor | TextAnchor | None = None) -> RollingNumber
Coloca el contador. Con TextAnchor.BASELINE_LEFT, BASELINE_CENTER o BASELINE_RIGHT, la línea base de las cifras queda en y, para alinearlo con texto; un Anchor usa la ventana completa, con el margen de line_height.
Ejemplo
number = scene.viz.rolling_number(2, min_digits=2, font_size=1)
number.move_to(-5, 1.12, TextAnchor.BASELINE_LEFT)
scene.text("Objetivos", size=1).move_to(-3.5, 1.12, TextAnchor.BASELINE_LEFT)
PROPIEDADRollingNumber.visual / parameter / current / animate / fill / opacityvisual: Drawable · parameter: Parameter · current: float · animate
visual es el objeto dibujado: usa counter.visual.animate para posición, opacidad o entradas (sin entrada, el contador es visible desde su declaración). parameter es el escalar subyacente, reutilizable en computed, lecturas o drive_from_samples. animate.set(value) anima el valor como en un Parameter. move_to, fill, opacity y set devuelven el contador; otros métodos heredados pueden devolver su Drawable.

Anillos de progreso y temporizadores

Un arco que se llena en el sentido del reloj desde las 12, con una pista tenue detrás y un contador rodante en el centro. Todo sigue a un único Parameter.

FÁBRICAVisualization.progress_ringprogress_ring(value: float = 0.0, *, radius: float = 1.0, width: float = 0.12, color: Optional[ColorLike] = None, track: bool = True, track_color: Optional[ColorLike] = None, label: bool = True, decimals: int = 0, label_color: Optional[ColorLike] = None, font_size: float = 0.5) -> ProgressRing
El arco tiene extremos redondeados y su caja es el anillo completo, así que la maquetación no sigue al barrido. Anima el valor con animate.set(...); la geometría depende solo del valor actual, así que reproducción, seeks y exportación coinciden. arc, track y label dan acceso a cada parte. Tamaños no positivos o un valor no finito lanzan ValueError.
Parámetros
NombreTipoPredeterminadoDescripción
valuefloat0.0Progreso inicial; el anillo muestra value recortado a [0, 1].
radius / widthfloat1.0 / 0.12Radio del anillo y grosor del trazo, en unidades de escena.
colorColor | NoneNoneColor del arco; sin él, el acento del tema.
track / track_colorbool / Color | NoneTrue / NoneDibuja un círculo tenue completo detrás del arco, con otro color si se indica.
label / decimalsbool / intTrue / 0Muestra el porcentaje con un contador rodante de 0 a 6 decimales.
label_color / font_sizeColor | None / floatNone / 0.5Color y tamaño del porcentaje; sin color, el del texto.
Ejemplo
from gaanim import Scene
scene = Scene(frame=(16, 9), background="#0f172a")
ring = scene.viz.progress_ring(0.0, radius=1.6, width=0.18)
scene.play([ring.animate.set(0.75).duration(1.5)])
scene.render()
animation preview
FÁBRICAVisualization.countdowncountdown(seconds: float, *, radius: float = 1.0, width: float = 0.12, color: Optional[ColorLike] = None, track: bool = True, track_color: Optional[ColorLike] = None, label: bool = True, label_color: Optional[ColorLike] = None, font_size: float = 0.6) -> ProgressRing
Un ProgressRing que empieza lleno, con los segundos restantes rodando en el centro. count_down() lo vacía a velocidad constante. El resto de opciones son las de progress_ring. Unos seconds no positivos lanzan ValueError.
Parámetros
NombreTipoPredeterminadoDescripción
secondsfloatobligatorioDuración de la cuenta; también es el máximo del anillo.
Ejemplo
from gaanim import Scene
scene = Scene(frame=(16, 9), background="#0f172a")
timer = scene.viz.countdown(3, radius=1.6, width=0.18)
scene.play([timer.count_down()])
scene.render()
animation preview
MÉTODOProgressRing.count_downcount_down(duration: Optional[float] = None) -> Anim
Con countdown(10) dura diez segundos reales. Una duración negativa lanza ValueError.
Parámetros
NombreTipoPredeterminadoDescripción
durationfloat | NoneNoneSegundos de la animación; por defecto, el valor actual.
Devuelve: Anim — El valor bajando hasta cero con easing lineal.
PROPIEDADProgressRing.parameter / visual / arc / track / label / maximum / current / set / animateparameter: Parameter · visual: Drawable · arc: Drawable · track: Drawable | None · label: Drawable | None · maximum: float · current: float · set(value) · animate
parameter es el escalar que mueve el arco y la etiqueta, reutilizable en computed o lecturas. maximum es 1.0 en un anillo y los segundos en una cuenta atrás. set fija el valor al instante (un corte reversible tras la declaración) y animate.set(value) lo anima. visual es el grupo de las tres partes: usa visual.animate para moverlo o desvanecerlo. move_to, shift_by y opacity devuelven el anillo. La etiqueta se asienta en cifras enteras: redondea el porcentaje (la cuenta atrás redondea los segundos hacia arriba) y gira a la cifra siguiente cuando el valor la cruza, así que un valor como 1/3 muestra 33% sin quedarse entre dos cifras.

Carrera de barras

Una bar chart race: barras ordenadas por valor que crecen, se adelantan unas a otras y salen del ranking. Todo es función pura de una única posición de fotograma clave, así que un seek muestra lo mismo que la reproducción continua.

FÁBRICAVisualization.bar_racebar_race(frames: Mapping[Any, Mapping[str, float]] | Sequence[tuple[Any, Mapping[str, float]]], *, top: int = 10, rank_smoothing: float = 0.3, value_format: str = "{:,.0f}", width: float = 10.0, height: float = 6.0, label_width: Optional[float] = None, bar_gap: float = 0.18, colors: Mapping[str, ColorLike] | Sequence[ColorLike] | None = None, label_color: Optional[ColorLike] = None, font_size: Optional[float] = None, ticker: bool = True, ticker_size: Optional[float] = None, ticker_color: Optional[ColorLike] = None) -> BarRace
Los valores se interpolan linealmente entre fotogramas clave. Cuando dos barras se cruzan, intercambian sus puestos con un movimiento suavizado en vez de saltar; entre cruces, cada barra ocupa su propio puesto. Las barras que salen del top se desvanecen por debajo del último puesto y la barra más larga ocupa siempre todo el ancho disponible. Los nombres van en una columna a la izquierda y un contador rodante (rolling_number) sigue el extremo de cada barra. Si todas las etiquetas son números enteros, como años, el ticker rueda entre ellos; si no, cambia de texto con un fundido. Datos vacíos o no finitos, nombres repetidos, un value_format no admitido o tamaños no positivos lanzan ValueError; otra forma de frames, TypeError.
Parámetros
NombreTipoPredeterminadoDescripción
framesMapping[Any, Mapping[str, float]] | Sequence[tuple[Any, Mapping[str, float]]]obligatorioCada etiqueta de fotograma clave (un año, una fecha…) con los {nombre: valor} de ese instante, en orden. Un nombre ausente vale cero en ese fotograma.
topint10Puestos visibles; el líder arriba.
rank_smoothingfloat0.3Fotogramas clave (de 0 a 2) que dura el intercambio de dos barras, centrado en el instante en que se cruzan y acortado para que termine antes de que esas dos barras vuelvan a cruzarse; con 0 cambian de puesto al instante.
value_formatstr"{:,.0f}"Campo de formato de Python con texto opcional alrededor: "{}", "{:,.0f}", "{:_.2f}" o "${:,.1f} M", hasta 6 decimales.
width / heightfloat10.0 / 6.0Tamaño de toda la carrera, centrada en su origen, en unidades de escena.
label_width / bar_gapfloat | None / floatNone / 0.18Ancho de la columna de nombres (por defecto el 22% de width) y fracción vacía de cada puesto. Un nombre más ancho que la columna se dibuja más pequeño, hasta que cabe.
colorsMapping[str, Color] | Sequence[Color] | NoneNoneColores por nombre o una lista que vuelve a empezar como una paleta; el resto toma la paleta del tema (Theme(palette=...)).
label_color / font_sizeColor | None / float | NoneNone / NoneColor y tamaño de nombres y valores; por defecto el color de texto del tema y el 55% del grosor de la barra, sin pasar del 12% de height para que las carreras de pocos puestos sigan legibles.
ticker / ticker_size / ticker_colorbool / float | None / Color | NoneTrue / None / NoneEtiqueta del fotograma clave abajo a la derecha, por defecto al 15% de height y con el color atenuado del tema.
Ejemplo
from gaanim import Scene
scene = Scene(frame=(16, 9), background="#0f172a")
frames = {
    2020: {"Ana": 12, "Luis": 30, "Marta": 18, "Noé": 5},
    2021: {"Ana": 35, "Luis": 34, "Marta": 22, "Noé": 20},
    2022: {"Ana": 48, "Luis": 40, "Marta": 51, "Noé": 38},
    2023: {"Ana": 60, "Luis": 44, "Marta": 58, "Noé": 71},
}
race = scene.viz.bar_race(frames, top=3, width=12, height=6)
scene.play([race.animate.play().duration(4)])
scene.render()
animation preview
MÉTODOBarRaceAnimation.play / toplay() -> Anim · to(position: float) -> Anim
race.animate.play() avanza desde la posición actual hasta el último fotograma clave a ritmo constante, un segundo por fotograma pendiente salvo que encadenes .duration(segundos). to(position) va a una posición concreta: 0 es el primer fotograma clave y las posiciones fraccionarias quedan entre dos. Una posición fuera de [0, frame_count - 1] lanza ValueError.
PROPIEDADBarRace.parameter / position / frame_count / names / ticker / bar / set / visualparameter: Parameter · position: float · frame_count: int · names: list[str] · ticker: Drawable | None · bar(name) -> Drawable · set(position) · visual: Drawable
parameter es la posición de fotograma clave, reutilizable en computed o lecturas. bar(name) devuelve la fila de una barra (barra, nombre y valor), que se mueve entre puestos y se desvanece al salir del top; su propia opacidad queda libre, así que race.bar(name).animate.opacity(0.3) la atenúa además de ese desvanecimiento. Un nombre desconocido lanza KeyError. set salta a una posición (un corte reversible tras la declaración). visual es el grupo de todas las filas y el ticker; move_to, shift_by y opacity devuelven la carrera.

Recta numérica, plano polar y plano complejo

Espacios tipados de una dimensión, polares y complejos.

FÁBRICAVisualization.number_linenumber_line(axis: Axis, *, length: Optional[float] = None, axis_visible: bool = True, ticks: bool = True, numbers: bool = True, labels: bool = True) -> NumberLine
Recta numérica tipada. axis_visible, ticks, numbers y labels controlan cada componente; los componentes ocultos siguen disponibles como capas vacías y la conversión de datos sigue activa.
Ejemplo
line = scene.viz.number_line(Axis.linear(0, 10).ticks(1), length=10)
MÉTODONumberLine.point_refpoint_ref(value: _ReactiveScalar, *, normal_offset: Optional[_ReactiveScalar] = None) -> PointRef
PointRef reactivo que sigue a la recta cuando se mueve, rota o escala. Las escalas categóricas rechazan valores reactivos con ValueError.
Parámetros
NombreTipoPredeterminadoDescripción
valuefloat | Parameter | Variable | ComputedobligatorioValor convertido con la escala de la recta.
normal_offsetfloat | fuente reactiva | NoneNoneDesplazamiento perpendicular en unidades locales.
MÉTODONumberLine.functionfunction(function: Callable[..., float], domain: Optional[tuple[float, float]] = None, *, normal_scale: float = 1.2, reveal: Optional[_ReactiveScalar] = None, samples: Optional[int] = None, tolerance: float = 0.0075, inputs: Sequence[Parameter | Variable | Computed | TimeInput] = ()) -> Drawable
Grafica una función perpendicular a la recta, con muestreo y actualización en Rust. Un reveal reactivo puede compartir el mismo escalar que un punto móvil sin desfases de longitud de arco.
Parámetros
NombreTipoPredeterminadoDescripción
functionCallable[..., float]obligatorioRecibe la coordenada y después inputs.
normal_scalefloat1.2Distancia local para una salida igual a 1.
revealfloat | fuente reactiva | NoneNoneExtremo visible exacto, en coordenadas de datos.
Ejemplo
import math
from gaanim import Axis, Scene, computed

scene = Scene(frame=(16, 9), background="#0f172a")
theta = scene.viz.parameter(0.0)
line = scene.viz.number_line(
  Axis.linear(0, 3 * math.pi).ticks(math.pi).numbers("pi", denominator=1),
  length=9.5,
)
curve = line.function(lambda t: math.sin(t), normal_scale=1.2, reveal=theta)
point = scene.geometry.dot(0.1).follow(
  line.point_ref(theta, normal_offset=computed(lambda t: 1.2 * math.sin(t), inputs=[theta]))
)
scene.play([line.animate.create(), curve.animate.fade_in().duration(0.01), point.animate.fade_in()])
scene.play([theta.animate.set(3 * math.pi).duration(4)])
MÉTODONumberLine.coord / data_to_local / layer / drawable / animatecoord(value) -> CoordinateRef · data_to_local(value) -> float · layer(name) -> Drawable · drawable() -> Drawable · animate
Coordenada simbólica, conversión a coordenada local y capas axis, ticks, numbers y labels.
FÁBRICAVisualization.polarpolar(radial: Axis, *, radius: float = 2.2, angle_divisions: int = 12, grid: bool = True, axes: bool = True, numbers: bool = True, labels: bool = True, rings: Optional[bool] = None, spokes: Optional[bool] = None) -> PolarSpace
Espacio polar de radio radius con angle_divisions radios. rings y spokes heredan grid si se omiten; numbers afecta a los valores radiales y labels, al título radial.
Ejemplo
polar = scene.viz.polar(Axis.linear(0, 2).ticks(0.5).label("r"), radius=3)
rose = polar.plot(lambda angle: 2 * abs(math.cos(3 * angle))).stroke(GOLD, 0.04)
MÉTODOPolarSpace.plotplot(function: Callable[[float], float], domain: tuple[float, float] = (0.0, 6.283185307179586), *, samples: int = 360) -> Drawable
Curva polar r = f(θ) sobre domain (una vuelta completa por defecto).
MÉTODOPolarSpace.coord / layer / drawable / animatecoord(radius, angle) -> CoordinateRef · layer(name) -> Drawable · drawable() -> Drawable · animate
Coordenada simbólica polar y capas grid, axes, numbers y labels.
FÁBRICAVisualization.complexcomplex(x: Optional[Axis] = None, y: Optional[Axis] = None, *, width: Optional[float] = None, height: Optional[float] = None, grid: bool = True, axes: bool = True, ticks: bool = True, numbers: bool = True, labels: bool = True, x_axis: Optional[bool] = None, y_axis: Optional[bool] = None, x_grid: Optional[bool] = None, y_grid: Optional[bool] = None, x_ticks: Optional[bool] = None, y_ticks: Optional[bool] = None, x_numbers: Optional[bool] = None, y_numbers: Optional[bool] = None, x_labels: Optional[bool] = None, y_labels: Optional[bool] = None) -> ComplexSpace
Plano complejo cartesiano. Sin ejes, crea Re e Im; los interruptores y su precedencia son los de cartesian_2d, y devuelve el mismo tipo de espacio.
Ejemplo
plane = scene.viz.complex(labels=False)
z = scene.geometry.dot(0.1).fill(GOLD).at_coordinate(plane.coord(1, 1))

Gráficos declarativos

ChartSpec es una receta inmutable: datos, marca, codificaciones, ejes y guías. scene.viz.chart(spec) la materializa en capas y chart.animate.to(otra) anima el paso de una receta a otra.

from gaanim import Axis, ChartSpec, Field, Guide, Scale, Scene, Value

scene = Scene(frame=(16, 9), background="#0f172a")
spec = (
  ChartSpec({
    "id": ["a", "b", "c", "d"],
    "x": [-2, -0.5, 1, 2.5],
    "y": [1, 3, 2, 3.5],
    "group": ["A", "B", "A", "B"],
  }, key="id")
  .mark("point")
  .encode(x="x", y="y", color=Field("group", scale=Scale.category()), size=Value(0.12))
  .axes(x=Axis.linear(-3, 3).ticks(1).label("x"), y=Axis.linear(0, 4).ticks(1).label("y"))
  .guides(color=Guide.legend(title="Grupo"))
)
chart = scene.viz.chart(spec)
scene.play([chart.layer("axes").animate.create().duration(0.8)])
scene.play([chart.layer("marks").animate.fade_in(), chart.layer("guides").animate.fade_in()], duration=0.6)
scene.render()
animation preview
CLASEChartSpecChartSpec(data, *, key=None)
Receta de gráfico. Una mutación externa posterior de los datos no cambia el resultado: los seeks anteriores siguen siendo iguales. Cada método devuelve una receta nueva, así que puedes derivar varios estados de una base.
Parámetros
NombreTipoPredeterminadoDescripción
datamapping | dataframe | DataTable | DataSourceobligatorioSe copia al instante como instantánea inmutable propia.
keystr | NoneNoneColumna de identidad estable para las transiciones; nulos o duplicados lanzan un error.
Ejemplo
base = ChartSpec({"category": ["a", "b", "c"], "time": [0, 1, 2], "value": [3, 1, 2]})
bars = base.mark("bar").encode(x="category", y="value")
points = base.mark("point").encode(x="time", y="value")
MÉTODOChartSpec.markmark(kind: ChartMark, **options: float | str | Color) -> ChartSpec
Tipo de marca: point, line, step, area, bar, histogram, box, violin, error_bar, heatmap, surface o arc. Las opciones dependen de la marca: las barras aceptan width (fracción de la banda en ejes categóricos), label_position ("outside" o "inside"), label_offset y label_color; los puntos, radius. arc dibuja una tarta sin ejes, una porción por fila según su parte del total de theta, en el sentido del reloj desde las 12; acepta inner_radius (fracción del radio que queda vacía, en [0, 1): un donut si es mayor que 0), pad_angle (radianes vacíos entre porciones), start_angle (radianes en sentido antihorario desde el eje x; math.pi / 2 por defecto), radius (unidades de escena) y label_color. Las marcas rellenas (bar, histogram, area, point, box, violin y las celdas de heatmap) se dibujan sin borde; para trazarlo, usa chart.layer("marks").stroke(...).
Ejemplo
bars = (
  ChartSpec({"method": ["one", "two"], "elapsed": [40, -10], "kind": ["new", "old"]})
  .mark("bar", width=0.72, label_position="outside", label_offset=0.2)
  .encode(
    x="method", y="elapsed", label="elapsed",
    color=Field("kind", scale=Scale.category().colors([BLUE, GOLD])),
  )
)
MÉTODOChartSpec.encodeencode(*, x: Optional[EncodingLike] = None, y: Optional[EncodingLike] = None, z: Optional[EncodingLike] = None, color: Optional[EncodingLike] = None, size: Optional[EncodingLike] = None, opacity: Optional[EncodingLike] = None, label: Optional[EncodingLike] = None, theta: Optional[EncodingLike] = None, radius: Optional[EncodingLike] = None) -> ChartSpec
Asigna canales (x, y, z, color, size, opacity, label, theta, radius). Cada canal acepta un nombre de columna, Field(columna, scale=...) o Value(constante). En point, size es el radio en unidades de escena (0.06 por defecto); un Field numérico lo reparte entre 0.03 y 0.12 con área proporcional. color y opacity con Field valen por fila; en line, step y area, un color categórico dibuja una serie por categoría. theta y radius son de arc: theta (obligatorio, no negativo; un valor ausente no ocupa sitio) da a cada fila su parte del círculo, y un campo radius dibuja un gráfico de rosa, con cada porción desde el radio interior hacia el exterior en proporción a su valor.
MÉTODOChartSpec.axesaxes(*, x: Optional[Axis] = None, y: Optional[Axis] = None, z: Optional[Axis] = None) -> ChartSpec
Ejes explícitos; los omitidos se infieren. Un dominio explícito se respeta tal cual. Al inferirlos en 2D, el dominio se amplía para que quepan las marcas: las barras incluyen la línea base y media barra de margen, los mapas de calor media celda, el área su línea base, las barras de error su rango más un 5 % y los puntos y líneas un 5 % del rango.
MÉTODOChartSpec.guidesguides(*, color: Optional[Guide] = None, size: Optional[Guide] = None, opacity: Optional[Guide] = None) -> ChartSpec
Leyendas y barras de color para los canales color, size y opacity, derivadas de sus escalas.
MÉTODOChartSpec.validatevalidate() -> None
Comprueba canales obligatorios, columnas, clave, escalas y opciones, y lanza un error antes de materializar.
PROPIEDADChartSpec.keykey: Optional[str]
Columna de identidad de las transiciones, o None.
MÉTODOScale.linear / log / symlog / power / time / categorylinear(domain=None, *, clamp=False) · log(domain=None, *, base=10, clamp=False) · symlog(domain=None, *, base=10, threshold=1, clamp=False) · power(domain=None, *, exponent=1, clamp=False) · time(domain=None, *, clamp=False) · category(values=None)
Escala de un canal codificado con Field. La misma escala gobierna posiciones, colores, leyendas y barras de color. category sin valores los infiere en orden de aparición.
Ejemplo
color = Field("temperature", scale=Scale.symlog((-100, 100), threshold=1))
kind = Field("kind", scale=Scale.category(["new", "old"]).colors(["#38bdf8", "#f97316"]))
MÉTODOScale.colorscolors(colors: PaletteLike) -> Scale
Copia de la escala con un rango ordenado de colores: una Palette, el nombre de una paleta ("okabe_ito") o colores (Color, cadenas CSS/hex o tuplas RGB(A), incluso mezclados). Colores inválidos, nombres desconocidos o una lista vacía lanzan ValueError. En un campo de texto, los colores se reparten entre las categorías por turno y vuelven a empezar como una paleta; sin ellos, las categorías toman la paleta del tema. En un campo numérico forman una rampa. En un heatmap, los colores y el dominio de la escala del canal color sustituyen a la paleta heatmap del tema solo en ese gráfico, y bands= cuantiza sobre ellos.
CLASEFieldField(column, *, scale=None)
Codifica un canal con una columna, opcionalmente con su escala. Úsalo cuando el valor cambia por fila.
CLASEValueValue(value)
Constante para todo un canal: un número finito, una cadena o un color. Por ejemplo, Value(BLUE) da un único color a toda la serie.
MÉTODOGuide.legend / colorbar / disabledlegend(*, title=None) · colorbar(*, title=None) · disabled()
legend lista cada categoría con una muestra de su color, en el orden de Scale.category o de aparición; colorbar es la guía continua; disabled oculta la guía de un canal.
CLASEDataSource.replace / append / version / DataTable.columnsDataSource(data, *, key=None) · replace(data) · append(data) · version
Tabla reemplazable. Tras replace o append, construye un ChartSpec nuevo para capturar la nueva versión inmutable; version cuenta los cambios. DataTable(columns) es la tabla inmutable equivalente, con columns y len().
Ejemplo
source = DataSource({"x": [0, 1], "y": [2, 3]})
before = ChartSpec(source).mark("line").encode(x="x", y="y")
source.append({"x": [2], "y": [5]})
after = ChartSpec(source).mark("line").encode(x="x", y="y")
FÁBRICAVisualization.chartchart(spec: ChartSpec) -> Chart
Materializa la receta en un Chart con capas semánticas por lotes: las marcas se agrupan por estilo resuelto en lugar de crear una entidad por registro.
MÉTODOChart.layerlayer(name: Literal["marks", "axes", "grid", "guides", "labels"]) -> Drawable
Capa estable como Drawable: marks, axes, grid, guides o labels. La capa axes reúne rejilla, ejes, marcas, números y títulos, así que chart.layer("axes").animate.create() la mantiene oculta hasta su entrada. Las tres últimas son opcionales: grid existe si los ejes dibujan rejilla, guides si la especificación tiene leyenda o título de guía (ChartSpec.guides) y labels en gráficos de barras o de arcos con el canal label. La capa axes de un gráfico de arcos está vacía. Pedir una que el gráfico no tiene lanza ValueError con las capas que sí tiene.
Ejemplo
chart = scene.viz.chart(ChartSpec({"x": [0, 1, 2], "value": [18, 42, 31]}).mark("bar").encode(x="x", y="value"))
scene.play([chart.layer("axes").animate.create(), chart.layer("marks").animate.fade_in()])
MÉTODOChart.drawabledrawable() -> Drawable
Objeto raíz del gráfico, para layout, estilo y animaciones genéricas. La opacidad se propaga por todas las capas, incluidas las mallas 3D.
MÉTODOChartAnimation.growgrow() -> Composition
Entrada de un gráfico de arcos, desde chart.animate.grow(): una manecilla gira en el sentido del reloj desde el ángulo inicial, acelerando y frenando, y cada porción se abre en su sitio cuando la manecilla la cruza. La etiqueta de una porción aparece desde que la manecilla pasa por su centro hasta que la porción se completa; las guías aparecen a lo largo de toda la entrada. En otro tipo de gráfico lanza ValueError.
Devuelve: Composition — Las animaciones de la entrada, en paralelo.
Ejemplo
sales = {"region": ["North", "South", "East"], "sales": [42, 28, 18]}
spec = ChartSpec(sales, key="region").mark("arc", inner_radius=0.55).encode(theta="sales", color="region", label="region")
chart = scene.viz.chart(spec)
scene.play([chart.animate.grow()], duration=1.5)
later = ChartSpec({"region": ["North", "South", "West"], "sales": [30, 35, 20]}, key="region")
scene.play([chart.animate.to(later.mark("arc", inner_radius=0.55).encode(theta="sales", color="region", label="region"))])
MÉTODOChartAnimation.toto(target: ChartSpec, *, match_: Literal["key", "index"] = "key", fallback: Literal["error", "crossfade"] = "error") -> Anim
Transición accesible desde chart.animate.to(...). Entre gráficos 2D, las marcas, ejes y etiquetas que se corresponden se transforman y el resto se funde; al terminar, el gráfico de destino sustituye al original. point, line y bar pueden pasar entre 2D y 3D (con un fundido), y heatmap y surface comparten rejilla. Entre dos gráficos de arcos, cada porción barre sus ángulos y radios hasta la porción con su clave (su fila con match_="index"); una porción sin pareja se cierra sobre su ángulo medio o se abre desde él. Nunca mueve la cámara global.
Parámetros
NombreTipoPredeterminadoDescripción
targetChartSpecobligatorioReceta de destino.
match_str"key""key" relaciona elementos por la columna de identidad (ambas recetas deben declararla); sin clave, pide "index".
fallbackstr"error"Con familias de marca incompatibles: "error" o "crossfade".
Ejemplo
target = spec.encode(z="height").axes(z=Axis.linear(-2, 2))
scene.play([
  chart.animate.to(target).duration(1.4),
  scene.camera.animate.look_at(eye=(8, 6, 8), target=(0, 0, 0)).duration(1.4),
])
MÉTODOChart.inspect / inspection_enabled / move_to / move_to_3d / scale_to / animate
Activa metadatos de inspección de los campos indicados en la vista previa. No aparece en capturas ni exportaciones; inspection_enabled indica si está activa. move_to, move_to_3d y scale_to devuelven el gráfico.

Campos vectoriales

Un VectorField separa la función de sus representaciones: el mismo campo produce flechas, líneas de corriente, advección y partículas.

MÉTODOCoordinateSpace.field / VectorField.dimensions / evaluation
Evaluador reutilizable de una función que devuelve dos componentes (tres en 3D) y recibe las coordenadas y después inputs. No dibuja por sí solo. dimensions vale 2 o 3 y evaluation, "python" en esta ruta.
Ejemplo
from gaanim import Axis, Scene

scene = Scene(frame=(16, 9), background="#0f172a")
plane = scene.viz.cartesian_2d(Axis.linear(-4, 4).ticks(1), Axis.linear(-3, 3).ticks(1))
field = plane.field(lambda x, y: (-y, x))
arrows = field.arrows(resolution=(18, 12), colormap="batlow")
streams = field.streamlines(seeds=(12, 8), max_time=3.5, colormap="vik")
scene.play([plane.animate.create(), arrows.animate.write()], duration=0.8)
scene.play([streams.animate.write().duration(0.8)])
scene.play(streams.flow(1.5, time_width=0.12))
scene.render()
animation preview
MÉTODOVectorField.arrowsarrows(*, resolution: Optional[tuple[int, int] | tuple[int, int, int]] = None, min_length: float = 0.0, max_length: Optional[float] = None, length_scale: float = 1.0, width: float = 0.02, tip_length: Optional[float] = None, tip_width: Optional[float] = None, color: Optional[ColorLike] = None, colormap: Optional[ColorMapLike] = None, color_range: Optional[tuple[float, float]] = None) -> ArrowVectorField
Devuelve un ArrowVectorField (grupo retenido de astas y puntas) con drawable() y animate. Se valida todo antes de crear geometría.
Parámetros
NombreTipoPredeterminadoDescripción
resolution(int, int) | (int, int, int) | NoneNoneMuestras regulares por eje.
min_length / max_length / length_scalefloat0 / 0.28 (0.24 en 3D) / 1Límites y escala de longitud en unidades locales.
width / tip_length / tip_widthfloat0.02 / auto / autoGrosor del asta y tamaño de la punta.
color / colormapColorLike | ColorMapLike | NoneNone / viridisExcluyentes; el mapa usa la magnitud.
color_range(float, float) | NoneNoneRango explícito de magnitudes.
MÉTODOVectorField.streamlinesstreamlines(*, seeds: Optional[tuple[int, int] | tuple[int, int, int]] = None, direction: Literal["forward", "backward", "both"] = "both", tolerance: float = 1e-4, min_step: float = 1e-5, max_step: float = 0.1, max_time: float = 3.0, max_length: Optional[float] = None, max_steps: int = 10_000, stagnation: float = 1e-10, padding: float = 0.05, separation: float = 0.035, width: float = 0.02, opacity: float = 1.0, color: Optional[ColorLike] = None, colormap: Optional[ColorMapLike] = None, color_range: Optional[tuple[float, float]] = None) -> StreamLines
Líneas de corriente deterministas coloreadas por velocidad. Devuelve StreamLines con drawable() y animate.
Parámetros
NombreTipoPredeterminadoDescripción
seeds(int, int) | (int, int, int) | NoneNoneResolución determinista de semillas candidatas.
directionstr"both"forward, backward o both.
tolerance / min_step / max_stepfloat1e-4 / 1e-5 / 0.1Control adaptativo Dormand–Prince RK45.
max_time / max_length / max_stepsfloat | int3 / None / 10000Límites que hacen reproducible cada trayectoria.
padding / separationfloat0.05 / 0.035Margen del dominio y distancia mínima entre líneas.
MÉTODOStreamLines.flow / drawable / animate / ArrowVectorField.drawable / animate / FlowParticles.flow / drawable / animateStreamLines.flow(duration=2.0, *, time_width=0.15) -> list[Anim] · FlowParticles.flow() -> list[Anim] · drawable() -> Drawable · animate
StreamLines.flow devuelve animaciones que desplazan resaltados más claros por las líneas sin recortar la geometría base; FlowParticles.flow devuelve un clip de advección por partícula. Son finitas y exactas al hacer seek: pasa la lista directamente a scene.play. Los tres grupos (StreamLines, ArrowVectorField y FlowParticles) ofrecen drawable() para layout y estilo y animate para sus entradas y salidas.
MÉTODOVectorField.advectadvect(target: Drawable, seed: tuple[float, float] | tuple[float, float, float], *, duration: float = 3.0, direction: Literal["forward", "backward", "both"] = "forward", tolerance: float = 1e-4, min_step: float = 1e-5, max_step: float = 0.1, max_time: float = 3.0, max_length: Optional[float] = None, max_steps: int = 10_000, stagnation: float = 1e-10, padding: float = 0.05) -> Anim
Mueve el centro de un objeto por la trayectoria que parte de seed (coordenadas de datos), con el mismo integrador. Devuelve un Anim finito por longitud de arco; no deforma la geometría del objeto.
Ejemplo
boat = scene.geometry.dot(0.12).fill(GOLD)
scene.play([field.advect(boat, (2, 0), duration=2)])
MÉTODOVectorField.particlesparticles(count: int = 32, *, radius: Optional[float] = None, duration: float = 3.0, tolerance: float = 1e-4, min_step: float = 1e-5, max_step: float = 0.1, max_time: float = 3.0, max_length: Optional[float] = None, max_steps: int = 10_000, stagnation: float = 1e-10, padding: float = 0.05, color: Optional[ColorLike] = None, colormap: Optional[ColorMapLike] = None, color_range: Optional[tuple[float, float]] = None, opacity: float = 1.0) -> FlowParticles
Crea count partículas con semillas de Halton deterministas (puntos en 2D, esferas en 3D). El FlowParticles resultante queda oculto hasta su primera entrada (create, write, fade_in, grow_from_center) y flow() devuelve un clip de advección por partícula.
Ejemplo
particles = field.particles(24, duration=2)
scene.play([particles.animate.fade_in()])
scene.play(particles.flow())

Los colores y mapas explícitos de flechas y líneas tienen prioridad sobre las reglas plot del tema, que sigue controlando el fondo y los ejes.

Espacios 3D

Ejes 3D con rejillas por plano, superficies y curvas paramétricas. Combínalos con la cámara en perspectiva de Escena.

FÁBRICAVisualization.cartesian_3dcartesian_3d(x: Axis, y: Axis, z: Axis, *, size: tuple[float, float, float] = (10.0, 8.0, 6.0), grid: bool = True, axes: bool = True, ticks: bool = True, numbers: bool = True, labels: bool = True, x_axis: Optional[bool] = None, y_axis: Optional[bool] = None, z_axis: Optional[bool] = None, xy_grid: Optional[bool] = None, xz_grid: Optional[bool] = None, yz_grid: Optional[bool] = None, x_ticks: Optional[bool] = None, y_ticks: Optional[bool] = None, z_ticks: Optional[bool] = None, x_numbers: Optional[bool] = None, y_numbers: Optional[bool] = None, z_numbers: Optional[bool] = None, x_labels: Optional[bool] = None, y_labels: Optional[bool] = None, z_labels: Optional[bool] = None) -> Cartesian3D
Devuelve un Cartesian3D (un CoordinateSpace3D) con capas estables. Las rejillas ocultas no emiten segmentos ni duplican las aristas de los ejes; las etiquetas miran a la cámara.
Parámetros
NombreTipoPredeterminadoDescripción
x / y / zAxisobligatorioEjes lineales o temporales reutilizables.
size(float, float, float)(10.0, 8.0, 6.0)Tamaño positivo en el mundo; elígelo según la distancia de la cámara.
xy_grid / xz_grid / yz_gridbool | NoneNoneVisibilidad de cada plano; None hereda grid.
x_axis / y_axis / z_axis, x_ticks / …, x_numbers / …, x_labels / …bool | NoneNoneEje, marcas, números y título de cada eje, con el prefijo x_, y_ o z_; None hereda el interruptor global.
Ejemplo
world = scene.viz.cartesian_3d(
    Axis.linear(-3, 3).ticks(1).label("x"),
    Axis.linear(-3, 3).ticks(1).label("y"),
    Axis.linear(-2, 2).ticks(1).label("z"),
    size=(6, 6, 4), xz_grid=False, yz_grid=False,
)
MÉTODOCoordinateSpace3D.surfacesurface(function: Callable[..., float], *, resolution: tuple[int, int] = (64, 48), inputs: Sequence[Parameter | Variable | Computed | TimeInput] = (), color_map: Optional[ColorMapLike] = None) -> Drawable
Superficie z = f(x, y) muestreada en una rejilla de resolution, con inputs reactivas como plot. Los vértices se colorean por altura, de la más baja a la más alta, con color_map: un ColorMap o el nombre de un mapa integrado, como "viridis" o "plasma". Sin él, usa una rampa de azul, verde y rojo.
Ejemplo
saddle = world.surface(lambda x, y: 0.1 * (x * x - y * y), resolution=(32, 24))
MÉTODOCoordinateSpace3D.parametricparametric(function: Callable[..., tuple[float, float, float]], domain: tuple[float, float], *, samples: int = 320, inputs: Sequence[Parameter | Variable | Computed | TimeInput] = ()) -> Drawable
Curva 3D t → (x, y, z) sobre domain con samples muestras e inputs reactivas.
MÉTODOCoordinateSpace3D.fieldfield(function: Callable[..., tuple[float, float, float]], *, inputs: Sequence[Parameter | Variable | Computed | TimeInput] = ()) -> VectorField
Campo vectorial 3D; sus flechas, líneas de corriente y partículas funcionan como en 2D, con color por vértice.
MÉTODOCoordinateSpace3D.layer / move_to_3d / scale_to / data_to_local / local_to_data / drawable / animate
Capa 3D animable por separado: grid, axes, ticks, numbers o labels. move_to_3d, scale_to, data_to_local y local_to_data completan el espacio.

Límites

La inspección interactiva solo existe en la vista previa. La exportación interactiva, los dashboards, las facetas, los volúmenes y las isosuperficies aún no forman parte de esta API. La animación de cámara es global, explícita y componible.