Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 15 additions & 0 deletions .changeset/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'latkit': minor
---

`View.create` opens a view in one call, a mark's kind key holds its table, and `Data` applies changes directly.

- Added: `View.create(canvas, scene, { region, camera, sources, gpu, pixelRatio })` draws a view on a `<canvas>` or `OffscreenCanvas`, over any views already there. With a `null` canvas, the view draws only into pictures.
- Added: views share the first `Gpu` created, and `View.create` creates one if there is none. `view.region` moves a view, and `view.gpu` is its GPU.
- Added: `data.table(name)` and `tiles.table(name)` return a `Table` for a mark to draw or `field(name).from(table)` to read. A view loads every dataset its scene's tables belong to, so `sources` lists only datasets named as `[data, table]`.
- Added: a `Data` method for every `Patch` change, applied immediately: `axis`, `replace`, `set`, `remove`, `append`, `forget`, `frames`.
- Added: `Shot.of(view, options)` captures what a view's canvas shows. `new Shot(size, { views })` draws views in their own regions, or in another region given as `{ view, region }`.
- Changed: a mark's kind key holds its table, next to its options: `{ vertices: grid.table('Bus'), radius: 4 }`, or `{ "vertices": ["grid", "Bus"], … }` in JSON. This replaces `{ vertices: { of: ['grid', 'Bus'], radius: 4 } }`.
- Changed: errors name the option's path, such as `marks[1].radius`.
- Changed: `replace` and `set` given only fields write rows `0` to `n - 1`.
- Removed: `gpu.view`, `gpu.canvas`, `Canvas` (with `canvas.show(views)` and its `panels`), and the `Panel` type.
14 changes: 14 additions & 0 deletions .changeset/camera.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'latkit': minor
---

A flat map shows one world, and every camera move stays within the camera's zoom and pan limits.

- Changed: a flat map's ground ends at the antimeridian, as it ends at Web Mercator's latitude limit.
- Changed: on a flat map, the camera zooms out only until the whole world fits inside the insets, and pans only until the world's edges meet the view's edges.
- Changed: on a globe, the camera zooms out until the globe spans a third of the view's shorter side.
- Changed: setting the camera, flights, fits, orbits, and gestures keep the camera within its zoom and pan limits, and its pitch within 75°. After a fit, the limits narrow to the area the fit framed.
- Changed: a `camera` event fires when a resize, new insets, or a new space moves the camera back within its limits.
- Changed: on a flat map, a flight eases across the world instead of the short way over the antimeridian. On a globe, longitude still turns the short way.
- Fixed: panning a flat map no longer wraps its center across the antimeridian, which moved every row a world away.
- Fixed: a scene with another kind of space drops the old space's fits and any camera motion in progress.
15 changes: 15 additions & 0 deletions .changeset/labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'latkit': minor
---

Labels are placed from each frame alone, with no fades. Where labels would overlap, the row drawn on top keeps its label.

- Added: a label's `text` takes a `when` that picks the text field or `null`. A row whose text is missing has no label, so `when(zoom().ge(6)).then(field('name')).otherwise(null)` labels rows from zoom 6 on.
- Changed: a row whose text is empty has no label, like a row whose text is missing.
- Changed: a label shows when its row is visible, its text is set, and the text fits, for example inside a block.
- Changed: labels beside or along their rows never overlap. Where they would, the row drawn on top wins, so a mark's `priority` decides.
- Changed: only labels that show need their texts, so a view of any size settles with as many texts as fit on screen.
- Changed: labels move with the camera in the frame it moves, keep showing while their mark gains texts, and never take another row's state when tiles change.
- Changed: when the text atlas is full of texts in view, it accepts no more until the view changes, instead of evicting texts it still shows.
- Changed: `'labels'` in `Waiting` means label texts are being read back or are not yet set.
- Removed: a label's `priority` and `max`, and label fades. A mark's `priority` orders its drawing, its thinning, and its labels.
77 changes: 77 additions & 0 deletions .changeset/sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
'latkit': minor
---

Views draw from sources, every wait can be cancelled, colors and lengths are typed, and large tables draw in pages that stay on the GPU.

- Added: `view.sources`. A source is a `Data`, drawn at its latest version from the frame after each patch. It can also be a `Snapshot`, one fixed version (`data.snapshot()`, `data.restore(snapshot)`), or `Tiles(name, url)`, time tiles or vector tiles fetched once per GPU.
- Added: `view.snapshot(name)` returns what a view currently draws of a source.
- Added: `load({ wasm, worker })` loads the WebAssembly. Using the library before it finishes throws `unloaded`.
- Added: every class and builder is exported from `latkit`.
- Added: every wait takes `{ signal }` and times out after 30 seconds by default: picks, `screenOf`, `arrange`, pictures, encodings, videos, `gpu.done()`, and `gpu.landed()`. A timeout rejects with `timeout`. An abort or a destroyed view rejects with `cancelled` and drops the work in progress.
- Added: `new Shot(size, { scale, transparent, capture, elapsed })`, where `capture: 'now'` takes views as they are, and `shot.video({ fps, output, bitrate, signal })`.
- Added: `px(n)` and `units(n)` set a length's unit. Lengths default to position units in diagrams and CSS pixels elsewhere. A stroke in pixels keeps its width as a diagram zooms.
- Added: theme groups `text`, `vertex`, `edge`, `line`, `area`, `focus`, `flow`, `diagram`, `earth`, and `plot`.
- Added: a comet's `flow.spacing` and `flow.length` take `{ px }` or `{ units }`.
- Added: `diagram.outline`, `corner`, `portSize`, and `bend` style blocks, groups, ports, and wire bends.
- Added: `shadow: true` on blocks and groups casts a soft shadow `diagram.shadow.lift` below them.
- Added: `Gpu.resident` and `Gpu.uploading`, the bytes on the GPU and the bytes waiting to upload, across all its views.
- Added: `Work.packed`, `Work.resolved`, `Work.cleared`, and `Work.uploads`.
- Added: `Handle`, the base class of every object that holds memory (`Data`, `Snapshot`, `Gpu`, `View`, `Image`), with `destroy()` and `using`.
- Added: `Dataset`, the reads that `Data` and `Snapshot` share.
- Added: `colormaps()` lists the catalog's colormaps with their names, labels, and kinds.
- Added: the package's types document every member, enumeration value, union form, and options field.
- Added: the CLI's `--camera` takes a camera as JSON or as a file, like `latkit query` takes a query.
- Changed: `view.scene`, `view.selection`, `view.screen(position)`, `view.position(point)`, `view.screenOf(row)`, `view.waiting`, and the `hovered` and `selection` events replace the methods and events removed below.
- Changed: every error the package throws is a `LatkitError` with a `code`.
- Changed: a failed fetch or an error status is `io`. A call on a destroyed object has the new code `destroyed`. A bad `Patch.stream` limit and a `Video` drawn out of turn are `invalid`.
- Changed: errors from the video encoder and worker jobs keep their codes. An abort during a fetch rejects as `cancelled` or `timeout`.
- Changed: latkit's bytes carry no version or flags. A frame's 12-byte header is the magic `LATK` and the frame's length. Files written by earlier versions no longer open.
- Changed: `theme.missing` is the color a colormap gives a missing value. An option given a missing value (`null`) still takes its theme value.
- Changed: `new Patch(dataset)` names its dataset.
- Changed: `Patch.read(source)` reads a patch's bytes or JSON, and `Data.read(source, { name, base })` reads a dataset's bytes, JSON, or GeoJSON. Both read from a `ByteSource` (bytes, a `Blob`, a `Response`, a byte stream or async iterable, or a URL) within the wait's `signal`.
- Changed: the signatures `patch.append(table, at, fields, { ids })`, `patch.frames(table, field, { level, first, at, values })`, and `data.envelope({ table, field, range, spans, ids })`.
- Changed: a video's size rounds down to whole 4×2 blocks, in the browser and the CLI. A smaller size is `invalid`.
- Changed: `view.screen` and `view.position` given the wrong number of coordinates throw `invalid` instead of returning `null`.
- Changed: the `'earth'` space takes `{ earth: { projection: 'mercator' | 'globe', graticule, daylight, atmosphere } }`. Its ground color is the theme's `earth.ground`.
- Changed: a table too large to draw at once draws in pages that keep their place on the GPU as the camera moves.
- Changed: pages no longer drawn stay on the GPU while the budget allows, so coming back to them is free.
- Changed: a write rebuilds only the pages it touched, and appending rows never thins what draws.
- Changed: lanes, net stars, joins, and fitted domains follow the data, not the camera.
- Changed: edges with far-apart ends draw within the budget. Their end rows load one by one, and only edges whose ends moved look them up again.
- Changed: a hit describes the frame it came from.
- Changed: all views of a GPU draw one frame together, and `Budget.uploadPerFrame` caps what they upload in it.
- Changed: editing a diagram recomputes only the moved blocks, the groups around them, and the wires near them.
- Changed: map tiles draw in the frame they finish loading.
- Changed: a plot measures the space its axis labels and titles need.
- Changed: a GPU picks its antialiasing once.
- Changed: zooming at a fit's limit keeps the center.
- Removed: `view.setData`, `view.setTiles`, `view.removeData`, and `view.data(name)`. Use `view.sources`.
- Removed: `view.show(scene)`, `view.select(rows)`, `view.point`, `view.ground`, `view.locate`, and the `hover` and `select` events.
- Removed: `Stats.waiting`, `Stats.uploading`, `Stats.resident`, and `Stats.budget`.
- Removed: `loadLatkit()` and its result object, and `latkit/core`.
- Removed: `createWorker`. Use `load({ worker })`.
- Removed: `{ deadline }` options. Use `{ signal }`.
- Removed: `Shot`'s builder methods, `shot.image(views)`, and `shot.video(gpu, fps, output)`.
- Removed: `Patch.to`, `Patch.fromBytes`, `Data.load`, and `Data.tables`.
- Removed: `data.ids()`, `data.fields()`, and `data.span`. Use `describe()`.
- Removed: the positional `envelope`.
- Removed: `expr.of(data, table)`. Use `from`.
- Removed: the `'mercator'` and `'globe'` spaces. Use `earth`.
- Removed: flat theme keys such as `vertexRadius`, `fontSize`, and `edgeSpacing`.
- Removed: reading a string as a color only when it begins with `#`.
- Removed: `Flow.units`.
- Removed: shadows on edges, lines, areas, and faces, and the theme's `shadow.light` and `shadow.softness`.
- Fixed: when a view's next version does not fit, the view releases what it draws so the new version can load. If nothing fits, it reports a `limit` error and stops.
- Fixed: on the earth, sizes in `units(n)` are degrees, like comet lengths, not Mercator radians.
- Fixed: zoomed in close on the globe, paged tables and vector tiles keep drawing their finer tiles. Past about zoom 12 they fell back to the coarsest.
- Fixed: a text the host cannot rasterize reports one error, not one every frame it shows. A text with nothing to draw is not an error.
- Fixed: a layout or shaping job that fails runs once until its inputs change, not every frame.
- Fixed: at the GPU's texture-layer limit, a new icon fails with a `limit` error instead of overwriting the last one.
- Fixed: `view.screenOf` returns `null` for a hidden stroke or face, and for a face beyond the globe's horizon.
- Fixed: edges and faces no longer compile an unused bounds pipeline before their first frame.
- Fixed: a `priority` that tests a field only with `contains()` now reads that field when ranking a paged table's rows.
- Fixed: a JSON patch's errors name the failing change the same way wherever they are found, such as `[0].append.fields.x`.
- Fixed: a tile index that is neither TileJSON nor PMTiles fails as `invalid`, and tile errors keep their code.
- Fixed: when the CLI cannot write an output, its error names that output, not the frames file.
- Fixed: a transition that starts in the frame a mark's tables grow no longer writes its rows over other data on the GPU, which could draw rows in the wrong colors or places.
10 changes: 10 additions & 0 deletions .changeset/spans.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'latkit': minor
---

The command line plays an axis span.

- Added: `--span FROM,TO` and `--rate`. A video or image sequence without `--frames` plays the datasets' span.
- Added: with `--span`, each `--frames` line applies at its `at`, and the camera flies between lines that set it.
- Added: `view.fly(camera, seconds)`.
- Changed: setting or flying the camera replaces a pending fit.
14 changes: 14 additions & 0 deletions .changeset/tiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'latkit': minor
---

A vector tile costs only the rows it brings, and the tiles drawn largest split first.

- Changed: an arriving vector tile appends its rows, so rows already drawn do not upload or evaluate again.
- Changed: a tile that stops drawing keeps its rows and draws them again if it comes back. Once rows that no tile draws outnumber the drawn rows by a page, they are all removed. Until then, `view.snapshot(name)` of a vector source includes them.
- Changed: tiles split by the area they cover on screen, so tiles near the globe's edge and a tilted map's horizon stay coarse.
- Changed: when the tile budget runs out, the tiles drawn largest split first, for vector tiles and paged tables alike.
- Changed: at most 128 vector tiles draw at once.
- Changed: a vector table draws each tile's rows in the order of the mark's `priority`, highest on top.
- Fixed: a flat map no longer fetches tiles past the antimeridian as if they were near.
- Fixed: two tiles arriving together with different kinds for the same field no longer fail every update.
17 changes: 17 additions & 0 deletions .changeset/values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'latkit': minor
---

One way to write a value in every option and query: `'$name'` reads a field, any other string is a CSS color, and `null` is missing.

- Changed: in a scene or a query, a string `'$name'` reads field `name` and any other string is a CSS color, whatever the option. `field(name)` writes `'$name'`.
- Changed: a color option that computes a number or a boolean is an error, and the error says how to map it to colors.
- Changed: an operation is an object with one key, its name: `{ add: [a, b] }`, `{ not: [a] }`, `{ is: ['$name', 'Austin'] }`.
- Changed: `null` is a missing value.
- Changed: one `when` chooses between values and colors alike.
- Changed: `.colormap()` returns `Mapped`, like `.scale()`.
- Changed: the layered layout's `flow` is renamed `direction`, so `flow` means only comets.
- Removed: `{ op, args }`, `{ color }`, and `{ missing: true }`.
- Removed: `Paint`, `PaintInput`, `Colormapped`, `Missing`, `color()`, and `missing()`.
- Removed: bare strings as field names.
- Removed: in Rust, converting a string or a `Name` into `Paint` or `Expr`.
58 changes: 19 additions & 39 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
name: CI

# What a release depends on: the crates build, pass their tests, document, and package on every
# platform they ship to; the npm tarball works in a consumer; the docs build. Frame budgets and the
# benchmark need a GPU, which runners lack: run `cargo xtask check` and `cargo xtask bench` locally.
# platform they ship to; the npm tarball works in a consumer; the docs build. The jobs that build
# Rust run their steps as `cargo xtask ci <job>`, and `cargo xtask check` runs every job's steps.
# Frame budgets and the benchmark need a GPU, which runners lack: run `cargo xtask check` and
# `cargo xtask bench` locally.
on:
pull_request:
push:
Expand Down Expand Up @@ -59,7 +61,6 @@ jobs:
- 'docs/**'
- '.readthedocs.yaml'
- 'npm/test/consumer/quickstart.ts'
- 'npm/examples/src/examples/**'
- '.github/workflows/ci.yml'

# Prettier over every file: cheap, and any pull request can unformat one.
Expand Down Expand Up @@ -89,29 +90,13 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- run: cargo fmt --all --check
- run: cargo clippy --locked --workspace --all-targets --all-features -- -D warnings
- run: cargo clippy --locked -p latkit --no-default-features --all-targets -- -D warnings
- run: cargo clippy --locked -p latkit-js --target wasm32-unknown-unknown -- -D warnings
# Lavapipe, Vulkan on the CPU, runs the GPU tests instead of skipping; text tests need fonts.
- name: Install a software Vulkan driver and fonts for the GPU tests
run: sudo apt-get update && sudo apt-get install -y mesa-vulkan-drivers fonts-dejavu-core
- run: cargo test --locked --workspace
env:
LATKIT_REQUIRE_GPU: 1
- run: cargo test --locked -p latkit --no-default-features
env:
LATKIT_REQUIRE_GPU: 1
- name: Document the library
run: cargo doc --locked -p latkit --no-deps --all-features
env:
RUSTDOCFLAGS: -D warnings
- name: Verify the native package
run: cargo package --locked -p latkit
- name: Verify the browser-target package
run: cargo package --locked -p latkit --target wasm32-unknown-unknown --features web
- name: Keep native font code out of browser builds
run: cargo check --locked -p latkit --target wasm32-unknown-unknown --features native,web
# Cached, since apt took up to 7 minutes.
- uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: mesa-vulkan-drivers fonts-dejavu-core
version: 1
- run: cargo xtask ci rust

# The CLI's native code on the other platforms it installs on; Linux runs in `rust`.
native-cli:
Expand All @@ -129,11 +114,10 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- run: cargo test --locked --package latkit --features native --lib --test validation
- run: cargo test --locked --package latkit-cli
- run: cargo xtask ci native-cli

# The npm package: built from the crate, checked, tested, every pipeline compiled in Chrome on
# SwiftShader, and the packed tarball installed and drawn in a consumer.
# The npm package: built once from the crate in release, checked, tested, every pipeline compiled
# in Chrome on SwiftShader, and that build packed, installed, and drawn in a consumer.
npm:
needs: changes
if: needs.changes.outputs.npm == 'true'
Expand All @@ -156,7 +140,8 @@ jobs:
- name: Read the WASM bindings generator's version
id: bindgen
run: |
echo "version=$(python3 -c 'import tomllib; print(next(p["version"] for p in tomllib.load(open("Cargo.lock", "rb"))["package"] if p["name"] == "wasm-bindgen"))')" >> "$GITHUB_OUTPUT"
version=$(cargo xtask bindgen-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v4
id: bindgen-cache
with:
Expand All @@ -165,15 +150,10 @@ jobs:
- name: Install the matching WASM bindings generator
if: steps.bindgen-cache.outputs.cache-hit != 'true'
run: cargo install wasm-bindgen-cli --version "${{ steps.bindgen.outputs.version }}" --locked
- run: pnpm --dir npm/examples exec playwright-core install --with-deps chromium
- run: cargo xtask wasm --dev
- run: pnpm lint
- run: node npm/docs.mjs --check
- run: pnpm typecheck
- run: pnpm test
- run: cargo xtask browser
- run: cargo xtask package
- run: cargo xtask package-test
# The runner's Chrome drives the browser gate.
- run: cargo xtask ci npm
env:
LATKIT_CHROME: /usr/bin/google-chrome

# The docs as Read the Docs builds them, from the locked environment: a warning fails.
docs:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ jobs:
if: env.PUBLISH == 'true'
id: bindgen
run: |
echo "version=$(python3 -c 'import tomllib; print(next(p["version"] for p in tomllib.load(open("Cargo.lock", "rb"))["package"] if p["name"] == "wasm-bindgen"))')" >> "$GITHUB_OUTPUT"
version=$(cargo xtask bindgen-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v4
if: env.PUBLISH == 'true'
id: bindgen-cache
Expand Down
Loading
Loading