Skip to content
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

Predefined objects and improved user experience #313

Closed
Wikunia opened this issue Feb 18, 2021 · 16 comments · Fixed by #356
Closed

Predefined objects and improved user experience #313

Wikunia opened this issue Feb 18, 2021 · 16 comments · Fixed by #356
Labels
enhancement New feature or request

Comments

@Wikunia
Copy link
Member

Wikunia commented Feb 18, 2021

Is your feature request related to a problem? Please explain.
I feel like writing anonymous functions is sometimes a pain and it doesn't contain much structure if everything is an Object.

Describe the solution you'd like
I'm wondering if it makes sense to have some types for Line, Circle, Rectangle, Polygon etc. and one can write:

myline = Object(1:100, Line(p1, p2))
act!(myline, Action(1:10, appear(:draw)))

such that the line gets drawn (see #218 )
and maybe also

myline = Object(1:100, [Line(p1, p2), Line(p2, p3)])
act!(myline, Action(1:10, appear(:draw)))

such that we can concatenate line segments easily.

Additionally each object could have some properties like, start_point, end_point for line and radius and center for Circle.

Then one would be able to write:

myline = Object(1:100, Line(p1, p2))
act!(myline, Action(1:10, appear(:draw)))
mycircle = Object(1:100, Circle(myline.start_point, 20))

where the start_point field is an anonymous function which has access to the start point of the line.

The main point for Line and the other objects is also that we can define them ourselves on top of Luxor and be able to specify all possible options to it like color, width for which one currently needs to write anonymous functions.

Old functionality should not be destroyed by this but just extended and it should be simple for the user to define their own objects.

Describe alternatives you've considered
I considered using

Line(1:100, p1, p2)

but it feels better to have the Object around it which takes care of the frames.

This might also somehow make it easier to work with #130 .

@TheCedarPrince What do you think and feel free to rename this issue as naming is hard 😄

@Wikunia Wikunia added the enhancement New feature or request label Feb 18, 2021
@Wikunia
Copy link
Member Author

Wikunia commented May 26, 2021

Would also like to hear your thoughts on this @Sov-trotter
I'm not sure how practical this is and whether it's easy to implement the myline.start_point functionality.
Additionally another ping for @TheCedarPrince 😉

@Sov-trotter
Copy link
Member

I think this task in general has tons of possibilities. I am not yet ready with a concrete design yet. For now I think we need a general data structure/API to store not just the start point but any information in general, hence a mutable metadata field attached with each object.

@Wikunia
Copy link
Member Author

Wikunia commented May 26, 2021

Oh yeah for sure. This was just an example. Each drawingType should have its own fields and possibilities to store such information

@Wikunia Wikunia mentioned this issue Jun 13, 2021
10 tasks
@codejaeger
Copy link
Contributor

codejaeger commented Jun 16, 2021

Hey @Wikunia , @TheCedarPrince I was thinking about how this could be done and came up with the following

  • Having a metadata::Any[] field seems important to me. It might seem useless for a Circle, but consider a line where you need to change one of the end points of the line after the object has been created.
  • Having a Dict (for the metadata field) to store the positions or radius arguments might suffice but take the example of an object where one of the drawing arguments itself needs to be a Dict.
  • To fill the metadata field we could have an implementation for Line like
function Line(p1, p2)
    return (video,  object, frame; kwargs...) -> begin push!(object.metadata, Dict(:p1=>p1, :p2=>p2)); line(p1, p2, :stroke); end
end
myline = Object(1:100, Line(p1, p2))

To change an end point for the line,
myline.metadata[1][:p1] = 12
or using some function like
change(myline, 1, :p1, 12)

  • I think there is a slight problem with this. This is because until now options and keywords added to the Object were explicitly done by the user which was available in the Object constructor itself. But now, it is the drawing functions which shall add such options in the metadata field. The action functions for each object are called before this function returned by Line (draw_object). The case when an action function refers to a property like p1 (which does not exist yet) is when an error would occur. This is unavoidable since actions need to be evaluated on the object at the start of each frame.

  • However using a syntax like Line(1:100, p1, p2) could be implemented. This can be generalized to any custom object like a Line, Circle or a GraphNode as

CustomObject(<frames>, <custom object specific args>, <custom draw function>, <start pos>; <optional keyword arguments>)

@Wikunia
Copy link
Member Author

Wikunia commented Jun 17, 2021

To change an end point for the line,
myline.metadata[1][:p1] = 12
or using some function like
change(myline, 1, :p1, 12)

The 1 here is definitely confusing and I'm not sure why we need a vector for metadata and push it to as we don't want to push something for every frame, right?

  • I think there is a slight problem with this. This is because until now options and keywords added to the Object were explicitly done by the user which was available in the Object constructor itself. But now, it is the drawing functions which shall add such options in the metadata field. The action functions for each object are called before this function returned by Line (draw_object). The case when an action function refers to a property like p1 (which does not exist yet) is when an error would occur. This is unavoidable since actions need to be evaluated on the object at the start of each frame.

Can you explain the problem here again? The points p1 and p2 are known, right? The problem is when we have something like pos(obj) such that the position depends on another opject. However I don't see how this is getting fixed by the other approach.
I was wondering whether we need a macro to make this work.

@Sov-trotter
Copy link
Member

Sov-trotter commented Jun 17, 2021

The way I look at metadata is something similar to what has been done in the GeometryBasics.jl package -> https://github.com/JuliaGeometry/GeometryBasics.jl/blob/master/src/metadata.jl#L181-L210 and https://juliageometry.github.io/GeometryBasics.jl/dev/metadata/
Geometry types like Point, Polygon etc. can be with or without metadata. In case one wants to attach metadata to a geometry, then constructors viz. PointMeta, PolygonMeta are generated. Everything is stored as NamedTuples inside a StructArray.

@codejaeger
Copy link
Contributor

codejaeger commented Jun 17, 2021

The 1 here is definitely confusing and I'm not sure why we need a vector for metadata and push it to as we don't want to push something for every frame, right?

Yes, we definitely don't need to push it for every frames. The Vector is for the case when a list of object functions are provided like `Object(1:100, [Line(p1, p2), Line(p2, p3)]).

Can you explain the problem here again? The points p1 and p2 are known, right? The problem is when we have something like pos(obj) such that the position depends on another opject. However I don't see how this is getting fixed by the other approach.

I was thinking that if points p1 and p2 needed to be changed somehow using a custom action, it could be done by storing it in the object just like the way draw_text option is provided for animating Latex text. Such an option needs to be added to the object in its constructor itself. But if Line(p1, p2) returns just a function then that would be called only through render.

Hey @Sov-trotter , I see the meta function which can be used to get back the metadata of an object. I had a question, is creating a meta type for Object what you are referring to or do you want it for the shapes Line, Circle etc. If it is the former, I believe the problem still occurs when you are trying to create the meta for the object, since in the current scenario that can happen only inside the function returned by Line. I am not sure how it would be done for the latter since we do not have any predefined structs for them yet. My knowledge of macros is limited, please correct me if I am wrong 😅 .

We could use multiple dispatch and provide versions of the Object constructor for basic shapes like Line or Circle but that would simply complicate the implementation. On a side node, I need to create a similar method GraphNode which needs to store some metadata along with the node. This is because I modify them with actions to create animations.

@Sov-trotter
Copy link
Member

So metadata can be attached to anything(not necessarily to shapes or an entire video). We obvoiusly don't need to stick with the implementation in GeometryBasics.jl since it's more tuned for geospatial data with millions of rows of geometries and metadata.
But I'd like to have something on the same line in the future. Eg: In layers also I end up pushing a bunch of image matrices to the layer struct which I personally don't quite like from performance perspective.

We could use multiple dispatch and provide versions of the Object constructor for basic shapes like Line or Circle but that would simply complicate the implementation.

So that's why we can use a macro to generate meta() methods rather than multiple dispatch as that won't be inherently extensible.

@codejaeger
Copy link
Contributor

codejaeger commented Jun 17, 2021

I am trying to wrap my head around the way this is done in GeometryBasics.jl, so if we have a meta() method then we still need the metadata field, right?

But does a user need to call the meta() directly after the object has been created for a custom shape like Line? I was hoping that could be done simultaneously during the object creation.

@Sov-trotter
Copy link
Member

So what the metatype macro does is create a struct eg: PointMeta and a meta() method for a Point Type. It also defines some overloads for getproperty etc.

This is helpful as it avoids much manual labour.

@codejaeger
Copy link
Contributor

codejaeger commented Jun 18, 2021

I was wondering whether we need a macro to make this work.

I think having a @Object macro solves the problem of assigning metadata to the currently created object. Having a macro, will allow partially creating an object (without a drawing function or any meta data assigned to it). Then the meta data assignment on the object can be done inside a custom shape function like Line. This will also allow using the nice syntax @Sov-trotter proposed i.e. meta(CURRENT_OBJECT[1], <some meta data>).

I am looking at a syntax like this

@Object(1:100, Line(p1, p2))
or
@Object 1:100 Line(p1, p2) O

This can also be extended to a list of objects

@Object 1:100 [Line(p1, p2), Line(p2, p3)] O

I was also hoping the following would be possible

@Object 1:100 Line(p1, p2) Line(p2, p3) O

but it would be difficult to parse this since there are also a variable number of keyword arguments passed to the Object constructor.

The separation can be thought to be made on whether a drawing function or a custom shape was being passed as an argument. What are your thoughts on it?

@Wikunia
Copy link
Member Author

Wikunia commented Jun 18, 2021

The problem we have with macros is that we can't use extensive dispatching functionality. I think we might be able to transform this into

Object(1:100, (args...; p1=p1, p2=p2) -> Line(p1,p2))

Not sure though whether that helps as we will have the same problem with things like:

@Object 1:100 Line(pos(o1),pos(o2))

where we want to call the functions at runtime. In that case it would only work if we just keep (args...)-> (without the kwargs)
but in that case we need a different way to use the change function to change those. One possibility there might be to use the metadata though. Maybe we can discuss this together @TheCedarPrince @Sov-trotter @codejaeger next week?

@codejaeger
Copy link
Contributor

Also, if the following manner of creating an object is supported

Object(1:100, [Line(p1, p2), Line(p2, p3)])

we will need to make the change keywords explicit to each "sub-object" or do some manipulation when calling the drawing functions of each "sub-object" individually so that only the keywords it requires are passed to it during calling it.
For my purposes of graph animation, I am using the syntax Object(1:100, (args...; p1=p1, p2=p2) -> Line(p1,p2)) to control animation with change function.

@Sov-trotter
Copy link
Member

This shouldn't be closed just yet? Or a separate issue to address metadata related discussion?

@Wikunia
Copy link
Member Author

Wikunia commented Aug 7, 2021

I'm currently unsure of what you're referring to but maybe it's best to open another issue for it. 😊

@Sov-trotter
Copy link
Member

I was referring to the fact that we still need a metadata implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants