-
Notifications
You must be signed in to change notification settings - Fork 279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: basic group shape #1294
feat: basic group shape #1294
Conversation
± Registry diff
📊 PerformanceKeyNote that each bar component rounds up to the nearest 100ms, so each full bar is an overestimate by up to 400ms.
Data
|
Codecov Report
@@ Coverage Diff @@
## main #1294 +/- ##
==========================================
+ Coverage 61.28% 61.43% +0.15%
==========================================
Files 63 65 +2
Lines 7828 8053 +225
Branches 1845 1887 +42
==========================================
+ Hits 4797 4947 +150
- Misses 2924 2998 +74
- Partials 107 108 +1
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
Deploying with Cloudflare Pages
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this @liangyiliang! Very important first step towards better structure and reusability in Style.
My main request is to take a closer look at group graph construction in the renderer and figure out if we can avoid re-computation of the graph as much as possible.
I haven't thought deeply about the usability and performance implications of the saturation rules. At a glance it makes sense to me and the implementation seems straightforward. I'll leave it to @joshsunshine and @samestep to evaluate.
Generally, this PR has some duplicated code and some functions tend to have very long names. I'd suggest refactoring them to avoid duplication. If a function doesn't have a great name and it's called once in your code, I think you can consider inlining it to improve readability. See my code-level comments below.
const rawShapeProps = gpi.contents[1]; | ||
if (!isShapeType(shapeType)) { | ||
throw new Error("Unknown shape in Group bbox: " + shapeType); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this PR: we should really remove the need for boilerplate like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, agreed; #715 touches on this, but doesn't entirely cover the problem.
I agree! I will store the Group Graph in the State. |
@liangyiliang you modified some newline at the end of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still need to review the rest of the code, but while I'm doing that, could you add information about Group
to the docs on the website in this PR? You'll at least need to add a packages/docs-site/docs/ref/style/shapes/group.md
file, but you should probably also put more info in packages/docs-site/docs/ref/style.md
too.
Done!
Yup I will do it! |
Done! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great modulo the remaining comments I left above! Thanks for pulling this through :)
Description
Resolves #783 .
We added a basic
Group
shape that contains multiple already-declared shapes. Each group is now rendered as a<g>
tag which contains the internal shapes.As an example,
can now generate
New Shape
We add a new shape,
Group
, to the shape library. As of now,Group
shape only has one property,shapes
, and an automatically generatedname
. Propertyshapes
is a list of paths to previously-defined shapes.Changes to the Compiler
The Style language now allows users to have a list of shapes, denoted
[path1, path2, ...]
wherepath1
,path2
, ... are paths to shapes.GPI or ShapeAD Objects?
We go through and process each Style statement in the
translation
process, at which point only the GPIs of each shape can be accessed. The actualShapeAD
objects are available near the end of the compilation process. Therefore, a Group shape, initially, stores a list of GPIs. Near the end of the compilation process, after we already have a list of all ShapeADs, we switch each GPI into the corresponding ShapeAD objects.Group Graph
The compiler uses a directed graph, named$g \rightarrow s$ ) if and only if shape $s$ is a child of group $g$ .
GroupGraph
, to represent group-shape relationships between shapes. Formally, a directed edge exists between two shapes (For example,
s1, s2, s3, s4, s5, s6
s1, s2
belong to groupg1
s3, s4
belong to groupg2
g1
andg2
, and shapes5
, all belong to groupg3
s6
does not belong to any groups.Then, the group graph should look like
We can use this group graph to check whether or not the grouping relations are well-defined, which means
The compiler fires a warning if any of these checks fail. A well-defined grouping relation should look like a tree (as in the example).
Layering
Suppose all layering directives have the form
layer s above s'
(layer s below s'
is equivalent tolayer s' above s
). We want the following nice properties: (these are not the actual rules we follow for the algorithm)Concretely, in the group graph above, writing
layer s1 above g2
implies:layer s1 above g3
andlayer s3 above s4
(sinces3, s4
are members ofg2
)layer g1 above s3
andlayer g1 above s4
(sinces1
is a member ofg1
)layer s2 above s3
andlayer s2 above s4
(sinces2
is a member ofg1
)layer g1 above g2
(sinces1
is a member ofg1
)These are the "saturation" rules which saturates the entire layer graph and hence is not efficient. The algorithm leverages upon the observation that layering directives are only useful when two shapes are siblings within the same group. Suppose we encounter the layering directive
layer s above s'
.When encountering
layer s1 above s2
, the algorithm attempts to "walk up" the group graph from boths1
ands2
towards the root, until when the algorithm findss1'
ands2'
which are siblings. Then, the algorithm applies the same layering directive tos1'
ands2'
.In the concrete example of
layer s1 above g2
, the algorithm would "walk up"s1
to getg1
. Sinceg1
andg2
are siblings, we addlayer g1 above g2
to the layering graph. As another example,layer s1 above s6
involves walking ups1
twice to getg3
which is a sibling ofs6
, so we addlayer g3 above s6
to the layer graph.A few more (more trivial) examples:
layer s3 above s4
gets added to the layer graph directly, sinces3
ands4
are already siblings.layer g3 above g3
also gets added to the layer graph directly, since we considerg3
to be a "sibling" of itself. This will cause a cycle, which is perfectly expected.layer s4 above g3
would involve walking ups4
twice to getg3
. Similarly, we addlayer g3 above g3
.The final layer graph would only contain edges between siblings.
Keeping Groups Together
The layering directives would force the layering to be
bottom s2 s3 s1 top
. This itself does not cause a loop, but does break up the groupg
. The compiler should generate a warning.Running the algorithm, we have
layer s1 above s2
just gets added to the layer graph becauses1, s2
are siblings.layer s3 above s2
requires one "walk up" froms2
tog
, which is a sibling ofs3
, solayer s3 above g
gets added to the layer graph.layer s3 below s1
requires one "walk up" froms1
tog
, which is a sibling ofs3
, solayer s3 below g
gets added to the layer graph.We hence have both
layer s3 above s2
andlayer s3 below s1
, which causes a loop and a warning should fire.Changes to Compilation
The style compilation results in a
State
object whoseshapes
field contains a flat, ordered list of all shapes. With groups, the list of shapes should no longer be a flat list. It still storesShapeAD
objects, but eachGroup
shape's members should be actualShapeAD
objects. That is, the list of shapes should contain onlyShapeAD
objects for the top-level shapes, and all lower-level shapes should be within their respective parent groups. Furthermore, the list of top-level shapes, and for each group, the list of its child shapes, should all be ordered according to the layering. This is easy simply by sorting the elements of the lists based on the layer ordering. The list of top-level shapes should then be passed into the renderer.Previously, after
translation
, the compiler does the following in order:computeShapeOrdering
)ShapeAD
objects (getShapes
)The change goes,
Group
shapes store GPIs, not actual shape objects, since shape objects don't exist yet).ShapeADs
, this time each group storing its actual sub-shapes. This is what we pass into the renderer.Changes to the Renderer
The renderer now takes a list of top-level shapes, and renders them. Whenever it encounters a group, it recursively renders all the member shapes before packaging everything into one
<g> ... </g>
tag.Examples with steps to reproduce them
The
Wet Floor
example now uses groups.Can also do bbox-related constraints on groups: here
Checklist
Open questions
Turns out, warnings don't show up in the IDE.