Module concur.draw
Passive geometric shape widgets to be drawn as image()
or frame()
overlay, or on their own.
All these widgets have the following in common:
- They are passive, so they don't need names. For active overlay, several mechanisms can be used, listed below.
- They don't do automatic layout. Instead the exact position is specified by hand.
- Color can be specified in several ways:
- RGBA tuple with values between 0 and 1. For example,
(0.5, 0.5, 1, 1)
is light blue. - RGB tuple with values between 0 and 1. The result is opaque.
- String specifying a color from the xkcd color set, for example,
'red'
. (str, float)
pair, where the first element specifies color, and the second element specifies alpha.- A single
int
, specifying ABGR color. For example,0xffaa0000
is dark blue.
- RGBA tuple with values between 0 and 1. For example,
tf
is theTF
object specifying transformations from screen-space to image-space and back. If no transformation is supplied, the element is drawn in screen space units.
Theses widgets are not re-exported in the root module, and are normally used as c.draw.line(…)
, etc.
They can be composed normally using the orr()
function.
Creating Interactive Overlays
There are several ways of creating overlays which react to user input.
-
Using interactive widgets as overlay. Widgets, such as buttons, can be displayed as overlay at a specified position using the
transform()
function. This can be used also for dragging stuff by wrapping the widget in adraggable()
. To create rectangular clickable areas, theinvisible_button()
widget can be used. See the image example for a basic usage example. -
Using the events managed by
image()
andframe()
. Thedrag_tag
,down_tag
, andhover_tag
arguments can be used to implement complex interactions, such as control point editing. See the image_events example for a basic usage example. This option will give you click/hover positions and drag deltas, but it is up to you to implement any logic on top of that, such as highlighting the hovered line, etc. -
Using the
mouse_click()
widget. This widget just returns mouse position for any click not inside any widget. This is the simplest option, but it is probably better to use the other options. See the annotation tool example for an usage example.
Expand source code
""" Passive geometric shape widgets to be drawn as `concur.extra_widgets.image.image` or `concur.extra_widgets.frame.frame` overlay, or on their own.
All these widgets have the following in common:
* They are passive, so they don't need names. For active overlay, several mechanisms can be used, listed below.
* They don't do automatic layout. Instead the exact position is specified by hand.
* Color can be specified in several ways:
* RGBA tuple with values between 0 and 1. For example, `(0.5, 0.5, 1, 1)` is light blue.
* RGB tuple with values between 0 and 1. The result is opaque.
* String specifying a color from the [xkcd color set](https://xkcd.com/color/rgb/), for example, `'red'`.
* `(str, float)` pair, where the first element specifies color, and the second element specifies alpha.
* A single `int`, specifying ABGR color. For example, `0xffaa0000` is dark blue.
* `tf` is the `concur.extra_widgets.pan_zoom.TF` object specifying transformations from screen-space to image-space and back.
If no transformation is supplied, the element is drawn in screen space units.
Theses widgets are not re-exported in the root module, and are normally used as `c.draw.line(...)`, etc.
They can be composed normally using the `concur.core.orr` function.
## Creating Interactive Overlays
There are several ways of creating overlays which react to user input.
* **Using interactive widgets as overlay**. Widgets, such as buttons, can be displayed as overlay at a specified
position using the `concur.widgets.transform` function. This can be used also for dragging stuff by wrapping the
widget in a `concur.extra_widgets.draggable.draggable`. To create rectangular clickable areas,
the `concur.widgets.invisible_button` widget can be used. See the
[image example](https://github.com/potocpav/python-concur/blob/master/examples/image.py) for a basic usage example.
* **Using the events managed by `concur.extra_widgets.image.image` and `concur.extra_widgets.frame.frame`**. The
`drag_tag`, `down_tag`, and `hover_tag` arguments can be used to implement complex interactions, such as control point
editing. See the
[image_events](https://github.com/potocpav/python-concur/blob/master/examples/extra/image_events.py) example for
a basic usage example. This option will give you click/hover positions and drag deltas, but it is up to you to
implement any logic on top of that, such as highlighting the hovered line, etc.
* **Using the `concur.widgets.mouse_click` widget**. This widget just returns mouse position for any click not inside any
widget. This is the simplest option, but it is probably better to use the other options. See the
[annotation tool](https://github.com/potocpav/annotation-tool/blob/master/annotator.py) example for an usage example.
"""
import numpy as np
import imgui
from concur.colors import color_to_rgba
from concur.core import nothing
__pdoc__ = dict(prepare_polyline_points=False)
def line(x0, y0, x1, y1, color, thickness=1, tf=None):
""" Line connecting two points. """
if tf is not None:
[x0, y0], [x1, y1] = tf.transform(np.array([[x0, y0], [x1, y1]]))
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_line(x0, y0, x1, y1, col, thickness)
yield
def rect(x0, y0, x1, y1, color, thickness=1, rounding=0, tf=None):
""" Straight non-filled rectangle specified by its two corners. """
if tf is not None:
[x0, y0], [x1, y1] = tf.transform(np.array([[x0, y0], [x1, y1]]))
# Avoid issues with disappearing lines on very large rectangles
x0, x1 = np.clip([x0, x1], -8192, 8192)
y0, y1 = np.clip([y0, y1], -8192, 8192)
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_rect(x0, y0, x1, y1, col, rounding, 15 if rounding else 0, thickness)
yield
def rects(rects, color, thickness=1, tf=None):
""" Multiple straight non-filled rectangles specified by their two corners.
`rects` is a NumPy array of shape `(n, 4)`, where `n` is the number of rectangles.
"""
if len(rects) == 0:
while True:
yield
if tf is not None:
rects = tf.transform(rects.reshape(-1, 2)).reshape(rects.shape)
# Avoid issues with disappearing lines on very large rectangles
rects = np.clip(rects, -8192, 8192)
polys = np.empty((len(rects), 4, 2), dtype=rects.dtype)
polys[:, 0] = rects[:, :2]
polys[:, 1, 0] = rects[:, 0]
polys[:, 1, 1] = rects[:, 3]
polys[:, 2] = rects[:, 2:]
polys[:, 3, 0] = rects[:, 2]
polys[:, 3, 1] = rects[:, 1]
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_polylines(polys, col, True, thickness)
yield
def rect_filled(x0, y0, x1, y1, color, rounding=0, tf=None):
""" Straight non-filled rectangle specified by its two corners. """
if tf is not None:
[x0, y0], [x1, y1] = tf.transform(np.array([[x0, y0], [x1, y1]]))
# Avoid issues with disappearing lines on very large rectangles
x0, x1 = np.clip([x0, x1], -8192, 8192)
y0, y1 = np.clip([y0, y1], -8192, 8192)
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_rect_filled(x0, y0, x1, y1, col, rounding, 15 if rounding else 0)
yield
def circle(cx, cy, radius, color, thickness=1, num_segments=16, tf=None):
""" Circle specified by its center and radius. """
if tf is not None:
assert np.allclose(np.abs(tf.c2s[0, 0]), np.abs(tf.c2s[1, 1])), \
"`tf` must be aspect ratio preserving to draw circles. Use `ellipse` instead, if it isn't the case."
[cx, cy], radius = np.matmul(tf.c2s, [cx, cy, 1]), radius * tf.c2s[0, 0]
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_circle(cx, cy, radius, col, num_segments=num_segments, thickness=thickness)
yield
def prepare_polyline_points(points, tf):
if len(points) == 0:
return np.zeros((0, 2))
if not isinstance(points, np.ndarray):
points = np.array(points)
if tf is not None:
points = tf.transform(points)
return points
def polyline(points, color, closed=False, thickness=1, tf=None):
""" Polygonal line or a closed polygon.
`points` is a list of (x, y) tuples, or a NumPy array of equivalent shape.
"""
points = prepare_polyline_points(points, tf)
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_polyline(points, col, closed, thickness)
yield
def polygon(points, color, tf=None):
""" Filled polygon. Points must form a convex area.
`points` is a list of (x, y) tuples, or a NumPy array of equivalent shape.
"""
points = prepare_polyline_points(points, tf)
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_convex_poly_filled(points, col)
yield
def polylines(points, color, closed=False, thickness=1, tf=None):
""" Multiple polygonal lines with the same length and parameters.
Calling this function is more efficient than calling `polyline` multiple times, because all the data is given
to the C++ back-end in one Python call, and because transformation is vectorized.
`points` is a NumPy array with shape `(n, m, 2)`, where `n` is the number of polylines, and `m` is the number of points
in each polyline.
"""
if len(points) == 0:
while True:
yield
if tf is not None:
points = tf.transform(points.reshape(-1, 2)).reshape(points.shape)
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_polylines(points, col, closed, thickness)
yield
def polygons(points, color, tf=None):
""" Multiple filled polygons with the same length and color.
Calling this function is more efficient than calling `polygon` multiple times, because all the data is given
to the C++ back-end in one Python call, and because transformation is vectorized.
`points` is a NumPy array with shape `(n, m, 2)`, where `n` is the number of polygons, and `m` is the number of points
in each polyline.
"""
if tf is not None:
points = tf.transform(points.reshape(-1, 2)).reshape(points.shape)
draw_list = imgui.get_window_draw_list()
col = color_to_rgba(color)
while True:
draw_list.add_convex_polys_filled(points, col)
yield
def text(string, x, y, color, tf=None):
""" Text, using the default font and font size.
This is a raw drawing function. Use `concur.widgets.text` instead if you want a text widget.
"""
if tf is not None:
x, y = np.matmul(tf.c2s, [x, y, 1])
col = color_to_rgba(color)
while True:
# Text was hanging the application if too far away
if -8192 < x < 8192 and -8192 < y < 8192:
draw_list = imgui.get_window_draw_list()
draw_list.add_text(x, y, col, string)
yield
def image(tex_id, x, y, width, height, uv_a=(0, 0), uv_b=(1, 1), tf=None):
""" Draw an image with the given origin (x, y), width and height.
This is a raw drawing function. Use `concur.extra_widgets.image.image` instead
if you want an image widget. Note that OpenGL textures may be rendered incorrectly
if width or height isn't divisible by 4.
"""
p1, p2 = [x, y], [x + width, y + height]
if tf is not None:
p1, p2 = tf.transform(np.array([p1, p2]))
draw_list = imgui.get_window_draw_list()
while True:
draw_list.add_image(tex_id, tuple(p1), tuple(p2), uv_a, uv_b)
yield
def ellipse(mean, cov, sd, color, thickness=1, num_segments=16, tf=None):
"Ellipse defined by a mean, covariance matrix, and SD."
[e1, e2], vs = np.linalg.eig(cov)
assert e1 > 0 and e2 > 0, "cov must be positive-semidefinite"
v1, v2 = vs.T
t = np.linspace(0, np.pi * 2, num_segments, endpoint=False).reshape(-1, 1)
el = v1 * np.sin(t) * np.sqrt(e1) * sd + v2 * np.cos(t) * np.sqrt(e2) * sd
return polyline(el + mean, color, True, thickness, tf=tf)
def ellipses(means, covs, sd, color, thickness=1, num_segments=16, tf=None):
"""Multiple ellipses defined by a means, covariance matrices, and SD.
The call is very similar to `ellipse`, but `mean` and `cov` are vectorized: there is one more dimension (zeroth) for both.
For `n` ellipses, the shape of `mean` is `(n, 2)`, and the shape of `cov` is `(n, 2, 2)`.
"""
if len(means) == 0 and len(covs) == 0:
return nothing()
assert len(means) == len(covs)
assert len(means.shape) == 2 and means.shape[1] == 2
assert len(covs.shape) == 3 and covs.shape[1] == 2 and covs.shape[2] == 2
es, vs = np.linalg.eig(covs)
assert np.all(es > 0), "covariance matrices must be positive-semidefinite"
v1 = vs[..., 0].reshape(-1, 1, 2)
v2 = vs[..., 1].reshape(-1, 1, 2)
e1 = es[:, 0].reshape(-1, 1, 1)
e2 = es[:, 1].reshape(-1, 1, 1)
t = np.linspace(0, np.pi*2, num_segments, endpoint=False).reshape(-1, 1)
el = v1 * np.sin(t) * np.sqrt(e1) * sd + v2 * np.cos(t) * np.sqrt(e2) * sd
return polylines(el + means.reshape(-1, 1, 2), color, True, thickness, tf=tf)
def scatter(pts, color, marker, marker_size=10, thickness=1, tf=None):
"""Draw a scatter plot with given marker settings.
If multiple settings are desired (such as two distinct point colors), call
this function more than once with different parameters.
Some markers are more performant than others, depending on the amount of
generated geometry.
Args:
pts: NumPy array with shape `(n, 2)`, where `n` is the point count.
color: Color to draw the markers.
marker: Marker type. See the table below.
marker_size: Size of the markers in pixels.
thickness: Line width for non-filled markers
------
marker | description
------ | ---
`"."` | filled square
`"x"` | cross
`"+"` | plus sign
`"o"` | non-filled circle
`"s"` | non-filled square
`"|"` | vertical line
`"-"` | horizontal line
"""
if len(pts) == 0:
pts = pts.reshape(-1, 2)
assert len(pts.shape) == 2 and pts.shape[1] == 2
if tf is not None:
pts = tf.transform(pts)
if marker == '.':
r = marker_size / 2
polys = np.empty((len(pts), 4, 2))
polys[:, 0, :] = pts + [-r, -r]
polys[:, 1, :] = pts + [r, -r]
polys[:, 2, :] = pts + [r, r]
polys[:, 3, :] = pts + [-r, r]
return polygons(polys, color)
elif marker == '+':
r = marker_size / 2
polys = np.empty((len(pts) * 2, 2, 2))
polys[0::2, 0, :] = pts - [r, 0]
polys[0::2, 1, :] = pts + [r, 0]
polys[1::2, 0, :] = pts - [0, r]
polys[1::2, 1, :] = pts + [0, r]
return polylines(polys, color, False, thickness)
elif marker in ['X', 'x', '×']:
r = marker_size / np.sqrt(8)
polys = np.empty((len(pts) * 2, 2, 2))
polys[0::2, 0, :] = pts - [r, r]
polys[0::2, 1, :] = pts + [r, r]
polys[1::2, 0, :] = pts - [-r, r]
polys[1::2, 1, :] = pts + [-r, r]
return polylines(polys, color, False, thickness)
elif marker in ['O', 'o']:
r = marker_size / 2
n_verts = 7
t = np.linspace(0, np.pi * 2, n_verts, endpoint=False)
polys = np.empty((len(pts), n_verts, 2))
polys[..., 0] = np.sin(t) * r
polys[..., 1] = np.cos(t) * r
polys += pts.reshape(-1, 1, 2)
return polylines(polys, color, True, thickness)
elif marker in ['s', 'S']:
r = marker_size / 2
t = np.pi/4 + np.linspace(0, np.pi * 2, 4, endpoint=False)
polys = np.empty((len(pts), 4, 2))
polys[..., 0] = np.sin(t) * r
polys[..., 1] = np.cos(t) * r
polys += pts.reshape(-1, 1, 2)
return polylines(polys, color, True, thickness)
elif marker == '|':
r = marker_size / 2
polys = np.empty((len(pts), 2, 2))
polys[:, 0, :] = pts - [0, r]
polys[:, 1, :] = pts + [0, r]
return polylines(polys, color, False, thickness)
elif marker == '-':
r = marker_size / 2
polys = np.empty((len(pts), 2, 2))
polys[:, 0, :] = pts - [r, 0]
polys[:, 1, :] = pts + [r, 0]
return polylines(polys, color, False, thickness)
else:
raise ValueError('Invalid marker')
Functions
def circle(cx, cy, radius, color, thickness=1, num_segments=16, tf=None)
-
Circle specified by its center and radius.
Expand source code
def circle(cx, cy, radius, color, thickness=1, num_segments=16, tf=None): """ Circle specified by its center and radius. """ if tf is not None: assert np.allclose(np.abs(tf.c2s[0, 0]), np.abs(tf.c2s[1, 1])), \ "`tf` must be aspect ratio preserving to draw circles. Use `ellipse` instead, if it isn't the case." [cx, cy], radius = np.matmul(tf.c2s, [cx, cy, 1]), radius * tf.c2s[0, 0] draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_circle(cx, cy, radius, col, num_segments=num_segments, thickness=thickness) yield
def ellipse(mean, cov, sd, color, thickness=1, num_segments=16, tf=None)
-
Ellipse defined by a mean, covariance matrix, and SD.
Expand source code
def ellipse(mean, cov, sd, color, thickness=1, num_segments=16, tf=None): "Ellipse defined by a mean, covariance matrix, and SD." [e1, e2], vs = np.linalg.eig(cov) assert e1 > 0 and e2 > 0, "cov must be positive-semidefinite" v1, v2 = vs.T t = np.linspace(0, np.pi * 2, num_segments, endpoint=False).reshape(-1, 1) el = v1 * np.sin(t) * np.sqrt(e1) * sd + v2 * np.cos(t) * np.sqrt(e2) * sd return polyline(el + mean, color, True, thickness, tf=tf)
def ellipses(means, covs, sd, color, thickness=1, num_segments=16, tf=None)
-
Multiple ellipses defined by a means, covariance matrices, and SD.
The call is very similar to
ellipse()
, butmean
andcov
are vectorized: there is one more dimension (zeroth) for both. Forn
ellipses, the shape ofmean
is(n, 2)
, and the shape ofcov
is(n, 2, 2)
.Expand source code
def ellipses(means, covs, sd, color, thickness=1, num_segments=16, tf=None): """Multiple ellipses defined by a means, covariance matrices, and SD. The call is very similar to `ellipse`, but `mean` and `cov` are vectorized: there is one more dimension (zeroth) for both. For `n` ellipses, the shape of `mean` is `(n, 2)`, and the shape of `cov` is `(n, 2, 2)`. """ if len(means) == 0 and len(covs) == 0: return nothing() assert len(means) == len(covs) assert len(means.shape) == 2 and means.shape[1] == 2 assert len(covs.shape) == 3 and covs.shape[1] == 2 and covs.shape[2] == 2 es, vs = np.linalg.eig(covs) assert np.all(es > 0), "covariance matrices must be positive-semidefinite" v1 = vs[..., 0].reshape(-1, 1, 2) v2 = vs[..., 1].reshape(-1, 1, 2) e1 = es[:, 0].reshape(-1, 1, 1) e2 = es[:, 1].reshape(-1, 1, 1) t = np.linspace(0, np.pi*2, num_segments, endpoint=False).reshape(-1, 1) el = v1 * np.sin(t) * np.sqrt(e1) * sd + v2 * np.cos(t) * np.sqrt(e2) * sd return polylines(el + means.reshape(-1, 1, 2), color, True, thickness, tf=tf)
def image(tex_id, x, y, width, height, uv_a=(0, 0), uv_b=(1, 1), tf=None)
-
Draw an image with the given origin (x, y), width and height.
This is a raw drawing function. Use
image()
instead if you want an image widget. Note that OpenGL textures may be rendered incorrectly if width or height isn't divisible by 4.Expand source code
def image(tex_id, x, y, width, height, uv_a=(0, 0), uv_b=(1, 1), tf=None): """ Draw an image with the given origin (x, y), width and height. This is a raw drawing function. Use `concur.extra_widgets.image.image` instead if you want an image widget. Note that OpenGL textures may be rendered incorrectly if width or height isn't divisible by 4. """ p1, p2 = [x, y], [x + width, y + height] if tf is not None: p1, p2 = tf.transform(np.array([p1, p2])) draw_list = imgui.get_window_draw_list() while True: draw_list.add_image(tex_id, tuple(p1), tuple(p2), uv_a, uv_b) yield
def line(x0, y0, x1, y1, color, thickness=1, tf=None)
-
Line connecting two points.
Expand source code
def line(x0, y0, x1, y1, color, thickness=1, tf=None): """ Line connecting two points. """ if tf is not None: [x0, y0], [x1, y1] = tf.transform(np.array([[x0, y0], [x1, y1]])) draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_line(x0, y0, x1, y1, col, thickness) yield
def polygon(points, color, tf=None)
-
Filled polygon. Points must form a convex area.
points
is a list of (x, y) tuples, or a NumPy array of equivalent shape.Expand source code
def polygon(points, color, tf=None): """ Filled polygon. Points must form a convex area. `points` is a list of (x, y) tuples, or a NumPy array of equivalent shape. """ points = prepare_polyline_points(points, tf) draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_convex_poly_filled(points, col) yield
def polygons(points, color, tf=None)
-
Multiple filled polygons with the same length and color.
Calling this function is more efficient than calling
polygon()
multiple times, because all the data is given to the C++ back-end in one Python call, and because transformation is vectorized.points
is a NumPy array with shape(n, m, 2)
, wheren
is the number of polygons, andm
is the number of points in each polyline.Expand source code
def polygons(points, color, tf=None): """ Multiple filled polygons with the same length and color. Calling this function is more efficient than calling `polygon` multiple times, because all the data is given to the C++ back-end in one Python call, and because transformation is vectorized. `points` is a NumPy array with shape `(n, m, 2)`, where `n` is the number of polygons, and `m` is the number of points in each polyline. """ if tf is not None: points = tf.transform(points.reshape(-1, 2)).reshape(points.shape) draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_convex_polys_filled(points, col) yield
def polyline(points, color, closed=False, thickness=1, tf=None)
-
Polygonal line or a closed polygon.
points
is a list of (x, y) tuples, or a NumPy array of equivalent shape.Expand source code
def polyline(points, color, closed=False, thickness=1, tf=None): """ Polygonal line or a closed polygon. `points` is a list of (x, y) tuples, or a NumPy array of equivalent shape. """ points = prepare_polyline_points(points, tf) draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_polyline(points, col, closed, thickness) yield
def polylines(points, color, closed=False, thickness=1, tf=None)
-
Multiple polygonal lines with the same length and parameters.
Calling this function is more efficient than calling
polyline()
multiple times, because all the data is given to the C++ back-end in one Python call, and because transformation is vectorized.points
is a NumPy array with shape(n, m, 2)
, wheren
is the number of polylines, andm
is the number of points in each polyline.Expand source code
def polylines(points, color, closed=False, thickness=1, tf=None): """ Multiple polygonal lines with the same length and parameters. Calling this function is more efficient than calling `polyline` multiple times, because all the data is given to the C++ back-end in one Python call, and because transformation is vectorized. `points` is a NumPy array with shape `(n, m, 2)`, where `n` is the number of polylines, and `m` is the number of points in each polyline. """ if len(points) == 0: while True: yield if tf is not None: points = tf.transform(points.reshape(-1, 2)).reshape(points.shape) draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_polylines(points, col, closed, thickness) yield
def rect(x0, y0, x1, y1, color, thickness=1, rounding=0, tf=None)
-
Straight non-filled rectangle specified by its two corners.
Expand source code
def rect(x0, y0, x1, y1, color, thickness=1, rounding=0, tf=None): """ Straight non-filled rectangle specified by its two corners. """ if tf is not None: [x0, y0], [x1, y1] = tf.transform(np.array([[x0, y0], [x1, y1]])) # Avoid issues with disappearing lines on very large rectangles x0, x1 = np.clip([x0, x1], -8192, 8192) y0, y1 = np.clip([y0, y1], -8192, 8192) draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_rect(x0, y0, x1, y1, col, rounding, 15 if rounding else 0, thickness) yield
def rect_filled(x0, y0, x1, y1, color, rounding=0, tf=None)
-
Straight non-filled rectangle specified by its two corners.
Expand source code
def rect_filled(x0, y0, x1, y1, color, rounding=0, tf=None): """ Straight non-filled rectangle specified by its two corners. """ if tf is not None: [x0, y0], [x1, y1] = tf.transform(np.array([[x0, y0], [x1, y1]])) # Avoid issues with disappearing lines on very large rectangles x0, x1 = np.clip([x0, x1], -8192, 8192) y0, y1 = np.clip([y0, y1], -8192, 8192) draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_rect_filled(x0, y0, x1, y1, col, rounding, 15 if rounding else 0) yield
def rects(rects, color, thickness=1, tf=None)
-
Multiple straight non-filled rectangles specified by their two corners.
rects()
is a NumPy array of shape(n, 4)
, wheren
is the number of rectangles.Expand source code
def rects(rects, color, thickness=1, tf=None): """ Multiple straight non-filled rectangles specified by their two corners. `rects` is a NumPy array of shape `(n, 4)`, where `n` is the number of rectangles. """ if len(rects) == 0: while True: yield if tf is not None: rects = tf.transform(rects.reshape(-1, 2)).reshape(rects.shape) # Avoid issues with disappearing lines on very large rectangles rects = np.clip(rects, -8192, 8192) polys = np.empty((len(rects), 4, 2), dtype=rects.dtype) polys[:, 0] = rects[:, :2] polys[:, 1, 0] = rects[:, 0] polys[:, 1, 1] = rects[:, 3] polys[:, 2] = rects[:, 2:] polys[:, 3, 0] = rects[:, 2] polys[:, 3, 1] = rects[:, 1] draw_list = imgui.get_window_draw_list() col = color_to_rgba(color) while True: draw_list.add_polylines(polys, col, True, thickness) yield
def scatter(pts, color, marker, marker_size=10, thickness=1, tf=None)
-
Draw a scatter plot with given marker settings.
If multiple settings are desired (such as two distinct point colors), call this function more than once with different parameters.
Some markers are more performant than others, depending on the amount of generated geometry.
Args
pts
- NumPy array with shape
(n, 2)
, wheren
is the point count. color
- Color to draw the markers.
marker
- Marker type. See the table below.
marker_size
- Size of the markers in pixels.
thickness
- Line width for non-filled markers
marker description "."
filled square "x"
cross "+"
plus sign "o"
non-filled circle "s"
non-filled square "|"
vertical line "-"
horizontal line Expand source code
def scatter(pts, color, marker, marker_size=10, thickness=1, tf=None): """Draw a scatter plot with given marker settings. If multiple settings are desired (such as two distinct point colors), call this function more than once with different parameters. Some markers are more performant than others, depending on the amount of generated geometry. Args: pts: NumPy array with shape `(n, 2)`, where `n` is the point count. color: Color to draw the markers. marker: Marker type. See the table below. marker_size: Size of the markers in pixels. thickness: Line width for non-filled markers ------ marker | description ------ | --- `"."` | filled square `"x"` | cross `"+"` | plus sign `"o"` | non-filled circle `"s"` | non-filled square `"|"` | vertical line `"-"` | horizontal line """ if len(pts) == 0: pts = pts.reshape(-1, 2) assert len(pts.shape) == 2 and pts.shape[1] == 2 if tf is not None: pts = tf.transform(pts) if marker == '.': r = marker_size / 2 polys = np.empty((len(pts), 4, 2)) polys[:, 0, :] = pts + [-r, -r] polys[:, 1, :] = pts + [r, -r] polys[:, 2, :] = pts + [r, r] polys[:, 3, :] = pts + [-r, r] return polygons(polys, color) elif marker == '+': r = marker_size / 2 polys = np.empty((len(pts) * 2, 2, 2)) polys[0::2, 0, :] = pts - [r, 0] polys[0::2, 1, :] = pts + [r, 0] polys[1::2, 0, :] = pts - [0, r] polys[1::2, 1, :] = pts + [0, r] return polylines(polys, color, False, thickness) elif marker in ['X', 'x', '×']: r = marker_size / np.sqrt(8) polys = np.empty((len(pts) * 2, 2, 2)) polys[0::2, 0, :] = pts - [r, r] polys[0::2, 1, :] = pts + [r, r] polys[1::2, 0, :] = pts - [-r, r] polys[1::2, 1, :] = pts + [-r, r] return polylines(polys, color, False, thickness) elif marker in ['O', 'o']: r = marker_size / 2 n_verts = 7 t = np.linspace(0, np.pi * 2, n_verts, endpoint=False) polys = np.empty((len(pts), n_verts, 2)) polys[..., 0] = np.sin(t) * r polys[..., 1] = np.cos(t) * r polys += pts.reshape(-1, 1, 2) return polylines(polys, color, True, thickness) elif marker in ['s', 'S']: r = marker_size / 2 t = np.pi/4 + np.linspace(0, np.pi * 2, 4, endpoint=False) polys = np.empty((len(pts), 4, 2)) polys[..., 0] = np.sin(t) * r polys[..., 1] = np.cos(t) * r polys += pts.reshape(-1, 1, 2) return polylines(polys, color, True, thickness) elif marker == '|': r = marker_size / 2 polys = np.empty((len(pts), 2, 2)) polys[:, 0, :] = pts - [0, r] polys[:, 1, :] = pts + [0, r] return polylines(polys, color, False, thickness) elif marker == '-': r = marker_size / 2 polys = np.empty((len(pts), 2, 2)) polys[:, 0, :] = pts - [r, 0] polys[:, 1, :] = pts + [r, 0] return polylines(polys, color, False, thickness) else: raise ValueError('Invalid marker')
def text(string, x, y, color, tf=None)
-
Text, using the default font and font size.
This is a raw drawing function. Use
text()
instead if you want a text widget.Expand source code
def text(string, x, y, color, tf=None): """ Text, using the default font and font size. This is a raw drawing function. Use `concur.widgets.text` instead if you want a text widget. """ if tf is not None: x, y = np.matmul(tf.c2s, [x, y, 1]) col = color_to_rgba(color) while True: # Text was hanging the application if too far away if -8192 < x < 8192 and -8192 < y < 8192: draw_list = imgui.get_window_draw_list() draw_list.add_text(x, y, col, string) yield