Skip to content

Commit dd2d5d5

Browse files
committed
Merge branch 'main' into data-tutorial
2 parents 01e5fe6 + 9b07e30 commit dd2d5d5

37 files changed

+1915
-964
lines changed

CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added pluggable function `trimesh_slice` in `compas_rhino`.
1313
* Added equality comparison for pointclouds.
1414
* Added `compas.data.is_sequence_of_uint`.
15+
* Added general plotter for geometry objects and data structures based on the artist registration mechanism.
1516

1617
### Changed
1718

1819
* `compas.robots.Axis` is now normalized upon initialization.
1920
* Fixed a bug in `compas.numerical.dr_numpy` when using numpy array as inputs.
2021
* Allowed for varying repository file structures in `compas.robots.GithubPackageMeshLoader`.
2122
* Fixed data schema of `compas.geometry.Polyline`, `compas.geometry.Polygon`, `compas.geometry.Pointcloud`.
22-
23-
### Fixed
24-
2523
* Fixed `Configuration.from_data` to be backward-compatible with JSON data generated before `compas 1.3.0`.
24+
* Changed `compas_rhino.drawing.draw_breps` to assume provided polygon is closed and automatically add missing corner to polycurve constructor.
2625

2726
### Removed
2827

126 KB
Loading
197 KB
Loading
127 KB
Loading
30.4 KB
Loading
109 KB
Loading
61.9 KB
Loading

docs/tutorial.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ Tutorial
1414
tutorial/numericaldata
1515
tutorial/rpc
1616
tutorial/geomaps
17+
tutorial/plotters

docs/tutorial/plotters.rst

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
**************
2+
Plotters
3+
**************
4+
5+
The COMPAS plotters (:mod:`compas_plotters`) provide an easy-to-use inteface for basic 2D visualisation
6+
of COMPAS objects based on matplotlib.
7+
8+
The package contains four types of plotters:
9+
:class:`compas_plotters.GeometryPlotter`,
10+
:class:`compas_plotters.NetworkPlotter`,
11+
:class:`compas_plotters.MeshPlotter`, and ... :class:`compas_plotters.Plotter`.
12+
The first three are deprecated in favour of :class:`compas_plotters.Plotter`, which is therefore the only one that will be described in this tutorial.
13+
14+
15+
Example
16+
=======
17+
18+
.. figure:: /_images/tutorial/plotters_example.png
19+
:figclass: figure
20+
:class: figure-img img-fluid
21+
22+
.. code-block:: python
23+
24+
import random
25+
import compas
26+
from compas.geometry import Circle, Polyline
27+
from compas.datastructures import Network
28+
from compas_plotters import Plotter
29+
30+
network = Network.from_obj(compas.get('grid_irregular.obj'))
31+
32+
start, end = random.sample(network.leaves(), 2)
33+
path = network.shortest_path(start, end)
34+
points = network.nodes_attributes('xy', keys=path)
35+
36+
polyline = Polyline(points)
37+
circles = [Circle([point, [0, 0, 1]], 0.1 * index) for index, point in enumerate(points)]
38+
39+
plotter = Plotter()
40+
41+
plotter.add(network)
42+
plotter.add(polyline, linewidth=3)
43+
plotter.add_from_list(circles, facecolor=(0, 1, 1))
44+
45+
plotter.zoom_extents()
46+
plotter.show()
47+
48+
49+
Basic Usage
50+
===========
51+
52+
Using :class:`compas_plotters.Plotter` is very simple.
53+
54+
1. Create a plotter instance.
55+
2. Add Objects.
56+
3. Optionally zoom the extents of all objects that were added.
57+
4. Show the plot.
58+
59+
.. code-block:: python
60+
61+
plotter = Plotter()
62+
63+
# add objects
64+
65+
plotter.zoom_extents()
66+
plotter.show()
67+
68+
COMPAS geometry objects and data structures can be added using :meth:`compas_plotters.Plotter.add`.
69+
By adding an object, a corresponding "artist" is created automatically in the background,
70+
and the plotter will use the artist to visualize the object.
71+
72+
The artists provide many configuration options to modify the display styles of the objects.
73+
The :meth:`compas_plotters.Plotter.add` method accepts additional keyword arguments corresponding to those configuration options.
74+
See the API reference of the individual artists for the available options per object type.
75+
76+
.. code-block:: python
77+
78+
point = Point(0, 0, 0)
79+
80+
plotter.add(point, size=10, facecolor=(1.0, 0.7, 0.7), edgecolor=(1.0, 0, 0))
81+
82+
Alternatively, multiple objects of the same type can also be added using :meth:`compas_plotters.Plotter.add_from_list`.
83+
In this case all configurations options will be applied uniformly to all objects in the list.
84+
85+
.. code-block:: python
86+
87+
cloud = Pointcloud.from_bounds(10, 10, 0, 100)
88+
89+
plotter.add_from_list(cloud.points, size=1, facecolor=(1.0, 0.7, 0.7), edgecolor=(1.0, 0, 0))
90+
91+
92+
Geometry Objects
93+
================
94+
95+
Most of the geometry primitives are supported
96+
and can be added to a plotter instance as described above:
97+
98+
* :class:`compas.geometry.Point`
99+
* :class:`compas.geometry.Vector`
100+
* :class:`compas.geometry.Line`
101+
* :class:`compas.geometry.Circle`
102+
* :class:`compas.geometry.Ellipse`
103+
* :class:`compas.geometry.Polyline`
104+
* :class:`compas.geometry.Polygon`
105+
106+
Bezier curves and pointclouds are currently not available yet, but will be added as well.
107+
Note that in all cases, the ``z`` coordinates of the objects are simply ignored, and only a 2D representation is depicted.
108+
109+
.. code-block:: python
110+
111+
plotter.add(point)
112+
plotter.add(vector)
113+
plotter.add(line)
114+
plotter.add(circle)
115+
plotter.add(ellipse)
116+
plotter.add(polyline)
117+
plotter.add(polygon)
118+
119+
120+
Data Structures
121+
===============
122+
123+
Of the three types of data structures, only network and mesh are supported.
124+
Also in this case, the ``z`` coordinates of the geometry is ignored, and only a 2D representation is depicted.
125+
126+
.. code-block:: python
127+
128+
plotter.add(point)
129+
plotter.add(vector)
130+
131+
132+
Visualisation Options
133+
=====================
134+
135+
Line and Polyline
136+
-----------------
137+
138+
.. rst-class:: table table-bordered
139+
140+
.. list-table::
141+
:widths: auto
142+
:header-rows: 1
143+
144+
* - Name
145+
- Value
146+
- Default
147+
* - ``linewidth``
148+
- :obj:`float`
149+
- ``1.0``
150+
* - ``linestyle``
151+
- ``{'solid', 'dotted', 'dashed', 'dashdot'}``
152+
- ``'solid'``
153+
* - ``color``
154+
- :obj:`tuple`
155+
- ``(0.0, 0.0, 0.0)``
156+
* - ``draw_points``
157+
- :obj:`bool`
158+
- ``False``
159+
160+
.. code-block:: python
161+
162+
pointcloud = Pointcloud.from_bounds(8, 5, 0, 10)
163+
164+
for a, b in grouper(pointcloud, 2):
165+
line = Line(a, b)
166+
plotter.add(line,
167+
linewidth=2.0,
168+
linestyle=random.choice(['dotted', 'dashed', 'solid']),
169+
color=i_to_rgb(random.random(), normalize=True),
170+
draw_points=True)
171+
172+
.. figure:: /_images/tutorial/plotters_line-options.png
173+
:figclass: figure
174+
:class: figure-img img-fluid
175+
176+
177+
Circle, Ellipse, Polygon
178+
------------------------
179+
180+
.. rst-class:: table table-bordered
181+
182+
.. list-table::
183+
:widths: auto
184+
:header-rows: 1
185+
186+
* - Name
187+
- Value
188+
- Default
189+
* - ``linewidth``
190+
- :obj:`float`
191+
- ``1.0``
192+
* - ``linestyle``
193+
- ``{'solid', 'dotted', 'dashed', 'dashdot'}``
194+
- ``'solid'``
195+
* - ``facecolor``
196+
- :obj:`tuple`
197+
- ``(1.0, 1.0, 1.0)``
198+
* - ``edgecolor``
199+
- :obj:`tuple`
200+
- ``(0.0, 0.0, 0.0)``
201+
* - ``alpha``
202+
- :obj:`float`
203+
- ``1.0``
204+
* - ``fill``
205+
- :obj:`bool`
206+
- ``True``
207+
208+
.. code-block:: python
209+
210+
poly1 = Polygon.from_sides_and_radius_xy(5, 1.0)
211+
poly2 = Polygon.from_sides_and_radius_xy(5, 1.0).transformed(Translation.from_vector([0.5, -0.25, 0]))
212+
poly3 = Polygon.from_sides_and_radius_xy(5, 1.0).transformed(Translation.from_vector([0.75, +0.25, 0]))
213+
214+
plotter.add(poly1, linewidth=3.0, facecolor=(0.8, 1.0, 0.8), edgecolor=(0.0, 1.0, 0.0))
215+
plotter.add(poly2, linestyle='dashed', facecolor=(1.0, 0.8, 0.8), edgecolor=(1.0, 0.0, 0.0))
216+
plotter.add(poly3, alpha=0.5)
217+
218+
.. figure:: /_images/tutorial/plotters_polygon-options.png
219+
:figclass: figure
220+
:class: figure-img img-fluid
221+
222+
223+
Points
224+
------
225+
226+
.. rst-class:: table table-bordered
227+
228+
.. list-table::
229+
:widths: auto
230+
:header-rows: 1
231+
232+
* - Name
233+
- Value
234+
- Default
235+
* - ``size``
236+
- :obj:`int`
237+
- ``5``
238+
* - ``facecolor``
239+
- :obj:`tuple`
240+
- ``(1.0, 1.0, 1.0)``
241+
* - ``edgecolor``
242+
- :obj:`tuple`
243+
- ``(0.0, 0.0, 0.0)``
244+
245+
.. code-block:: python
246+
247+
pointcloud = Pointcloud.from_bounds(8, 5, 0, 10)
248+
249+
for point in pointcloud:
250+
plotter.add(point, size=random.randint(1, 10), edgecolor=i_to_rgb(random.random(), normalize=True))
251+
252+
.. figure:: /_images/tutorial/plotters_point-options.png
253+
:figclass: figure
254+
:class: figure-img img-fluid
255+
256+
257+
Vectors
258+
-------
259+
260+
.. rst-class:: table table-bordered
261+
262+
.. list-table::
263+
:widths: auto
264+
:header-rows: 1
265+
266+
* - Name
267+
- Value
268+
- Default
269+
* - ``point``
270+
- :class:`compas.geometry.Point`
271+
- ``None``
272+
* - ``draw_point``
273+
- :obj:`bool`
274+
- ``False``
275+
* - ``color``
276+
- :obj:`tuple`
277+
- ``(0.0, 0.0, 0.0)``
278+
279+
.. code-block:: python
280+
281+
pointcloud = Pointcloud.from_bounds(8, 5, 0, 10)
282+
283+
for index, (a, b) in enumerate(pairwise(pointcloud)):
284+
vector = b - a
285+
vector.unitize()
286+
plotter.add(vector, point=a, draw_point=True, color=i_to_red(max(index / 10, 0.1), normalize=True))
287+
288+
.. figure:: /_images/tutorial/plotters_vector-options.png
289+
:figclass: figure
290+
:class: figure-img img-fluid
291+
292+
293+
Dynamic Plots
294+
=============
295+
296+
Dynamic plots, or animations, can be made with the "on" decorator :meth:`compas_plotters.Plotter.on`.
297+
Simply add the decorator to a callback functions that updates the geometry in the plot at a specified interval.
298+
299+
.. code-block:: python
300+
301+
@plotter.on(interval=0.1, frames=50)
302+
def move(frame):
303+
for a, b in pairwise(pointcloud):
304+
vector = b - a
305+
a.transform(Translation.from_vector(vector * 0.1))
306+
307+
For example, the following will update the locations of the points of a pointcloud
308+
for 50 frames and with an interval of 0.1 seconds between the frames.
309+
310+
.. code-block:: python
311+
312+
from compas.geometry import Pointcloud, Translation
313+
from compas.utilities import i_to_red, pairwise
314+
315+
from compas_plotters import Plotter
316+
317+
plotter = Plotter(figsize=(8, 5))
318+
319+
pointcloud = Pointcloud.from_bounds(8, 5, 0, 10)
320+
321+
for index, (a, b) in enumerate(pairwise(pointcloud)):
322+
artist = plotter.add(a, edgecolor=i_to_red(max(index / 10, 0.1), normalize=True))
323+
324+
plotter.add(b, size=10, edgecolor=(1, 0, 0))
325+
plotter.zoom_extents()
326+
plotter.pause(1.0)
327+
328+
@plotter.on(interval=0.1, frames=50)
329+
def move(frame):
330+
for a, b in pairwise(pointcloud):
331+
vector = b - a
332+
a.transform(Translation.from_vector(vector * 0.1))
333+
334+
If you want to keep the plot alive at the end of the animation, add a call to ``show``.
335+
336+
.. code-block:: python
337+
338+
plotter.show()
339+
340+
To save the animation to an animated gif, set the ``record`` flag to ``True``, and add a ``recording`` path.
341+
342+
.. code-block:: python
343+
344+
@plotter.on(interval=0.1, frames=50, record=True, recording='docs/_images/tutorial/plotters_dynamic.gif')
345+
def move(frame):
346+
for a, b in pairwise(pointcloud):
347+
vector = b - a
348+
a.transform(Translation.from_vector(vector * 0.1))
349+
350+
.. figure:: /_images/tutorial/plotters_dynamic.gif
351+
:figclass: figure
352+
:class: figure-img img-fluid
353+
354+
355+
Interactive Plots
356+
=================
357+
358+
*Coming soon*.

0 commit comments

Comments
 (0)