Animated charts and plots
Axes, number lines, function plots and bars. Data coordinates map into a box, and every part follows when a range animates.
Loading live preview…
Source of charts — edit in the playground
//! Axes and plots: a curve with a dot riding on it, a morph into another
//! function, a zoom out, then a benchmark whose bars grow with their labels.
use oxframe::prelude::*;
#[oxframe::scene]
pub async fn functions(view: View) {
let axes = view.add(Axes::new().x_range(-5.0..5.0).y_range(-2.0..2.0));
let wave = axes.plot(|x| x.sin());
wave.draw_on().await;
// A signal drives the dot along the curve.
let x = Signal::new(-5.0);
axes.point_on(&wave, x.clone());
x.to(5.0, 2.0).await;
wave.to_fn(|x| (2.0 * x).sin() * 1.5, 1.0).await;
// The grid, ticks, labels and the curve follow the range.
axes.x_range().to(-10.0..10.0, 1.2).await;
wait_for(0.4).await;
}
#[oxframe::scene]
pub async fn build_times(view: View) {
transition(Transition::fade()).await;
let title = view.add(Txt::new().text("Build time, s").h1().to_edge(Edge::Top));
let axes = view.add(
Axes::new()
.x_range(-0.5..3.5)
.y_range(0.0..10.0)
.height(pct(55))
.y(40.0),
);
let bars = axes
.bars([0.0, 0.0, 0.0, 0.0])
.labels(["rustc", "gcc", "clang", "zig"]);
title.fade_in().await;
stagger(
0.15,
[3.2, 7.5, 5.1, 9.4].into_iter().enumerate(),
|(i, v)| bars.value(i).to(v, 1.0),
)
.await;
wait_for(0.6).await;
}
pub fn project() -> Project {
Project::new("charts")
.scene(functions())
.scene(build_times())
}Axes
Axes::new() is a plotting box with a grid, two axes with arrows, ticks and labels. .x_range(-5.0..5.0) and .y_range(-2.0..2.0) set the data ranges; .width() and .height() set the box in pixels or pct() of
the frame (80 % by 60 % by default); .no_grid() drops the grid. The axes cross at 0 when
0 is in range. Lines and labels take their colors from the theme.
let axes = view.add(Axes::new().x_range(-5.0..5.0).y_range(-2.0..2.0));
axes.x_range().to(-10.0..10.0, 1.0).await; axes.x_range() is a signal of Interval, so axes.x_range().to(-10.0..10.0, 1.0) zooms out. Ticks sit on round steps (1, 2 or 5 times
a power of ten), and each label keeps its value while the range slides, so nothing flickers.
Function plots
axes.plot(|x| x.sin()) draws a function and returns a Plot. It is
sampled again every frame, finer where it bends, so it follows the ranges. It breaks where it
jumps — tan has no vertical line across its gaps — and it is cut at the edge of the
box. .stroke(Accent) and .width(4.0) style it, .draw_on() draws it like any path, and plot.to_fn(|x| x.cos(), 1.0) morphs it: every x moves from
the old value to the new one.
let wave = axes.plot(|x| x.sin()).stroke(Accent);
wave.draw_on().await;
let x = Signal::new(-5.0);
axes.point_on(&wave, x.clone());
x.to(5.0, 2.0).await;
wave.to_fn(|x| (2.0 * x).sin(), 1.0).await; axes.point_on(&plot, x) keeps a dot on the curve at a signal x. axes.c2p(x, y) gives the scene point of a data point, and axes.place(node, x, y) keeps any node there while the ranges move.
Bars and data
axes.bars([3.0, 7.5]) draws a bar per value at x = 0, 1, 2… with the value above it;
set the x range to -0.5..n - 0.5. With bars the y axis sits at the left edge. bars.value(0).to(9.0, 1.0) grows one bar and its label, and .labels(["a", "b"]) names the bars. axes.line_series(..) draws a line
through data points and axes.scatter(..) a dot per point.
let bars = axes.bars([0.0, 0.0, 0.0]).labels(["rustc", "gcc", "clang"]);
stagger(0.15, [3.2, 7.5, 5.1].into_iter().enumerate(), |(i, v)| bars.value(i).to(v, 1.0)).await; Number lines
NumberLine::new(0.0..10.0) is a single axis with ticks, labels and an arrow. It has
the same methods as the axes: x_range, c2p and place.
There is no pie chart yet; draw slices with Custom. Long rows of a BulletList now wrap inside the frame, so a list of results next to a chart stays readable.