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

repeat - new higher-order transformation #1

Closed
anandology opened this issue Jun 7, 2021 · 12 comments · Fixed by #9
Closed

repeat - new higher-order transformation #1

anandology opened this issue Jun 7, 2021 · 12 comments · Fixed by #9
Assignees

Comments

@anandology
Copy link
Contributor

I'm proposing a new higher-order transformation repeat, what would apply a transformation repeatedly and combine all the resulting shapes.

For example:

repeat(rect(), n=4, transform=lambda s: scale(rotate(s, angle=45), sx=1/SQRT2))

would produce:

Screenshot 2021-06-07 at 10 04 52 AM

New Transform API

To make it easier to use the transforms, I propose a new API for transforms.

>>> t = rotate(45) | scale(1/SQRT2)
repeat(rect(), n=4, transform=t)

All the transform functions work in two ways.

When a shape argument is suplied, it returns a new shape with the transformation applied. For example rotate(rect(), 45) would rotate the shape by 45 degrees.

When shape argument is not supplied, it returns a transformation object. rotate(45) return the transformation object that rotates given shape by 45 degres.

t = rotate(45)
t.apply(rect()) # apply the transformation
t(rect()) # another way to apply the transformation

A new transformation can be created by combining multiple transformations and the | is used for combining them.

>>> t = rotate(45) | scale(1/SQRT2)
>>> repeat(rect(), n=4, transform=t)

Even repeat can be called without a shape and that creates a new transformation.

With this we can do create many transformations with ease.

Some examples:

Draw four circles in a row:

repeat(circle(r=50), n=4, transform=translate(x=100))

Draw a 4x4 grid of circles.

grid = repeat(n=4, transform=translate(y=100)) | repeat(n=4, transform=translate(x=100))
grid(circle(r=50))

In fact, the cycle function will become a special case of repeat.

@anandology anandology self-assigned this Jun 7, 2021
@anandology
Copy link
Contributor Author

@amitkaps please let me know your comments.

@anandology
Copy link
Contributor Author

Also, I would like to make it easier to specify anchor point when doing rotations.

For example, I want to draw a square by rotating a the line by 90 degrees 4 times. It could be expressed as:

repeat(line(), n=5, transform=rotate(90, anchor="end"))

Every shape maintains important points for that shape. Here are the points that are available for each shape:

  • line - start and end
  • circle - center, top, bottom, left, right
  • rectangle - top_left, top_right, bottom_left, bottom_right or (A,B,C,D)

Some examples of the possibilties with this:

Rotate the circle around the right most point.

repeat(circle(), n=36, transform=scale(angle=10, anchor="right"))

Rotate the rectangle around the top-left corner.

repeat(rect(), n=36, transform=scale(angle=10, anchor="A"))

When combining multiple shapes, we could give labels to each shape and use it when specifying anchors.

shape = combine(
    c=circle(cx0, cy=0, r=50), 
    d=line(x1=-100, y1=0, x2=100, y2=0))

cycle(shape, n=36, transform=rotate(angle=10, anchor="d.end")

@amitkaps
Copy link

amitkaps commented Jun 7, 2021

In principle, I like the functional pipe transformation syntax as well as the construct of a transform function without applying to a shape. Both are intuitive to understand for me.

For the rotation along a point, the point construct makes sense.

Not sure about extracting points from rect and circle though based on canvas coordinate direction, as that is unclear (after any transforms) and basically our discussion on #2.

In general for polygon - ordered vertices would make sense and for arc/curved lines - equal partitions of length segments make sense. Adds complexity so maybe park it for now.

@anandology
Copy link
Contributor Author

In principle, I like the functional pipe transformation syntax as well as the construct of a transform function without applying to a shape. Both are intuitive to understand for me.

Agreed. I liked the pipe syntax.

For the rotation along a point, the point construct makes sense.

Do you mean the following?

Rotate(90, anchor="end")

Not sure about extracting points from rect and circle though based on canvas coordinate direction, as that is unclear (after any transforms) and basically our discussion on #2.
In general for polygon - ordered vertices would make sense and for arc/curved lines - equal partitions of length segments make sense. Adds complexity so maybe park it for now.

Agreed. For rect, having .points is good enough. Will leave the circle/arcs for now.

@anandology
Copy link
Contributor Author

With every basic shape and transformation becoming title-case classes, only show and combine remains as functions. Wouldn't that be confusing about when to use uppercase and when to use lowercase?

@anandology
Copy link
Contributor Author

Wondering if it would be a good ideas to replace combine with + operator. What do you think @amitkaps ?

def donut(center, radius):
    c1 = Circle(center=center, radius=radius)
    c2 = Circle(center=center, radius=radius/2)
    return c1+c2

@anandology
Copy link
Contributor Author

I'm going to complete this task leaving out the labeled points for anchor. Will create another issue to handle that.

@amitkaps
Copy link

amitkaps commented Jun 9, 2021

On case - here is a thought.

Nouns - Objects like Shapes - Titlecase
Verbs - Actions like transform - lowercase

anandology added a commit that referenced this issue Jun 9, 2021
@anandology
Copy link
Contributor Author

On case - here is a thought.

Nouns - Objects like Shapes - Titlecase
Verbs - Actions like transform - lowercase

That is an interesting thought.

So we'll have translate, rotate, scale and repeat instead of Translate, 'Rotate, ScaleandRepeat`.

Now could could add more higher-order transformations like cycle as functions.

We could even have a style transformation.

shape = Circle() + Ellipse()
shape2 = shape | style(fill="red", stroke="none")

@anandology
Copy link
Contributor Author

anandology commented Jun 9, 2021

I'm not completely convinced.

The Rotate/rotate is not really an action. It is creating a new transformation object. Calling it RotateTransform may be a more appropriate, but that is too long.

Just like Circle() is creating a shape, Rotate(...) is creating a transformation. Why should there be a case variation between them?

c = Rectangle()
tx = Rotate(angle=45)
show(c | tx)

It would have made sense to use lower case if they were really actions, like in this example:

rotate(Circle(), angle=45)

@anandology
Copy link
Contributor Author

@amitkaps any comments?

@amitkaps
Copy link

Noun = Objects = Data, which are currently Shapes (Singular or Group), Numeric (Integer, Float), String, Boolean, Lists(?)
Verbs = Actions = Transformations, which operate on Data (Shapes), so transform, style similiar to map, filter, sort in a typical numeric based transformation.

Maybe case syntax is not that important to make this difference clear - using nouns & verbs properly is enough. That was my thought process.

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

Successfully merging a pull request may close this issue.

2 participants