-
Notifications
You must be signed in to change notification settings - Fork 280
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
Extending Penrose viz-à-viz documentation #362
Conversation
…ables (but still evaluate the new state for label rendering)
…xamples of shapes in vizdoc
This is dope omg |
The heatmap demo is super cool! I'm assuming this is done with respect to one shape and it's positional properties. Would be interesting to see if this can be generalized to other types of properties (for optimized colors, a heatmap on the color space??). |
Yeah this is all great work! Stella and I have talked about extending the shape mod tab to incorporate information from the computational graph of the diagram. As I wrote in Slack:
|
Thanks @strout18, I was able to use the @maxkrieger or @wodeni, can you review how @strout18's |
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.
({ properties, shapeType }: Shape, key: number) => { | ||
// If the inspector is crashing around here, then probably the shape doesn't have the width/height properties, so add a special case as below | ||
// console.log("properties, shapeType", properties, shapeType, properties.w, properties.h); | ||
|
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 like this is for computing the viewbox of the preview svg? This is okay for now. In the future, I think it would be helpful to have a generic bbox function on or property of IShape
types.
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.
Also the formatting looks a bit off. I'd recommend a linter. Lmk what editor you use @strout18 !
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.
That code is copied and pasted directly from Shapeview
, so maybe to change this we should involve @maxkrieger . That's also probably why the formatting is off (I use VSCode anyway).
|
||
// if we move to not hardcoding canvas size this needs to be changed | ||
// returns number corresponding to x/y coord boundary of canvas if necessary | ||
const toCanvas = (jsonVal: string) : string => { |
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.
Our frontend should really have a base module that exports the canvas size. Will be a TODO if we resolve #361
@@ -0,0 +1,55 @@ | |||
{ |
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.
Great to finally have shapedefs in the frontend! We will eventually move the backend shapedefs into the frontend as well. A more principled way to generate these mod widgets: each shapedef will also include the datatype for each property. We can then derive the inputType
based on it (e.g. FloatT
-> range
, StringT
-> select
). The new shapedefs should also define the domain of their values (e.g. arrowheadStyle
-> ["arrowhead-1", "arrowhead-2"]
). WIth this information, we can generate sample functions for resampling shapes as well.
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.
@wodeni I did consider that, but I opted for extra customization, in case someone thinks a number
input is more appropriate than range
for their custom property of type FloatT
. Also, I think the same types can map to multiple input options; e.g, the option to edit the content of a text
should be presented as a text field, but arrowheadStyle
should be a select, though both have values of type String
. But I think once the backend shapedefs are moved to the front end it could be possible to do what you're suggesting.
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.
Good point. My current thought is to annotate shape properties with types and a range of values, and then make the decision on how to present them. Maybe an extension point within vizdoc to override defaults will give users enough flexibility.
@@ -25,8 +25,37 @@ A useful library component is `import { ObjectInspector } from "react-inspector" | |||
|
|||
See `ShapeView`, `Timeline`, and `Frames` for useful examples. | |||
|
|||
## Adding shape support to Mod view |
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 this thorough documentation!
right: 0, | ||
}} | ||
> | ||
{frame.shapes.map( |
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.
This looks like a copy-paste of code from ShapeView
to calculate the bbox of a shape. Can you factor it out into Util.tsx
and use it in both files?
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.
Fixed in 0e5d3a8
interface IProps { | ||
inputProps: IInputProps, | ||
eAttr: string; | ||
eValue: Value<any>; // is this the best typing? |
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.
Note, I should come back to this when merging, not sure if it should be Value<number>
or Value<VarAD>
(probably the former?)
properties: object; | ||
} | ||
|
||
// Q - should this update shape properties in state? not really necessary functionally but maybe ideologically |
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.
Does this not update shape properties in the state? I expected it to do so, but if not can you say more about how what it is updating?
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.
^Possibly related: my comments in Mod.tsx
(github isn't letting me link them directly)
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.
@hypotext It doesn't change the state of that component specifically, just passes up the changes to App.tsx
where that state gets modified (which then triggers a re-render, so it does change the state indirectly...)
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.
LGTM in general, thanks for the awesome work @strout18. If you can address the minor changes I pointed out in comments (forgot to put them in the GitHub review feature...) then we can move along with the merge (pending @maxkrieger's thoughts on the React integration). Thanks!
@@ -7,5 +7,6 @@ export interface IViewProps { | |||
frame: State | null; | |||
// Either a valid index in History | |||
frameIndex: number; | |||
modShapes?(state: IState): void; // todo - null check |
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.
why is it nullable?
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.
Not sure... I'll change it.
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.
Addressed in commit 2b9315a
if (frameIndex === -1 || frameIndex === history.length - 1) { // make it only work on last frame | ||
const shap = frame!.shapes[(selectedShape === -1 ? 0 : selectedShape)]; // the -1 index behaves weirdly so workaround | ||
if (shap.properties.hasOwnProperty(attrname)) { | ||
shap.properties[attrname] = attrval; |
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.
This seems prone to bugs because you're not supposed to mutate properties in-place if they're from props. Props should always be immutable https://reactjs.org/docs/optimizing-performance.html#the-power-of-not-mutating-data.
It shouuuuld be as simple a replacement as:
const shape = frame!.shapes[(selectedShape === -1 ? 0 : selectedShape)];
if (attrname in shape) {
const newFrame = {...frame,
shapes: {...frame.shapes,
[(selectedShape === -1 ? 0 : selectedShape)]:
{...shape, properties: {...shape.properties, [attrname]: attrval } }
};
modShapes(newFrame);
}
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.
@maxkrieger frame.shapes
is an array, so that code won't exactly work. I tried replacing it with the following code:
const shapeIndex = selectedShape === -1 ? 0 : selectedShape;
const newShapes = _.cloneDeep(frame!.shapes);
let shape = newShapes[shapeIndex];
shape.properties[attrname] = attrval;
const newFrame = {
...frame,
shapes: newShapes
} as IState;
modShapes(newFrame);
but the compiler didn't want to let me cast to IState.
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.
but the compiler didn't want to let me cast to IState.
Maybe check the type of attrval
and see if that conform to IState
's types?
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.
Fixed in 0e5d3a8
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.
Overall looks good, but there are some issues for mod
that we'll have to refactor eventually because of readability/immutability/maintainability issues (see inline comments incl those not attached below).
const {frame, modShapes, frameIndex, history} = this.props; | ||
const {selectedShape} = this.state; | ||
if (frameIndex === -1 || frameIndex === history.length - 1) { // make it only work on last frame | ||
const shap = frame!.shapes[(selectedShape === -1 ? 0 : selectedShape)]; // the -1 index behaves weirdly so workaround |
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.
would be interested in why:
-
-1
index behaves weirdly -
frame!
is possibly null: just check if it's null at the top level of the function?
boxSizing: "border-box" | ||
}} | ||
> | ||
{/* todo enable only on last frame */} |
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.
hm?
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.
Expired comment... I'll get it rid of it.
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.
Addressed in 2b9315a
properties: object; | ||
} | ||
|
||
// Q - should this update shape properties in state? not really necessary functionally but maybe ideologically |
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.
^Possibly related: my comments in Mod.tsx
(github isn't letting me link them directly)
this.updateAttr(event.target.id, teval); // a little hacky - id is name of property | ||
} | ||
// when an input corresponding to a part of an attribute is modified instead of an entire attribute | ||
public handleSubChange = (event: React.ChangeEvent<HTMLInputElement>) => { |
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 have no idea what this does but thats fine I guess
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.
Currently it modifies inputs that map to only part of a shape attribute, e.g. how both the opacity
and color
input map to the color
attribute. I plan on factoring it out.
} | ||
// when an input corresponding to a part of an attribute is modified instead of an entire attribute | ||
public handleSubChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const ptinfo = event.target.id.split("_"); |
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.
Don't encode data into a string and parse it afterwards! There's always, ALWAYS a better way!
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.
Addressed in 2b9315a
@strout18 Let us know when this is ready for re-review! (The merge conflicts between this and |
@hypotext This is ready for re-review! Also dealt with the merge conflicts. |
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.
LGTM, the heatmap looks fine now, though on trying to re-run your branch (pulling and building) I got this error:
Failed to compile.
./src/inspector/views/mod/LabeledInput.tsx
Module not found: Can't resolve '../../../Util' in '~/Desktop/Code/penrose2/react-renderer/src/inspector/views/mod'
Can you fix this first?
In GitHub web it says there are several merge conflicts, but on CLI I can merge with no problem (which I just did).
@maxkrieger Can you give Stella's changes a quick re-review? If all looks ok (after Stella fixes the Utils import problem above), I'll merge into |
The import problem should be fixed in 6153a1b ! |
@@ -1,5 +1,5 @@ | |||
import * as React from "react"; | |||
import {toHex} from "../../../Util"; | |||
import {toHex} from "../../../utils/Util" |
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.
BTW I changed the module resolution rules and you can just use absolute paths! utils/Util
would work just fine.
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.
LGTM, thanks for fixing this stuff!
Thanks @strout18 for building this! And @wodeni @maxkrieger for reviewing. |
Description
This branch has multiple projects, which are each independent but share one common goal: increasing Penrose's visual documentation and enabling future documentation. Current code focuses mainly on shapes.
Mod tab on Inspector.
Added a tab to the inspector that lets you modify shapes declared in a Substance file. It works with any
sub/sty/dsl
files, but recommended as base examples are any of the sub files inexamples/vizdoc/baseshapes
. Select a shape on the left. You should then be able to adjust any of the attributes on the selected shape pre- or post-optimization. To add custom shapes or adjust input types just check out the “Adding shapes to mod” section inside of the Inspector README. The goal of this was to provide an in-Penrose shape explorer allowing the user to see in real-time how different attributes affect the shape. Could also see this being used to 'tweak' a final diagram post-optimization.runpenrose vizdoc/baseshapes/circle.sub vizdoc/baseshapes/baseshapes.sty vizdoc/shapes.dsl
Docshapes
The
examples/vizdoc/docshapes
directory contains multiple.sub .sty .dsl
triplets that can be used to create simple shape diagrams. The goal with these was to create aesthetically pleasing, informative diagrams accessible to the casual user that demonstrate relevant properties of the shapes. A couple examples and the paths to reproduce them are below.runpenrose vizdoc/docshapes/circle.sub vizdoc/docshapes/circle.sty vizdoc/shapes.dsl
runpenrose vizdoc/docshapes/line2.sub vizdoc/docshapes/line2.sty vizdoc/shapes.dsl
All example PNGs so far can be found in the Google Drive. Current shapes are
Square, Rectangle, Line, Ellipse, Arrow, Text, Circle,
andImage.
Shape modifier - HTML style
Added Penrose-independent shape-explorers, similar to the Mod tab. They can be found in
examples/vizdoc/html
. There are shape-specific explorers for current shapes in Penrose, as well astemplate.html
andtemplate.js
files that support custom shapes. Thetemplate
files currently work only on shape attributes that have a one-to-one correspondence with an SVG attribute or a one-to-many (like howSquare.side
in Penrose maps to both thewidth
andheight
properties of a corresponding SVG rectangle), but not a many-to-one or many-to-many. The template file could be extended further, but the Mod tab in the inspector covers most of its functionality so it's probably deprecated. The immediate advantage of the pure HTML/JS file is that it could be embedded in a documentation page, whereas the Mod tab is only accessible through Penrose. To run just open the HTML file in Chrome. If this were to be implemented in documentation, we might want to implement CSS styling. Currently very bare bones. The example below isexamples/vizdoc/html/rectangle.html
.Heatmap
Implemented an energy heatmap generator for constraints/objectives at
examples/vizdoc/html/heatmap.html
. Currently, the expression for the constraint/objective is entered manually in the file, and a heatmap is rendered using d3. It also supports adjusting x and y coordinates of shapes that objectives are being applied to. Still in most basic stage. Would not advise using in documentation until further work is done. Example below uses the expression for the sameCenter objective.Square hotfix
The Square shape did not support the strokeStyle property. I added that functionality. Can repro by
runpenrose vizdoc/baseshapes/square.sub vizdoc/baseshapes/baseshapes.sty vizdoc/shapes.dsl
and modifying strokeStyle in the mod view of the inspector.Remaining issues
onCanvas
function inLabeledInput.tsx
will need to be updated. For more information on this, see the "Adding shapes to mod” section in the inspector README.string
property of aText/Label
element. I think this is because thestring
property is set during thecollectLabels
method that occurs during optimization and so will not be affected by a change in Canvas state (which Mod changes in order to modify the shapes on the canvas). Perhaps this issue could be fixed in Mod, but the most straightforward fix seems to be to not gather labels during optimization...Curve
instances with points only:runpenrose linear-algebra-domain/twoVectorsPerp.sub linear-algebra-domain/linear-algebra-paper-simple.sty linear-algebra-domain/linear-algebra.dsl
- Mod tab has not been tested onCurve
instances. This should happen eventually, but most of the functions (inFunctions.hs
onmaster
) that are used to generate points on aCurve
have not yet been ported toweb-runtime
and I wasn't sure about manually defining points.