Diagrams boundingRect including the lines' width - haskell

Is there an equivalent to the boundingRect function which includes the diagram's lines width(*), so that each line, however thick it is, is entirely contained within the bounding rectangle? (the boundingRect function "ignores" their thickness and parts of the lines stay outside the bounding rectangle).
(*) My question is for lines with width expressed in local units.

Unfortunately there's no way to do this automatically yet. The easiest way would be to frame the diagram before finding the boundingRect. Since you're using the local units you just need to frame half the local width used in the diagram (add half of the line width used for the bounding rectangle if that has a line too).
Here's a simple example:
{-# LANGUAGE GADTs #-}
import Diagrams.Prelude
import Diagrams.Backend.Rasterific.CmdLine
main :: IO ()
main = mainWith $ frame 1 rects
rects :: Diagram B
rects = hsep 1 $ map (dia <>) [br1, br2, br3]
where
br1 = boundingRect dia # lwL 0.2 # lc red
br2 = boundingRect (frame 0.1 dia) # fc dodgerblue # lw none
br3 = boundingRect (frame 0.2 dia) # lwL 0.2 # lc red
dia :: Diagram B
dia = circle 3 # fc orange # lwL 0.2
A more general solution would be to draw the offset curves of each path using the local line width in the Diagram and find the bounding box of that. Diagrams.TwoD.Offset can almost do this but I don't think it works for all cases.

Related

Is it accurate to conclude the radius of a circle given 4 bazier curves in svg?

I have used svg2paths2, and wanted to figure out what is the position and radius of a circle, I have noticed the circle is consructed by 4 CubicBezier, as follow:
Path(CubicBezier(start=(127.773+90.5469j), control1=(127.773+85.7656j), control2=(123.898+81.8906j), end=(119.121+81.8906j)),
CubicBezier(start=(119.121+81.8906j), control1=(114.34+81.8906j), control2=(110.465+85.7656j), end=(110.465+90.5469j)),
CubicBezier(start=(110.465+90.5469j), control1=(110.465+95.3281j), control2=(114.34+99.1992j), end=(119.121+99.1992j)),
CubicBezier(start=(119.121+99.1992j), control1=(123.898+99.1992j), control2=(127.773+95.3281j), end=(127.773+90.5469j)))
I have read the standard approach is to divide the circle into four equal sections, and fit each section to a cubic Bézier curve.
So I was wondering is it accurate to say the Radius of the circle is
(q1.start.real - q3.start.real)/2
or
(q2.start.imag - q4.start.imag)/2
And the center of the circle is:
c_x = (q1.start.real + q1.end.real) / 2
c_y = (q1.start.imag + q1.end.imag) / 2
Thank you!
I'm assuming you are using svg.path library in python, or svg2paths2 is related.
from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, Close
path = Path(CubicBezier(start=(127.773+90.5469j), control1=(127.773+85.7656j), control2=(123.898+81.8906j), end=(119.121+81.8906j)),
CubicBezier(start=(119.121+81.8906j), control1=(114.34+81.8906j), control2=(110.465+85.7656j), end=(110.465+90.5469j)),
CubicBezier(start=(110.465+90.5469j), control1=(110.465+95.3281j), control2=(114.34+99.1992j), end=(119.121+99.1992j)),
CubicBezier(start=(119.121+99.1992j), control1=(123.898+99.1992j), control2=(127.773+95.3281j), end=(127.773+90.5469j)))
q1 = path[0]
q2 = path[1]
q3 = path[2]
q4 = path[3]
.real is the X coordinate
.imag is the Y coordinate
There's a very slight error in accuracy in the drawing program you are using and it's not at all an issue unless you want extreme accuracy.
(q1.start.real - q3.start.real) / 2 # 8.6539 is the radius in this case.
(q4.start.imag - q2.start.imag)/2 # 8.6543 is also the radius.
(q1.start.real - q1.end.real) # 8.6539 is again also the radius.
This accesses the same property, q1 of path and I' prefer it to the two above ways because it's accessing one property not two.
Below shown by green circle in diagram
c_x = (q1.start.real + q1.end.real) / 2 # 123.447 not center x
c_y = (q1.start.imag + q1.end.imag) / 2 # 86.21875 not center y
Below shown by red circle in diagram
c_x = q1.end.imag # 119.121 this is center x
c_y = q1.start.real # 90.5469 this is center y
To explain how serious the error in accuracy, the pink circle uses 8.6543 radius, below it is 8.6539 in green, perhaps viewable with an extreme zoom. But this does illustrate how important or not the decimal points can be.
Consider using numbers under 100 and as few decimal points as possible, especially understanding a new idea. Shorter text-length numbers vastly improves readability, understanding no end.
I often use just numbers below ten.
Note: you are drawing the circle counter-clockwise. Clockwise is the usual way.

How to combine shapes via set operations?

I would like to subtract one shape from another, and then combine the resulting shape with another shape. In my example a square is to be clipped in half and that clipped version is to be extended by a half circle to the right.
So I subtract one square from the other via difference and make a union with the whole circle assuming that overlapping areas will just merge.
I'm thinking in terms of set operations where ({1,2,3,4} / {3,4}) U {2,3} equals {1,2,3} but in my implementation it equals {1,3}:
import Diagrams.Backend.SVG.CmdLine
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
import Diagrams.Prelude
import qualified Diagrams.TwoD.Path.Boolean as B
main = mainWith (combination # fc red # bgFrame 0.1 white)
where
combination :: QDiagram B V2 Double Any
combination = strokePath plusCircle
shorterSquare = B.difference Winding (square 2) (square 2 # translateX 1)
plusCircle = B.union Winding (circle 1 <> shorterSquare)
But I get this:
This is not what I want, I want the half circle merged with the rectangle, and the result to be filled just red with no lines inside.
This particular usage of B.difference reverses the direction of the shorterSquare path, so you need to re-reverse it:
shorterSquare = B.difference Winding (square 2) (square 2 # translateX 1)
# reversePath
As this is quite subtle, it is worth it to spend a moment describing how I diagnosed it. Firstly, such fill rule wackiness felt quite like the sort of issue caused by path (or face, etc.) orientations. Secondly, redefining shorterSquare as...
shorterSquare = square 2 # scaleX 0.5 # translateX 0.5
... gives the expected result. That means the issue has to do with B.difference and the definition of shorterSquare, rather than with B.union. Confirmation can be obtained through pathVertices:
GHCi> -- Counterclockwise.
GHCi> pathVertices $ square 2 # scaleX 0.5 # translateX 0.5
[[P (V2 1.0 (-1.0)),P (V2 0.9999999999999999 1.0),P (V2 (-1.1102230246251565e-16) 1.0),P (V2 (-2.220446049250313e-16) (-1.0))]]
GHCi> -- Clockwise.
GHCi> pathVertices $ B.difference Winding (square 2) (square 2 # translateX 1)
[[P (V2 (-1.0) 1.0),P (V2 0.0 1.0),P (V2 0.0 (-1.0)),P (V2 (-1.0) (-1.0))]]
I'm not an expert on Diagrams, but it looks like you are combining stroke paths rather than the shapes they represent. Fill Rules has some interesting things to say about how the Winding fill rule behaves for stroke paths that overlap themselves, which seems relevant to explaining why you get the result you do.
Instead, I'd suggest using the techniques in Composing diagrams, such as atop, to compose the completed shapes.

Python 3 Tkinter canvas

I am trying to draw a clock that works. I am using a 600x600 form. I cant' figure out how to place the oval in the center of the form or how to add the minutes or the seconds tick marks inside the oval. I tried dash but couldn't get it to look right. Any suggestions. Thanks in advance.
This is what I have done so far:
from tkinter import *
canvas_width = 600
canvas_height = 600
master = Tk()
w = Canvas(master, width = canvas_width, height = canvas_height)
w.pack()
oval = w.create_oval(75,75,500,500)
minline = w.create_line(150,150,300,300)
mainloop()
The center of a drawn shape is the middle of the two points specified when it is drawn. Currently, the middle point of your shape (draw from 75, 75 to 500, 500) is 237.5, so, if you want the middle of it to be the middle of your page, and keep the 75, 75 coordinate, you would have to make the other one 525, 525 to completely mirror the first.
As for drawing the shape, you'll need some math in python, so I would first suggest doing an image as the background for the clock, so that less objects are drawn. But, if you must do it without other images, you must first import the math library.
import math
Now, for a mathematic principle: Any point on the circle of radius r can be expressed as the point (r*cosθ), (r*sinθ), where θ is the angle from the center to the point. The reason this is important is that you want each line on the side of the clock face to be pointing towards the center of the circle. To do that, we need the two points to draw the line on that together point towards the center, and fortunately for us this means that both points on the line are on different circles (our circle and one within it) but are at the same angle from the center.
Since we want 12 hour points around the circle, and 4 minute points between each of those (so 60 points in total), and 360 degrees in a circle (so 1 point for every 6 degrees), we will need a for loop that goes through that.
for angle in range(0, 360, 6):
Then we'll want 3 constants: One for the radius of the exterior circle (for the points to begin from), one for an interior circle (for the minute points to add at), and one for an even more interior circle (for the hour points to end at). We'll also want it to choose the more interior radius only every 30 degrees (because it appears every 5 points, and there are 6 degrees between them).
radius_out = 225
radius_in = 0 #temporary value
if (angle % 30) == 0: #the % symbol checks for remainder
radius_in = 210
else:
radius_in = 220
Now, for the conversion into radians (As math in python needs radians for sin and cos):
radians = (angle / 180) * math.pi
Next off, assigning the coordinates to variables so it's easier to read.
x_out = (radius_out * math.cos(radians)) + 300
y_out = (radius_out * math.sin(radians)) + 300
x_in = (radius_in * math.cos(radians)) + 300
y_in = (radius_in * math.sin(radians)) + 300
#the (+ 300) moves each point from a relative center of 0,0 to 300,300
And finally we assign it to a list so we can access it later if we need to. Make sure to define this list earlier outside of the for loop.
coords.append( w.create_line(x_out, y_out, x_in, y_in) )
This should give you your clock lines.
NOTE: Due to the way tkinter assigns x and y coordinates, this will draw lines from the 3 hour line clockwise back to it.
Hope this was helpful! If there is anything you don't understand, comment it below.

How can I draw two shapes in a time sequence using Gloss?

I have a simple window displaying two shapes like so:
import Graphics.Gloss
circles = pictures [Translate 80 0 one, Translate (-80) 0 two]
main = display (InWindow "t" (400,400) (800,0)) white circles
one = Color red $ Circle 80
two = Color blue $ Circle 50
I am new to Gloss, but from what I gather "display" just displays a static image once main (i.e. my module) is run so you cant make an animation using "display" right?
What I want to do is to run my program with these shapes, but instead of displaying them both at once, I want to first display one circle, the next second the other circle like some kind of animation.
So far I can only do something static and display both circles at once immediately when the program is run. But I want them to appear after each other like Run the program -> (0 sec) Blank screen -> (1 sec) One of the circles is drawn -> (2 sec) the other circle is drawn -> The window now displays circles until I close it.
This should be so simple using the "animate" function but I can't figure it out. If anyone is out there with knowledge, please consider helping! It would really make my day.
You use animate in order to draw a picture depending on the animation time:
main = animate (InWindow "t" (400,400) (800,0)) white draw
draw :: Float -> Picture
draw t
| t <= 1 = blank -- in the first second
| t <= 2 = pictures [Translate 80 0 one] -- between 1s and 2s
| otherwise = pictures [Translate 80 0 one, Translate (-80) 0 two] -- afterwards

How to draw concentric hexagons?

I need to draw concentric hexagons (4 to 10 in each set) randomly using Python turtle graphics (see image). I can draw random hexagons but can not make concentric ones:
import turtle
from random import randint
window = turtle.Screen()
window.bgcolor("yellow")
brad= turtle.Turtle()
brad.color("blue")
window.colormode(255)
def drawPoly(sideLen, noOfsides):
for i in range(noOfsides):
brad.forward(sideLen)
brad.left(360/noOfsides)
for i in range(20):
sideLen = randint(20,150)
xpos = randint(-200,200)
ypos = randint(-200,200)
brad.pensize(randint(1,3))
brad.pencolor(randint(1,255),randint(1,255),randint(1,255))
brad.penup()
brad.setpos(xpos,ypos)
brad.pendown()
drawPoly(sideLen,6)
window.exitonclick()
Here's a link to image
How can I make hexagons within a hexagon for 4 to 10 times and then move to the next hexagon?
The concept of an mcve applies to development as well as posting here. Start with a simple function or block of code that makes concentric hexagons without worrying about color, thickness, or style of the lines.
The key thing about hexagons is that the 'radius' (center to corners) is the same as the edge length (corner to corner). (Concentric pentagons, for instance, are harder, requiring some trig.) Assume you want two corners on the x axis. If the center is x,y, start at x+e,y at a rotation of 120 (where 0 is facing right). Given x, y, a list edges of radii-edge lengths, and turtle t in a pen-up state, the following should work.
for edge in edges:
t.setpos(x+e, y)
t.setheading(120)
t.pendown()
for i in range(6):
t.forward(e)
t.left(60)
t.penup()
EDIT: replace setangle with setheading, thanks to comment by cdlane.

Resources