Repository navigation
Add isometric objects - #13
Merged
Merged
Conversation
…strokes Each face previously stroked its own four edges, producing visible seams at the internal boundaries. Faces now fill only; a separate hull path traces the six-point silhouette with the bold stroke, and a faint path draws the three interior seam lines converging on the near corner. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lter Generalizes SvgFilterDefinitions.define() to take any filter-primitive value, adds a feGaussianBlur branch, and wires the isometric object's ground shadow to shadowStyle.blur (default 8) so it can be softened instead of rendering a hard-edged ellipse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Interactivity: isometric objects were tagged with their own SceneRole but never added to the interaction layer's per-role allowlists, so clicking one never resolved to an owner at all - selection, move, resize and rename were all silent no-ops. Also fixes the hull being picked as the primary node (not the ground shadow or a single face, which gave the selection ring the wrong bounds) and the SFL block scanner's keyword list (it never knew `Isometric` was a valid owner header, so even a resolved move/resize/rename never found the block to patch). Resize now also preserves the object's depth (previously the generic 2-value size writer silently dropped the third value), and rides the composite (chart/table) scale+translate preview instead of the per-kind preview path, since isometric has no registered shape-kind geometry function there - this is what gives it live resize feedback and keeps the label translating correctly instead of jumping to the drag handle. Rendering: each face rounded its own corners independently of the hull's silhouette rounding, so the two never quite lined up - leaving slivers of background visible at the corners and the interior seam lines overshooting past the rounded edge. Faces are now drawn sharp and clipped to the hull's own rounded path, so the fill and the stroke are always trimmed by the exact same curve. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dragging a corner handle stretched width and height independently, breaking the isometric angle - the box no longer looked like a projected cube once released. Isometric objects now get their own selection overlay (three circular axis handles at the right/bottom/left vertices instead of the generic rectangle's eight square ones): one drags width, one height, one depth, and nothing else moves - x/y and the other two dimensions stay exactly fixed, so the angle is preserved by construction instead of needing to be re-derived after the fact. Live preview recomputes the actual isometric geometry (faces, hull, seams, ground shadow, label) on every pointermove via the same isometricFaces/roundedPolygonPath used at render time, rather than a generic bounding-box transform - this also fixes the corner-radius clip being clipped to the stale pre-drag silhouette mid-drag (dropped for the duration of the drag; the post-commit re-render restores it). Also fixes a precision bug this surfaced: reading the hull's rendered getBBox() as the resize start size drifted by a few units because rounded corners pull the Bezier curve in from the sharp-corner bounds. The hull now also carries a declaredBounds attribute (the same mechanism Symbol shapes use) with the exact pre-rounding size. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The axis handles from the previous commit still sat inside a generic rectangular selection ring, which read as the same box-resize UI as every other shape despite only offering 3 points instead of 8. Swaps the ring for a halo that traces the hull's own (rounded) outline, and each handle for a shaft-and-arrowhead vector pointing outward along that dimension's true isometric edge angle - closer to a 3D tool's per-axis scale gizmo than a resize box. Also makes the live-drag preview reposition the halo and the two stationary arrows (not the one being dragged) to match the live geometry; previously only the object itself updated, leaving the overlay visibly pinned to the pre-drag size until drop. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous formula tied width and depth to the same term (both set the apex's horizontal/vertical reach together), so changing either one retargeted the apex angle of the other - exactly the "significant geometry changes" the axis handles were producing. isometricFaces now anchors the apex and reaches right/left along two fixed-angle (30 deg) vectors scaled independently by width and depth - each is a pure translation of its own side, the other side's geometry is completely unaffected. The on-screen bounding box is derived from these (see the new isometricBounds), not stored directly - equal width/height/depth now renders a true cube. Depth still has one residual coupling: the apex sits at x + depth*cos (so `left` lands exactly on the box's declared x), so holding x fixed while depth changes still drags the apex sideways. The axis-resize session now compensates x by the same amount depth moves, so only the left side visibly slides and the apex/right side stay exactly put. Width/depth arrows are repositioned to the center of the face each one alone resizes (side, front) rather than a corner vertex. Updates the existing isometric-geometry unit tests to the new formula and demo values across the stickyflow.js build - not run here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…med label anchor - Shaft/arrowhead sized up slightly (still a vector, not a box handle). - width/depth point at the true 30deg axis angle, not horizontal/ vertical - swapped their cursors from ew/ns-resize to the closer nwse/nesw-resize diagonals (no native CSS cursor exists for an exact 30deg angle). - Added isometricTopFaceCenterX and used it for the label/label-guide x position in both the renderer and the live-drag preview, instead of the equivalent but unexplained bounds.x + bounds.width / 2 - same number, but now reads as "centered on the top face" like the seams/hull it sits above. Verified live (scripted pointer drags + DOM attribute snapshots mid- drag) that hull/seams/label/label-guide were already updating on every pointermove before this commit, not just on drop - most likely a stale dev-server build (file: deps don't hot-reload) rather than a real regression, flagged to the user rather than silently assumed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… Flucht snapping Label fix: the label/label-guide were anchored to the top face's x only, with y pinned to the apex (which never moves). That made the label slide horizontally while the top face's actual centroid - built from the same apex+toRight+toLeft vectors as every edge - moves diagonally when width or depth changes. isometricTopFaceCenter now returns the full point and both the renderer and the live-drag preview anchor to it, so the label rides the same fixed-angle line the edges do instead of visibly lagging on one axis. Cursor fix: nwse/nesw-resize are 45deg, not this object's fixed 30deg axis angle, so they never quite matched the arrow they sat on. Replaced with a tiny inline double-headed-arrow SVG cursor rotated to the exact angle of the direction vector the arrow itself is drawn with (same source value, so they can't drift apart). New: isometric "Flucht" snapping. Dragging a single isometric object now tries snapping its apex onto the 30/150deg line through another isometric object's apex before falling back to the regular horizontal/ vertical bounds snap - lets objects be dragged into the same true- isometric diagonal instead of hand-tuning position numbers. Required widening GuideLine to a union (a plain horizontal/vertical pair can't express an arbitrary-angle line) - computeSnap's own return type stays narrowed to the h/v-only variant via the new RectGuideLine so its existing callers didn't need any guard added. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…w fixes - Label can be freely dragged (labelOffsetX/Y) with an optional labelBackground plate for legibility over colored faces - Ground shadow is now the true isometric footprint (top face carried down by height, spread outward), following both axes instead of a fixed-orientation ellipse - Shadow sits in its own layer strictly behind every isometric object's body, so one object's shadow can never paint over another's faces - Shadow stays pinned to the object's own base instead of drifting with height, and intensifies the taller the object grows
…ks height Without distance, the ground shadow keeps dropping by the object's own height as before (moves with it when scaling up/down). Setting distance explicitly anchors it a fixed drop below the top face instead, independent of height, so users can choose per object whether the shadow should follow the resize or stay pinned to one plane.
It previously replaced the height-based drop outright, so a distance smaller than the object's own height moved the shadow closer to it instead of further away - visibly "upward" and counter-intuitive. distance is now added on top of the height-based drop, so positive values only ever increase the gap.
Adds a duplicate bubble at each of the six faces (the three visible ones plus their hidden opposites, mirrored through the object's visual center), each offset exactly along that face's own true isometric vector - width/depth move diagonally at the fixed 30deg axis angle, height moves straight down, so a chain of duplicates stays aligned on the same axis instead of drifting. Click-only (no drag-to-link, unlike the generic 2D quickDuplicate bubbles) via a new, separate binding - a cube face is a 3D axis direction, not a 2D box edge, so dragging it onto another shape to link up doesn't carry the same meaning. Reuses the existing `quickDuplicate` event, widened to accept the six new iso-prefixed directions alongside top/right/bottom/left. Also extends insertCanvasObject with an isometric kind (depth + 3-value layout.size), and fixes the axis-resize live-drag preview, which was still writing cx/cy/rx/ry to the ground shadow - now a <path>, not an <ellipse>, since the shadow rewrite - so it silently no-opped and the shadow sat frozen until the drag ended.
…, hover - Only the three visible-face directions (top, front, side) get a bubble now; the three hidden/reflected ones are gone - Bubble margin now clears the axis-resize arrow tips instead of sitting mid-shaft and overlapping them - Bubbles grow and switch to the brand color on hover, with a matching transition
Tags each face with the same prop/propKind:'color' contract chart legend swatches use, so clicking a face in an editable canvas opens the color picker directly - cubes aren't limited to whatever fixed colors the SFL happened to declare.
- Replaces topFill/frontFill/sideFill with one `fill` property; the front/side faces derive their shade from it automatically (uniform RGB darkening), instead of three independently chosen colors that can drift out of sync with each other - Clicking a face now requires the cube to already be selected - the first click always just selects it, a second click opens the color picker. Gated on a selection snapshot taken at pointerdown (before the click's own selection can happen), since checking live DOM state at click time would always see the object as "already selected", even on the very click that just selected it
0.88/0.76 made a white cube's side face a visibly harsh gray next to it. 0.95/0.88 reads as a soft, elegant light/shadow step instead of a hard color jump, while still giving darker colors enough contrast to read as three faces.
anchorY was missing a /2 - it landed on the "center" vertex (where top/front/side meet) instead of the top face's centroid, snapping the guide line's start point there for the live drag preview and only correcting back once the renderer re-ran on release. Matches isometricTopFaceCenter's formula exactly now.
…ashes computeIsolineSegments (marching squares) hands back one independent segment per grid cell - correct in isolation, but each was drawn as its own disconnected "M ... L ..." command, so the isoline was never actually one continuous path, just many short straight dashes abutting at every cell boundary. That's what read as crooked, not the data. Adds stitchIsolineSegments: walks the segment graph (adjacent cells compute their shared edge intersection identically, so endpoints match exactly) into ordered point chains, one per contiguous strand or closed loop. Each chain is then run through the same Catmull-Rom smoothing (linePathData) line/path charts already use for their own points, instead of straight linear segments.
This was referenced Oct 4, 2026
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.
Isometric Objects — Connections
New
ConnectionSFL construct, distinct fromLink: connects twoIsometricobjects face-to-face rather than side-to-side.top/bottom/front/back/left/right) —previously only the 3 visible faces existed, which made a connection to
the far side of an object route incorrectly across its visible faces.
straight line in 3D space stays straight in 2D — no elbow/orthogonal
routing needed).
that owns that face, instead of floating on top of it.
(new face-aware parallel to the existing rect+side live-link-preview
mechanism, since face-center geometry doesn't fit that model).
Connectionreferencing it(previously only
Linkblocks were cleaned up).insertConnectionpatch function, exported fromstickyflow/interaction.Sankey
reordering within their column. The dropped position is persisted as an
offsetX/offsetY on the node's
zonesentry, added on top of the automaticcolumn/stacking layout rather than replacing it — repeated drags
accumulate correctly.
drag (previously only the node itself moved).
dragging, reusing the same snap mechanism shape drags already have.
the
zonesentry and everysource/targetreference to that node inone patch (
applySankeyNodeRenameChange).sankeyNodeMove/sankeyNodeRenameinteraction events andapplySankeyNodeMoveChange/applySankeyNodeRenameChangepatchfunctions, exported from
stickyflow/interaction.Boxplot
the horizontal Y-axis variant) are now wired into the existing
property-edit system (
data-sf-prop→Series[id].label), so they canbe renamed via double-click like every other label in the library. This
was previously dead — the tick labels carried no editable property path.