diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aeeb6787e5a..db5a93edf20e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added `compas_plotters.artists.NetworkArtist.draw_nodelabels`. +* Added `compas_plotters.artists.NetworkArtist.draw_edgelabels`. +* Added `compas_plotters.Plotter.fontsize`. + ### Changed +* Fixed bug in inheritance of `compas_plotters.artists.NetworkArtist`. +* Changed `compas_plotters.artists.MeshArtist.draw_edges` to ignore edge direction for assignment of edge colors and widths. +* Changed `compas_plotters.artists.MeshArtist.draw_vertexlabels` to use `compas_plotters.Plotter.fontsize`. +* Changed `compas_plotters.artists.MeshArtist.draw_edgelabels` to use `compas_plotters.Plotter.fontsize`. +* Changed `compas_plotters.artists.MeshArtist.draw_facelabels` to use `compas_plotters.Plotter.fontsize`. + ### Removed diff --git a/src/compas/artists/networkartist.py b/src/compas/artists/networkartist.py index 3ed343e9d055..bde62d5d30ec 100644 --- a/src/compas/artists/networkartist.py +++ b/src/compas/artists/networkartist.py @@ -60,7 +60,7 @@ class NetworkArtist(Artist): default_edgewidth = 1.0 def __init__(self, network, **kwargs): - super(NetworkArtist, self).__init__(**kwargs) + super(NetworkArtist, self).__init__() self._network = None self._nodes = None @@ -133,7 +133,7 @@ def node_color(self, node_color): @property def node_size(self): if not self._node_size: - self._node_size = {node: self.default_nodesize for node in self.network.vertices()} + self._node_size = {node: self.default_nodesize for node in self.network.nodes()} return self._node_size @node_size.setter @@ -141,7 +141,7 @@ def node_size(self, nodesize): if isinstance(nodesize, dict): self._node_size = nodesize elif isinstance(nodesize, (int, float)): - self._node_size = {node: nodesize for node in self.network.vertices()} + self._node_size = {node: nodesize for node in self.network.nodes()} @property def edge_color(self): diff --git a/src/compas/artists/volmeshartist.py b/src/compas/artists/volmeshartist.py index 0dd4d26acefe..ef74a8cc654a 100644 --- a/src/compas/artists/volmeshartist.py +++ b/src/compas/artists/volmeshartist.py @@ -83,7 +83,7 @@ class VolMeshArtist(Artist): default_cellcolor = (1, 0, 0) def __init__(self, volmesh, **kwargs): - super(VolMeshArtist, self).__init__(**kwargs) + super(VolMeshArtist, self).__init__() self._volmesh = None self._vertices = None self._edges = None diff --git a/src/compas_plotters/artists/meshartist.py b/src/compas_plotters/artists/meshartist.py index 74639b62d1bb..4eff0b06f544 100644 --- a/src/compas_plotters/artists/meshartist.py +++ b/src/compas_plotters/artists/meshartist.py @@ -316,9 +316,10 @@ def draw_edges(self, colors = [] widths = [] for edge in self.edges: + u, v = edge lines.append([self.vertex_xyz[edge[0]][:2], self.vertex_xyz[edge[1]][:2]]) - colors.append(self.edge_color.get(edge, self.default_edgecolor)) - widths.append(self.edge_width.get(edge, self.default_edgewidth)) + colors.append(self.edge_color.get(edge, self.edge_color.get((v, u), self.default_edgecolor))) + widths.append(self.edge_width.get(edge, self.edge_width.get((v, u), self.default_edgewidth))) collection = LineCollection( lines, @@ -468,7 +469,7 @@ def draw_vertexlabels(self, text: Optional[Dict[int, str]] = None) -> None: artist = self.plotter.axes.text( x, y, f'{text}', - fontsize=12, + fontsize=self.plotter.fontsize, family='monospace', ha='center', va='center', zorder=10000, @@ -499,7 +500,8 @@ def draw_edgelabels(self, text: Optional[Dict[int, str]] = None) -> None: labels = [] for edge in self.edges: - text = self.edge_text.get(edge, None) + u, v = edge + text = self.edge_text.get(edge, self.edge_text.get((v, u), None)) if text is None: continue @@ -510,7 +512,7 @@ def draw_edgelabels(self, text: Optional[Dict[int, str]] = None) -> None: artist = self.plotter.axes.text( x, y, f'{text}', - fontsize=12, + fontsize=self.plotter.fontsize, family='monospace', ha='center', va='center', zorder=10000, @@ -550,7 +552,7 @@ def draw_facelabels(self, text: Optional[Dict[int, str]] = None) -> None: artist = self.plotter.axes.text( x, y, f'{text}', - fontsize=12, + fontsize=self.plotter.fontsize, family='monospace', ha='center', va='center', zorder=10000, diff --git a/src/compas_plotters/artists/networkartist.py b/src/compas_plotters/artists/networkartist.py index 3d29e2408c92..7a7a50d45e61 100644 --- a/src/compas_plotters/artists/networkartist.py +++ b/src/compas_plotters/artists/networkartist.py @@ -9,12 +9,14 @@ from matplotlib.patches import Circle from compas.datastructures import Network +from compas.artists import NetworkArtist +from compas.utilities.colors import is_color_light from .artist import PlotterArtist Color = Tuple[float, float, float] -class NetworkArtist(PlotterArtist): +class NetworkArtist(PlotterArtist, NetworkArtist): """Artist for COMPAS network data structures. Parameters @@ -57,18 +59,19 @@ def __init__(self, network: Network, nodes: Optional[List[int]] = None, edges: Optional[List[int]] = None, - nodecolor: Color = (1, 1, 1), - edgecolor: Color = (0, 0, 0), + nodecolor: Color = (1.0, 1.0, 1.0), + edgecolor: Color = (0.0, 0.0, 0.0), edgewidth: float = 1.0, show_nodes: bool = True, show_edges: bool = True, nodesize: int = 5, sizepolicy: Literal['relative', 'absolute'] = 'relative', - zorder: int = 2000, + zorder: int = 1000, **kwargs): super().__init__(network=network, **kwargs) + self.sizepolicy = sizepolicy self.nodes = nodes self.edges = edges self.node_color = nodecolor @@ -77,16 +80,15 @@ def __init__(self, self.edge_width = edgewidth self.show_nodes = show_nodes self.show_edges = show_edges - self.sizepolicy = sizepolicy self.zorder = zorder @property def zorder_edges(self): - return self.zorder + return self.zorder + 10 @property def zorder_nodes(self): - return self.zorder + 10 + return self.zorder + 20 @property def item(self): @@ -101,6 +103,22 @@ def item(self, item: Network): def data(self) -> List[List[float]]: return self.network.nodes_attributes('xy') + @property + def node_size(self): + if not self._node_size: + factor = self.plotter.dpi if self.sizepolicy == 'absolute' else self.network.number_of_nodes() + size = self.default_nodesize / factor + self._node_size = {node: size for node in self.network.nodes()} + return self._node_size + + @node_size.setter + def node_size(self, nodesize): + factor = self.plotter.dpi if self.sizepolicy == 'absolute' else self.network.number_of_nodes() + if isinstance(nodesize, dict): + self.node_size.update({node: size / factor for node, size in nodesize.items()}) + elif isinstance(nodesize, (int, float)): + self._node_size = {node: nodesize / factor for node in self.network.nodes()} + # ============================================================================== # clear and draw # ============================================================================== @@ -218,3 +236,91 @@ def draw_edges(self, ) self.plotter.axes.add_collection(collection) self._edgecollection = collection + + def draw_nodelabels(self, text: Optional[Dict[int, str]] = None) -> None: + """Draw a selection of node labels. + + Parameters + ---------- + text : dict of int to str, optional + A node-label map. + If not text dict is provided, the node identifiers are drawn. + + Returns + ------- + None + """ + if self._nodelabelcollection: + for artist in self._nodelabelcollection: + artist.remove() + + if text: + self.node_text = text + + labels = [] + for node in self.nodes: + bgcolor = self.node_color.get(node, self.default_nodecolor) + color = (0, 0, 0) if is_color_light(bgcolor) else (1, 1, 1) + + text = self.node_text.get(node, None) + print(text) + if text is None: + continue + + x, y = self.node_xyz[node][:2] + artist = self.plotter.axes.text( + x, y, + f'{text}', + fontsize=self.plotter.fontsize, + family='monospace', + ha='center', va='center', + zorder=10000, + color=color + ) + labels.append(artist) + + self._nodelabelcollection = labels + + def draw_edgelabels(self, text: Optional[Dict[int, str]] = None) -> None: + """Draw a selection of edge labels. + + Parameters + ---------- + text : dict of tuple of int to str + An edge-label map. + + Returns + ------- + None + """ + if self._edgelabelcollection: + for artist in self._edgelabelcollection: + artist.remove() + + if text: + self.edge_text = text + + labels = [] + for edge in self.edges: + u, v = edge + text = self.edge_text.get(edge, self.edge_text.get((v, u), None)) + if text is None: + continue + + x0, y0 = self.node_xyz[edge[0]][:2] + x1, y1 = self.node_xyz[edge[1]][:2] + x = 0.5 * (x0 + x1) + y = 0.5 * (y0 + y1) + + artist = self.plotter.axes.text( + x, y, f'{text}', + fontsize=self.plotter.fontsize, + family='monospace', + ha='center', va='center', + zorder=10000, + color=(0, 0, 0), + bbox=dict(boxstyle='round, pad=0.3', facecolor=(1, 1, 1), edgecolor=None, linewidth=0) + ) + labels.append(artist) + + self._edgelabelcollection = labels diff --git a/src/compas_plotters/plotter.py b/src/compas_plotters/plotter.py index d458e07fbb80..b98e9c1b2d8b 100644 --- a/src/compas_plotters/plotter.py +++ b/src/compas_plotters/plotter.py @@ -33,6 +33,8 @@ class Plotter(metaclass=Singleton): """ + fontsize = 12 + def __init__(self, view: Tuple[Tuple[float, float], Tuple[float, float]] = ((-8.0, 16.0), (-5.0, 10.0)), figsize: Tuple[float, float] = (8.0, 5.0),