I am plotting some geometry using bokeh and came across this. I am plotting a rectangle with equal sides (i.e. a square), and in that square, plotting a circle with diameter = width of the square. The circle should tangent to the square at edges, but it is not.
here is the code:
from bokeh.plotting import output_notebook, figure, show
output_notebook()
p = figure(width=500, height=500)
p.rect(0, 0, 300, 300, line_color='black')
p.circle(x=0, y=0, radius=150, line_color='black',
fill_color='grey', radius_units='data')
p.axis.minor_tick_out = 0
show(p)
Which results in this:
Is there anything I am doing wrong or could change to make the circle fit exactly in the square?
Thanks in advance,
Randall
Here's another case - just drawing a circle:
p = figure(width=500, height=500, x_range=(-150, 150), y_range=(-150, 150))
p.circle(x=0, y=0, radius=150, line_color='black',
fill_color='grey', radius_units='data')
show(p)
radius of the circle is 150 in the x direction, but not the y-direction.
I would like to report that as of Bokeh 0.12.7, this issue can now be fixed in a simpler manner.
As described in other posts, the main issue is not that the circle is not a circle, but that the square is not a square. This is due to the fact that actual area on which Bokeh draws the figure (the canvas) is usually not a square by default or even when the width and height are set to the same value. Bokeh by default will attempt to draw a figure by using up all the space on the canvas. This creates a mismatch between the data distance and the pixel distance of the plot.
As of 0.12.7, figures can now accept a match_aspect property which when set to True will will match the aspect of the data space to the pixel space of the plot.
In your example, simply adding the match_aspect = True in your figure
p = figure(width=500, height=500, match_aspect=True,
title="Circle touches all 4 sides of square")
p.rect(0, 0, 300, 300, line_color='black')
p.circle(x=0, y=0, radius=150, line_color='black',
fill_color='grey', radius_units='data')
will now produce
UPDATE: Please note new answer by #DuCorey below. As of Bokeh 0.12.7, aspect control is now available, for situations like this.
The issue is actually that the square is not square, and that is because the pixel aspect ratio and the "data" aspect ratio do not match. i.e., the distance per pixel is different in the x direction than it is in the y direction.
There are a few options:
You can use various properties to control the dimensions of the central plot area (e.g. plot border width and axis tick label orientation) You can also control you data ranges explicitly. In other words, you can make the aspect ratios match, and then the circle and rect will match
You can use absolute pixel units (e.g. size for a circle, and use a large square marker instead of rect) instead of "data" units.
Alternatively, if you want a circle that "deforms" when the aspects do not match, then your best bet is to use an ellipse with an identical width and height, which will work because in this case bokeh has two dimensions to use to measure (instead of the single radius) and can match each to the scale along each dimension independently.
(This is actually the fundamental difference that explains the behaviour: rect has two dimensions to measure independently. circle does not, it only has one, and has to arbitrarily use the x or y dimension to measure distance per pixel)
ok, based on the suggestions, I tried a few things.
Changed the orientation of the y-axis tick labels - still
had issue.
Changed various stand-offs, even moving in the tick
labels inside the plot (with a negative offset). Did not work either.
Changed the x_range and r_range in figure() to be equal tuples. Did not work either
Changes the plot_height (decreased it), and I could eventually, through rial and error, get the circle to fit in the square with a plot_height that was < plot width.
Lots of great practice controlling attributes of the plot. Time will invested.
However, the last change I tried worked the best. It was one of the first suggestions - change the plot border.
Weirdly, setting p.min_border=40, which on 0.12.6 is the default value, and voila, it appears the chart aspect ratio for a chart where plot_width=plot_height is truly 1 on the screen as well.
p = figure(plot_width=500, plot_height=500)
p.rect(0, 0, 300, 300, line_color=None)
p.circle(x=0, y=0, radius=150, line_color=None,
fill_color='lightgrey', radius_units='data')
p.min_border=40
show(p)
Before and after images showing the effect of adding p.min_border=40. Any value over ~33 appeared to be enough force the plot area to have the same screen x and y dimension - so the square was really a square (and the circle fit inside).
The reason for this is that you're creating a circular marker (or circle glyphs) and placing it at position (0, 0), while it seems like you want to create a circle centered at 0.
I think the rect here "happens" to work because it can scale correctly in both dimensions and remain a "rectangle".
Keyword Args:
radius (UnitsSpecPropertyDescriptor) : The radius values for circle markers (in "data space" units, by default). (default None)
radius_dimension (BasicPropertyDescriptor) : What dimension to measure circle radii along. (default 'x')
radius_units (Enum('screen', 'data')) : (default 'data')
I guess my point is here you've taken a shortcut by trying to use a "glyph" as your plot and specifying the units to be the data units.
If you want to create an actual circle you could do the following:
th = np.linspace(0, 2*np.pi)
r = 150
p = figure(width=500, height=500)
p.rect(0, 0, 300, 300, line_color='black')
p.line(r * np.cos(th), r * np.sin(th), line_color='black')
# p.circle(x=0, y=0, radius=150, line_color='black',
# fill_color='grey', radius_units='data')
p.axis.minor_tick_out = 0
show(p)
Notice the above is harder to fill (I didn't bother) because I'm guessing you need to define some closed polygon function while I only defined a line that happens to be a closed polygon, in this case a circle.
Not sure, but the bleu rectangle is not your rectangle.
Replace:
p.rect(0, 0, 300, 300, line_color='black')
By:
p.rect(-150, -150, 150, 150, line_color='black')
Related
I'm trying to set as initial camera of a 3D volume plot where the upper left corner is the origin (x, y, z = 0). I've read the documentation about the camera controls but cannot figure out how can I accomplish this.
The initial view I want it's something like this:
I tried it and this one work on me
If you want the front upper left corner as (0,0,0)
camera = dict(
eye=dict(x=0, y=-0.5, z=-2.5)
)
fig.update_layout(scene_camera=camera, title=name)
fig.show()
what I understand from this eye is basically the position of the eye(or you) look at eyepoint(0,0,0) which is I believe the center of the 3D graph (not the coordinate)
And if you need to change the axes direction to the opposite, you can try to put it on negative on the eye position, and if it is zero you can put negative small number (in this example I used -0.5, but you can use -0.01 too)
I'm implementing an experiment in Psychopy 1.85.1.
The task will be administered on a 1920 x 1080 monitor (i.e. 16:9 aspect ratio).
Rather than change the size of each individual visual image object I use by the following:
some_obj.size *= [0.6,1]
I'd rather use a single line of code that just universally chances the size parameters of all the visual objects to account for the warping caused by being in a 16:9 ratio.
Any suggestions?
EDIT: Below is an example of the stretch caused by psychopy's default to an 4:3 ratio and the appropriate image. Left is what occurs when the image size is unchanged, right is what occurs when the image size is altered as shown above.
The window for the experiment is created by:
myWin = visual.Window(size = (1200,675), fullscr = False,
winType = 'pyglet', color = 'white')
The circle images, as they appear above, and created by:
testCircleL = visual.Circle(myWin, fillColor = 'lightskyblue', lineColor = 'lightskyblue', units = 'norm',
radius = 0.5, pos=(-.50,0.15))
testCircleR = visual.Circle(myWin, fillColor = 'lightskyblue', lineColor = 'lightskyblue',
radius = 0.5, pos=(0.5,0.15)); testCircleR.size *= [0.6,1]
As Mike pointed out in the comments, If maintaining the aspect ratio is the issue, use height, pix (pixels), or deg as units in the window, e.g., myWin = visual.Window(units='pix'). Notice that for deg, you have to enter some distance measures in the Monitor Centre for it to work. Also, notice that you will have to rescale the numbers in your script. For example, a radius of 0.5 "norms" is big while a radius of 0.5 pixels barely covers one pixel.
If you really wanted to scale many stimuli at once, you would probably do it in a loop:
# Set up many visual objects
stim1 = visual.Circle(myWin)
stim2 = visual.ImageStim(myWin)
stim3 = visual.GratingStim(myWin)
# Scale multiple visual objects
for this_stim in [stim1, stim2, stim3]:
this_stim.size *= [0.6, 1]
Of course, this only works for stimuli with a size attribute.
I have a problem in controlling the size of objects in network plots done by igraph. The documentation of the plot command says:
bbox:: The bounding box of the plot. This must be a tuple containing the desired width and height of the plot. The default plot is 600 pixels wide and 600 pixels high.
arrow_size: Size (length) of the arrowhead on the edge if the graph is directed, relative to 15 pixels.
vertex_size: Size of the vertex in pixels
So to my understanding all these arguments represent numbers of pixels.
Therefore, multiplying all of them, say, by a factor of 2, I would expect the images to scale completely with this factor.
Consider this following minimal example in python:
from igraph import Graph, plot
def visualize(res=1.0):
g=Graph([(0,1), (1,0)], directed=True)
layout = g.layout_fruchterman_reingold()
plot(g, target='plot.png',
layout=layout,
bbox=(120*res,120*res),
vertex_size=5*res,
arrow_size=10*res)
This plots a trivial graph,
However for res=1.0 and res=2.0 the arrows and vertices become smaller compared to the image size.
How is that possible?
Just a wild guess, but could the stroke width account for the difference? The default stroke width is 1 units, and you don't seem to scale the stroke width. Try setting vertex_frame_width=res in the call to plot().
How can I draw shapes in matplotlib using point/inch dimensions?
I've gone through the patch/transform documentation so I understand how to work in pixel, data, axes or figure coordinates but I cannot figure out how to dimension a rectangle in points/inches.
Ideally I would like to position a rectangle in data coordinates but set its size in points, much like how line markers work.
Here is an example of the plot I am trying to create. I currently position the black and red boxes in (data, axes) coordinates. This works when the graph is a known size, but fails when it gets rescaled as the boxes become smaller even through the text size is constant.
Ended up figuring it out with help from this question: How do I offset lines in matplotlib by X points
There is no built in way to specify patch dimensions in points, so you have to manually calculate a ratio of axes or data coordinates to inches/points. This ratio will of course vary depending on figure/axes size.
This is accomplished by running a (1, 1) point through the axes transform and seeing where it ends up in pixel coordinates. Pixels can then be converted to inches or points via the figure dpi.
t = axes.transAxes.transform([(0,0), (1,1)])
t = axes.get_figure().get_dpi() / (t[1,1] - t[0,1]) / 72
# Height = 18 points
height = 18 * t
I am trying to draw a tilted ellipse in image draw. However, I am not sure how to define it, since while the scheme below would move the points, I think this would just squish the ellipse, not rotate it (also I think there is something slightly wrong with the transformation in any case). I am feeding the output of this function into the ellipse command and adding it to an existing picture, so any methods that would rotate the entire image are no good. OD is just a square offset to the coordinate center I am using.
def ellipsebound(major, minor, tilt=0, offset=0, angle=0):
#creates a bound for an ellispe, defined with tilt meaning to rotate the orthogonal axis and angle corresponds to rotating the ellipse position
angle = radians(angle)
tilt = radians(tilt)
box=(
1 + int(ceil((OD+offset*cos(angle)+(major*cos(tilt)+minor*sin(tilt)))/conv)),
1 + int(ceil((OD+offset*sin(angle)+(major*sin(tilt)-minor*cos(tilt)))/conv)),
int(ceil((2*OD-(OD-offset*cos(angle)-(major*cos(tilt)+minor*sin(tilt)))/conv))),
int(ceil((2*OD-(OD-offset*sin(angle)-(major*sin(tilt)-minor*cos(tilt)))/conv)))
) #create bounding box
return box
Does anyone know how to accomplish this?
It looks like the 'box' that is being used to draw the ellipse has no rotation associated with it. It is simply defined by the (left, top, right, bottom) extents.
One possible workaround (depending on what you need to do) is to draw the ellipse (sized correctly, but without the rotation) onto an intermediary image, use the image.rotate() method, and then paste it into your target image.
I hope that helps.