Say where you are: a rail for rows, a dial for the disc - #49
Merged
Conversation
Which nodes a run of rows comes down to, and the span in x that holds them, were written inline in the middle of painting them. Both are now named functions the paint calls, so a second drawing pass over the same tree cannot quietly pick a different set and disagree with the first about the shape of it. Nothing about the picture changes: the same 9,822 branches come out of a two million node tree in the same 1.7 ms.
A wheel notch will take a reader to seven rows out of two million and nothing on the canvas said so, with Fit the only way back and it throwing away where they were. The rail down the right is the whole tree at the height of the canvas, drawn once from the same selection the main view is drawn from so it cannot disagree with it, and the block on it is the rows on screen: clicking, dragging or wheeling there goes to those rows and keeps how many of them there are. It holds its mark at four pixels because seven rows in two million is a thousandth of one, stands down on a canvas too narrow to spare the width, and is checked by eight new cases that fail when the rail is built from the window instead of the tree.
A review of the rail found five things and measurement backed all of them. The walk up to the root has no bound on a ladder, where every tip hangs off one spine: a hundred and twenty thousand tip caterpillar cost a hundred and fifty eight milliseconds a frame and stayed expensive when zoomed into, so the walk now steps over an ancestor that would be drawn inside the same pixel as the branch below it and that tree costs nine. Compared against the old selection pixel by pixel the ink does not move: at a deep zoom the two draw the same number of branches and every differing pixel is one of the tip names that used to run under the rail. The rail is drawn once onto a canvas of its own and stamped from there rather than re-stroked, which took a frame with fifty branches on it from ten milliseconds to none. Clicks measured the box around the canvas rather than the canvas, and that box has a one pixel border, so every one of them landed three thousand rows low at two million tips. The rail's ink was 1.96 to 1 on white, which is a silhouette nobody can see, and its mark was a fourth hue on a three hue page: they are now 3.75 to 1 and the page's own accent. A gesture belongs to the pointer that began it and ends on lost capture as well, and a canvas with no size in the stylesheet can no longer grow without limit.
The page had one projection where the crate has three, and the rail only fits the one it had: a circle has no top and bottom to scroll between. So the circular view draws the same layout in the crate's own polar arithmetic, checked against the formula in radial.rs rather than against itself, and the small picture of the whole tree becomes a dial in the corner with the window drawn on it. Nothing is asked of the program to switch: the rows and depths are the same numbers, read as angles and radii. A million tips comes out at fourteen milliseconds a frame at full extent and under two anywhere in a zoom, which took two bounds. A walk that has gone further in than the window can see has nothing left to draw, and a branch further out than the window can see is walked past rather than drawn: the second one takes twenty four thousand branches down to two on the worst gesture and, compared pixel by pixel against the same view without it, does not change the picture at all.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On a tree of two million tips a wheel notch will take a reader to seven rows and
nothing on the canvas says so. Fit is the only way back, and it throws away where
they were. This adds the rail: the whole tree, at the height of the canvas, down
the right hand side, with the rows on screen marked on it.
It cannot disagree with the tree
The rail is drawn from the same
selectthe main view is drawn from, over thewhole row range instead of the window, at the canvas's own height. That is the
point of the first commit here, which lifts the choice of what to draw out of the
middle of drawing it: with one selector there is no second opinion to have. It is
built once per load and again only if the canvas changes height, which on
2,097,151 nodes is 9,822 branches and about 1.5 ms.
Per frame it costs a re-stroke of those branches. Measured on a 500,000 node tree
at 900 by 520: a paint with the rail is 3.0 ms and panning holds under a
millisecond a frame.
What a hand can do with it
Click, drag or wheel on the rail and the window goes to those rows, keeping how
many rows are in it. Measured on the real page against the million tip demo:
clicking at 5, 35, 65 and 95 percent down the rail lands on tips 47,242, 347,242,
647,242 and 947,242 of a million, and dragging from 20 to 80 percent walks
through 197,242, 397,242, 597,242 and 797,242. Wheeling down moves the mark down
the rail and wheeling back up returns it to the pixel it started on.
A wheel on the rail scrolls rather than zooms, because a zoom there would be
anchored on the wrong row: the two sides of the canvas are at different scales.
Which side a gesture began on decides what it is for its whole life, so a drag
that starts on the rail and wanders onto the tree goes on moving the window.
The small print
Seven rows in two million is a thousandth of a pixel, so the mark is held at four
pixels and kept on the rail rather than allowed to hang off either end. Below 420
pixels of canvas width there is no rail at all and the tree gets the width back,
which is what a phone gets: verified at 375 px, where the pixels where the rail
would be come back fully transparent.
panBygrew adxit drops on purpose, and now says so: the depth axis isfitted to what is drawn on every paint, so nudging it sideways would be undone
before the frame reached the screen.
Checks
Eight new cases in
tests/tree-canvas.test.js, which CI runs. Two of them arethere to stop the others passing vacuously: one asserts that a rail built from
the window would follow a zoom where the real one does not, and one asserts that
scrubbing to the top and to the bottom of the rail land more than 700 px apart,
which a dropped or constant mapping would fail. The fake 2d context gained
fillRectandstrokeRectand now records them, so the mark's size, positionand colours are checked rather than assumed.
And the same idea, bent round
The crate has three projections and the page had one. The rail does not fit a
circle, because a circle has no top and bottom to scroll between, so the
circular view gets a dial instead: the whole disc, small, in the corner, with
the window drawn on it. Same idea, shape following the projection.
The disc is the crate's own arithmetic and not another one that resembles it.
radial.rssays a radial tree is "the rectangular one in polar coordinates: thesame rows, with depth becoming radius and row becoming angle", and the canvas
uses that formula with the crate's own defaults: the sweep starts at the top of
the circle, goes all the way round, and leaves a hole eight percent of the way
out. A check writes that formula out again from the crate's source and compares
it against
where(node): the two agree to a thousandth of a pixel, and movingthe hole from 0.08 to 0.10 or the start from -90 to 0 degrees fails it.
Switching projection asks the program for nothing. The rows and depths are the
same numbers read a different way, so there is no round trip to the worker and
a check pins that the layout arrays come back untouched.
Two bounds, because a circle needs them
A million tips draws at fourteen milliseconds a frame at full extent and under
two milliseconds anywhere in a zoom. Getting there took two rules, and the first
version of the first one was wrong in a way worth recording.
Every walk heads inward, so a walk that has gone further in than the window can
see has nothing left to draw. Stopping instead on "off the canvas" seemed
equivalent and is not: a tip outside the window walking inward was cut off
before it reached the part of its own lineage that is inside it, and the picture
came out with 1,423 of its 1,430 branches off screen. The rule is a radius, not
a box.
And a branch further out than the window can see is walked past rather than
drawn. On the worst gesture, zooming into the middle of a two million node disc,
that takes 24,051 branches down to 2. Rendered pixel by pixel against the same
view without the rule, at four zooms, the ink is identical: 544,112 pixels
against 544,112 at full extent, 41,623 against 41,623 zoomed in, one pixel
different out of 5,555 at the deepest.
The sample budget is the circumference of the widest circle the window can see
rather than of the rim, which is not the same thing once the rim is off the
canvas: measuring against the rim asked for six thousand samples of a region
that holds a few hundred.
Export says
--projection circular, which is a flag the crate already had.