Styling: Colours and Line Widths¶
This notebooks shows different ways and patterns how to style renderings in pleat.
import numpy as np
from pleat import (
colorization,
conway,
example_graphs,
example_tilesets,
)
from pleat.rendering import multi_show
Render-time auto-colouring¶
The simplest path: ask G.show() to colour at render time without ever writing to face.attributes. Pass a preset name, a Classifier, or any callable returning a hashable.
Presets:
- faces:
"congruency","order"(= side count) - edges:
"length","orientation"(angle mod π) - vertices:
"order"(= degree)
The default colormap is tab10 (great for ≤ 10 classes); past that, the palette switches to evenly-spaced hsv so every class still gets a distinct hue. Override per-call with face_cmap=, edge_cmap=, vertex_cmap= — accepting any matplotlib colormap name, a Colormap instance, or a list of colours. Qualitative cmaps (tab10, Set2, ...) are used directly; continuous cmaps (viridis, plasma, ...) are sampled across their full [0, 1] range so categorical inputs span the whole gradient. Project-wide defaults live in colorization.DEFAULT_FACE_CMAP etc.
def show_coloring_variants(G):
multi_show(
[G, G, G, G],
titles=[
'by face order (tab10)',
'by face congruence (tab10)',
'by face congruence (viridis)',
'by edge orientation',
],
render_faces=True, face_inset=0.05, render_vertices=False,
per_subplot_kwargs=[
dict(face_color_by='order', render_edges=False),
dict(face_color_by='congruency', render_edges=False),
dict(face_color_by='congruency', face_cmap='viridis', render_edges=False),
dict(edge_color_by='orientation', render_faces=False, line_width="300%"),
],
ncols=2,
)
show_coloring_variants(example_graphs.from_tiles(example_tilesets.t_4_6_12(), rings=2))
show_coloring_variants(example_graphs.from_tiles(example_tilesets.t_4_6_12(), rings=2).gyro())
Baking colors into the graph¶
When you do want the assignment baked into the graph — for serialisation, propagation through Conway operators (see the pre_conway section below), because you'll render the same coloured graph many times, or because the graph will travel through transformations that change geometry — use colorization.colorize(). It writes a hashable class id to face['color_key'], and the renderer's palette resolution maps that id to a colour just like the face_color_by= path above.
Below: a hyperbolic {7, 3} expand tiling, coloured by face congruence while still hyperbolic (where lengths and angles match), then projected to the Poincaré disk for rendering. The color_key attributes survive the projection.
from pleat.classifiers import congruency_classifier
# A hyperbolic {7, 3} *expand* tiling — heptagons, squares, and triangles.
G = example_graphs.from_tiles(example_tilesets.curved_expand(7, 3), rings=5)
print('geometry:', G.geometry.__name__)
# Bake congruency colours while the lengths and angles are still hyperbolic.
colorization.colorize(G, congruency_classifier())
# Project to the Poincaré disk — face['color_key'] travels with the graph.
G.convert_to_euclidean()
G.normalize_edge_lengths()
G.show(render_faces=True, render_edges=False, render_vertices=False)
geometry: PoincareDiskModel
Hand-assigned face colours¶
For one-off highlights, just write to face['color_key'] directly. Pick faces with any predicate; below we paint everything within a small radius of the origin orange.
G = example_graphs.from_tiles(example_tilesets.platonic(6), rings=3)
G.recompute_lengths_and_angles()
for f in G.faces:
if np.linalg.norm(f.midpoint()) < 3:
f['color_key'] = (1.0, 0.6, 0.0, 0.9) # opaque orange
G.show(render_faces=True, face_inset=0.05, render_vertices=False)
Hand-assigned edge colours and widths¶
Per-edge 'color_key' and 'line_width' work the same way. Below we make the boundary halfedges of one chosen face thick and red. Note that we set both directions of every edge.
G = example_graphs.from_tiles(example_tilesets.platonic(4), rings=3)
G.recompute_lengths_and_angles()
central = G.central_face()
for h in central.halfedge_iter():
h['color_key'] = h.rev['color_key'] = (0.85, 0.10, 0.10, 1.0)
h['line_width'] = h.rev['line_width'] = 0.2
G.show(face_inset=0.05, render_vertices=False)
Inheriting colours via pre_conway¶
Every Conway operator stores some back-pointers on its outputs: some of the new vertex / face / halfedges in the result graph carry obj['pre_conway'] → the corresponding element of the input graph. We can use that to propagate any per-face attribute through an operator, for example for coloring
from pleat.half import Face
G = example_graphs.from_tiles(example_tilesets.platonic(4), rings=1)
central_face = G.central_face()
central_face['color_key'] = (1.0, 0.5, 0.1, 0.9)
for v in central_face.vertex_iter():
v['color_key'] = np.random.rand(3)
D = conway.chamfer_graph()(G.copy(), delete_on_border=False)
D.recompute_lengths_and_angles()
for obj in D.vertices.union(D.faces).union(D.halfedges):
src = obj.attributes.get('pre_conway')
if isinstance(src, Face) and 'color_key' in src.attributes:
obj['color_key'] = src['color_key']
multi_show([G, D],
titles=['original', 'dual (colors inherited from original)'],
render_faces=True, render_vertices=True, face_inset=0,
vertex_radius=0.1)