--- /dev/null
+#!/usr/bin/env python
+#
+# A Diagram abstraction based on Cairo
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import cairo
+from math import *
+
+
+class Diagram(object):
+
+ def __init__(self, width, height, background=[1, 1, 1]):
+ self.width = width
+ self.height = height
+ self.background = background
+
+ # TODO: use a RecordingSurface
+ self.surface = cairo.SVGSurface(None, width, height)
+ self.cr = cr = cairo.Context(self.surface)
+
+ # convert to left-bottom-origin cartesian coordinates
+ cr.translate(0, self.height)
+ cr.scale(1, -1)
+
+ cr.select_font_face("Georgia", cairo.FONT_SLANT_NORMAL,
+ cairo.FONT_WEIGHT_NORMAL)
+ cr.set_font_size(20)
+
+ # Adjust the font matrix to left-bottom origin
+ M = cr.get_font_matrix()
+ M.scale(1, -1)
+ cr.set_font_matrix(M)
+
+ def clear(self):
+ cr = self.cr
+
+ r, g, b, a = self.color_to_rgba(self.background)
+ cr.set_source_rgba(r, g, b, a)
+ cr.paint()
+
+ def save_svg(self, filename):
+ surface = cairo.SVGSurface(filename + '.svg', self.width, self.height)
+ cr = cairo.Context(surface)
+ cr.set_source_surface(self.surface, 0, 0)
+ cr.paint()
+
+ def save_png(self, filename):
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
+ cr = cairo.Context(surface)
+ cr.set_source_surface(self.surface, 0, 0)
+ cr.paint()
+ surface.write_to_png(filename + '.png')
+
+ def show(self):
+ import Image
+ import StringIO
+ f = StringIO.StringIO()
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
+ cr = cairo.Context(surface)
+ cr.set_source_surface(self.surface, 0, 0)
+ cr.paint()
+ surface.write_to_png(f)
+ f.seek(0)
+ im = Image.open(f)
+ im.show()
+
+ def get_regular_polygon(self, x, y, sides, r, theta0=0.0):
+ theta = 2 * pi / sides
+
+ verts = []
+ for i in range(0, sides):
+ px = x + r * sin(theta0 + i * theta)
+ py = y + r * cos(theta0 + i * theta)
+ verts.append((px, py))
+
+ return verts
+
+ def color_to_rgba(self, color):
+ if len(color) == 3:
+ return color[0], color[1], color[2], 1.0
+ elif len(color) == 4:
+ return color[0], color[1], color[2], color[3]
+ else:
+ return None
+
+ def draw_polygon(self, verts, stroke_color=[0, 0, 0], fill_color=None):
+ cr = self.cr
+
+ if fill_color:
+ v = verts[0]
+ cr.move_to(v[0], v[1])
+ for v in verts[1:]:
+ cr.line_to(v[0], v[1])
+ cr.close_path()
+
+ r, g, b, a = self.color_to_rgba(fill_color)
+ cr.set_source_rgba(r, g, b, a)
+ cr.fill()
+
+ n = len(verts)
+ for i in range(0, n):
+ v1 = verts[i]
+ v2 = verts[(i + 1) % n]
+ cr.move_to(v1[0], v1[1])
+ cr.line_to(v2[0], v2[1])
+
+ r, g, b, a = self.color_to_rgba(stroke_color)
+ cr.set_source_rgba(r, g, b, a)
+ cr.stroke()
+
+ def draw_star(self, cx, cy, verts, stroke_color=[0, 0, 0]):
+ cr = self.cr
+
+ v = verts[0]
+ cr.move_to(cx, cy)
+ for v in verts:
+ cr.line_to(v[0], v[1])
+ cr.move_to(cx, cy)
+
+ r, g, b, a = self.color_to_rgba(stroke_color)
+ cr.set_source_rgba(r, g, b, a)
+ cr.stroke()
+
+ def draw_dot(self, cx, cy, size=10.0, fill_color=[0, 0, 0, 0.5]):
+ cr = self.cr
+
+ cr.save()
+ cr.set_source_rgba(fill_color[0], fill_color[1], fill_color[2],
+ fill_color[3])
+ cr.arc(cx, cy, size, 0, 2 * pi)
+ cr.fill()
+ cr.restore()
+
+ def normalized_angle_01(self, theta):
+ return fmod(theta, 2 * pi) / (2 * pi)
+
+ def draw_line(self, x1, y1, x2, y2, stroke_color=[0, 0, 0, 1]):
+ cr = self.cr
+ r, g, b, a = self.color_to_rgba(stroke_color)
+ cr.set_source_rgba(r, g, b, a)
+ cr.move_to(x1, y1)
+ cr.line_to(x2, y2)
+ cr.stroke()
+
+ def draw_rect_from_center(self, cx, cy, width, height, theta=0,
+ fill=True, fill_color=[1, 1, 1],
+ stroke=False, stroke_color=[0, 0, 0]):
+ cr = self.cr
+
+ mx = width / 2.0
+ my = height / 2.0
+
+ rx = cx - (mx * cos(theta) - my * sin(theta))
+ ry = cy - (mx * sin(theta) + my * cos(theta))
+
+ cr.save()
+ cr.translate(rx, ry)
+ cr.rotate(theta)
+
+ if fill:
+ cr.rectangle(0, 0, width, height)
+ cr.set_source_rgba(fill_color[0], fill_color[1], fill_color[2], 0.8)
+ cr.fill()
+
+ if stroke:
+ cr.rectangle(0, 0, width, height)
+ cr.set_source_rgba(stroke_color[0], stroke_color[1], stroke_color[2], 0.5)
+ cr.stroke()
+ self.draw_dot(0, 0, 3.0, list(stroke_color) + [0.5])
+
+ cr.restore()
+
+ def draw_rect(self, x, y, width, height, fill=True, fill_color=[1, 1, 1],
+ stroke=False, stroke_color=[0, 0, 0]):
+ cr = self.cr
+
+ cr.save()
+ cr.translate(x, y)
+
+ if fill:
+ cr.rectangle(0, 0, width, height)
+ cr.set_source_rgba(fill_color[0], fill_color[1], fill_color[2], 0.8)
+ cr.fill()
+
+ if stroke:
+ cr.rectangle(0, 0, width, height)
+ cr.set_source_rgba(stroke_color[0], stroke_color[1], stroke_color[2], 0.5)
+ cr.stroke()
+ self.draw_dot(0, 0, 3.0, list(stroke_color) + [0.5])
+
+ cr.restore()
+
+ def draw_centered_text(self, cx, cy, text, theta=0, color=[0, 0, 0], bounding_box=False):
+ cr = self.cr
+
+ x_bearing, y_bearing, width, height, x_advance = cr.text_extents(text)[:5]
+ ascent, descent = cr.font_extents()[:2]
+
+ # The offset of the lower-left corner of the text.
+ # NOTE: y is kept on the baseline
+ tx = width / 2.0 + x_bearing
+ ty = 0
+
+ # Angles are intended clockwise by the caller, but the trigonometric
+ # functions below consider angles counter-clockwise
+ theta = -theta
+
+ # The coordinate of the lower-left corner of the rotated rectangle
+ rx = cx - tx * cos(theta) + ty * sin(theta)
+ ry = cy - tx * sin(theta) - ty * cos(theta)
+
+ cr.save()
+ cr.translate(rx, ry)
+ cr.rotate(theta)
+ self.draw_rect(0, -descent, width, ascent, fill_color=[1, 1, 1, 0.1], stroke=bounding_box)
+ cr.set_source_rgba(color[0], color[1], color[2], 0.8)
+ cr.move_to(0, 0)
+ cr.show_text(text)
+ cr.fill()
+ cr.restore()
+
+ return x_advance
+
+
+if __name__ == "__main__":
+ diagram = Diagram(400, 400)
+
+ diagram.clear()
+
+ x = 40
+ y = 200
+
+ x_offset = x
+
+ theta = 0
+
+ advance = diagram.draw_centered_text(x_offset, y, "Ciao", theta, bounding_box=True)
+ x_offset += advance
+
+ advance = diagram.draw_centered_text(x_offset, y, "____", theta + pi / 4, bounding_box=True)
+ x_offset += advance
+
+ advance = diagram.draw_centered_text(x_offset, y, "jxpqdlf", theta + pi / 2, bounding_box=True)
+ x_offset += advance
+
+ advance = diagram.draw_centered_text(x_offset, y, "pppp", theta + 3 * pi / 4, bounding_box=True)
+ x_offset += advance
+
+ advance = diagram.draw_centered_text(x_offset, y, "dddd", theta + pi, bounding_box=True)
+ x_offset += advance
+
+ advance = diagram.draw_centered_text(x_offset, y, "Jjjj", theta + 5 * pi / 4, bounding_box=True)
+ x_offset += advance
+
+ advance = diagram.draw_centered_text(x_offset, y, "1369", theta + 3 * pi / 2, bounding_box=True)
+ x_offset += advance
+
+ advance = diagram.draw_centered_text(x_offset, y, "qqqq", theta + 7 * pi / 4, bounding_box=True)
+ x_offset += advance
+
+ diagram.draw_line(0, y, 400, y, [0, 0, 1, 0.2])
+
+ diagram.show()
--- /dev/null
+all:
+ ./RadialSymmetry.py
+
+test:
+ ./RadialSymmetryDiagram.py
+
+pep8:
+ pep8 --ignore=E501 *.py
+
+clean:
+ rm -f *.pyc radial_symmetry_test.svg radial_symmetry_test.png
--- /dev/null
+#!/usr/bin/env python
+#
+# An app to interactively change RadialSymmetryDiagram properies
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from gi.repository import Gtk, Gio
+import sys
+import math
+
+import RadialSymmetryDiagram
+
+APPLICATION_ID = "it.ao2.RadialSymmetryApp"
+WINDOW_TITLE = "Radial Symmetry"
+CANVAS_WIDTH = 600
+CANVAS_HEIGHT = 600
+
+
+class RadialSymmetryWindow(Gtk.ApplicationWindow):
+
+ def __init__(self):
+ Gtk.Window.__init__(self, title=WINDOW_TITLE)
+ self.set_border_width(10)
+ self.set_property('resizable', False)
+
+ self.model = model = RadialSymmetryDiagram.RadialSymmetryModel()
+ self.diagram = RadialSymmetryDiagram.RadialSymmetryDiagram(CANVAS_WIDTH,
+ CANVAS_HEIGHT)
+ model.base_polygon_sides = 6
+ ad = Gtk.Adjustment(model.base_polygon_sides, 1, 32, 1, 0, 0)
+ self.spin_base_polygon_sides = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
+ self.spin_base_polygon_sides.connect("value-changed", self.get_base_polygon_sides)
+
+ model.element_sides = 4
+ ad = Gtk.Adjustment(model.element_sides, 2, 16, 1, 0, 0)
+ self.spin_elem_sides = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
+ self.spin_elem_sides.connect("value-changed", self.get_elem_sides)
+
+ max_element_radius = min(CANVAS_WIDTH, CANVAS_HEIGHT)
+ model.element_radius = max_element_radius / 4.0
+ ad = Gtk.Adjustment(model.element_radius, 0, max_element_radius, 1, 0, 0)
+ self.spin_element_radius = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
+ self.spin_element_radius.connect("value-changed", self.get_element_radius)
+
+ model.radial_orientate = True
+ self.checkbox_radial_orientate = Gtk.CheckButton(label="Radial orientation")
+ self.checkbox_radial_orientate.set_active(model.radial_orientate)
+ self.checkbox_radial_orientate.connect("toggled", self.get_radial_orientate)
+
+ model.show_base_polygon = False
+ self.checkbox_base_polygon = Gtk.CheckButton(label="Draw base polygon")
+ self.checkbox_base_polygon.set_active(model.show_base_polygon)
+ self.checkbox_base_polygon.connect("toggled", self.get_base_polygon)
+
+ max_base_polygon_radius = min(CANVAS_WIDTH, CANVAS_HEIGHT)
+ model.base_polygon_radius = max_base_polygon_radius / 4.0
+ ad = Gtk.Adjustment(model.base_polygon_radius, 0, max_base_polygon_radius, 1, 0, 0)
+ self.spin_base_polygon_radius = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
+ self.spin_base_polygon_radius.connect("value-changed", self.get_base_polygon_radius)
+
+ model.show_stars = False
+ self.checkbox_stars = Gtk.CheckButton(label="Draw stars")
+ self.checkbox_stars.set_active(model.show_stars)
+ self.checkbox_stars.connect("toggled", self.get_draw_stars)
+
+ model.show_elements = True
+ self.checkbox_elements = Gtk.CheckButton(label="Draw elements")
+ self.checkbox_elements.set_active(model.show_elements)
+ self.checkbox_elements.connect("toggled", self.get_draw_elements)
+
+ model.show_labels = False
+ self.checkbox_labels = Gtk.CheckButton(label="Draw labels")
+ self.checkbox_labels.set_active(model.show_labels)
+ self.checkbox_labels.connect("toggled", self.get_draw_labels)
+
+ model.element_angle_offset = 0
+ ad2 = Gtk.Adjustment(math.degrees(model.element_angle_offset), -1, 360, 1, 0, 0)
+ self.spin_element_angle = Gtk.SpinButton(adjustment=ad2, climb_rate=1, digits=0)
+ self.spin_element_angle.connect("value-changed", self.get_element_angle)
+
+ model.base_polygon_angle = 0
+ ad = Gtk.Adjustment(math.degrees(model.base_polygon_angle), -1, 360, 1, 0, 0)
+ self.spin_global_angle = Gtk.SpinButton(adjustment=ad, climb_rate=1, digits=0)
+ self.spin_global_angle.connect("value-changed", self.get_global_angle)
+
+ self.darea = Gtk.DrawingArea()
+ self.darea.connect("draw", self.draw)
+ self.darea.set_size_request(CANVAS_WIDTH, CANVAS_HEIGHT)
+
+ self.export_button = Gtk.Button(label="Export diagram")
+ self.export_button.connect("clicked", self.on_export_diagram)
+
+ controls = Gtk.VBox(spacing=10)
+
+ vbox = Gtk.VBox()
+ vbox.pack_start(Gtk.Label("Elements"), False, False, 0)
+ vbox.pack_start(self.spin_base_polygon_sides, False, False, 0)
+ controls.pack_start(vbox, False, False, 0)
+
+ vbox = Gtk.VBox()
+ vbox.pack_start(Gtk.Label("Element sides"), False, False, 0)
+ vbox.pack_start(self.spin_elem_sides, False, False, 0)
+ controls.pack_start(vbox, False, False, 0)
+
+ vbox = Gtk.VBox()
+ vbox.pack_start(Gtk.Label("Element radius"), False, False, 0)
+ vbox.pack_start(self.spin_element_radius, False, False, 0)
+ controls.pack_start(vbox, False, False, 0)
+
+ controls.pack_start(self.checkbox_radial_orientate, False, False, 0)
+
+ vbox = Gtk.VBox()
+ vbox.pack_start(Gtk.Label("Element angle"), False, False, 0)
+ vbox.pack_start(self.spin_element_angle, False, False, 0)
+ controls.pack_start(vbox, False, False, 0)
+
+ vbox = Gtk.VBox()
+ vbox.pack_start(Gtk.Label("Global angle"), False, False, 0)
+ vbox.pack_start(self.spin_global_angle, False, False, 0)
+ controls.pack_start(vbox, False, False, 0)
+
+ controls.pack_start(self.checkbox_base_polygon, False, False, 0)
+
+ vbox = Gtk.VBox()
+ vbox.pack_start(Gtk.Label("Base polygon radius"), False, False, 0)
+ vbox.pack_start(self.spin_base_polygon_radius, False, False, 0)
+ controls.pack_start(vbox, False, False, 0)
+
+ controls.pack_start(self.checkbox_elements, False, False, 0)
+ controls.pack_start(self.checkbox_stars, False, False, 0)
+ controls.pack_start(self.checkbox_labels, False, False, 0)
+ controls.pack_end(self.export_button, False, False, 0)
+
+ main_container = Gtk.HBox(spacing=10)
+ main_container.add(controls)
+ main_container.add(self.darea)
+
+ self.add(main_container)
+
+ def normalize_angle(self, angle):
+ if angle == 360:
+ angle = 0
+ elif angle == -1:
+ angle = 359
+
+ return angle
+
+ def get_base_polygon_sides(self, event):
+ self.model.base_polygon_sides = self.spin_base_polygon_sides.get_value_as_int()
+ self.darea.queue_draw()
+
+ def get_elem_sides(self, event):
+ self.model.element_sides = self.spin_elem_sides.get_value_as_int()
+ self.darea.queue_draw()
+
+ def get_element_radius(self, event):
+ self.model.element_radius = self.spin_element_radius.get_value_as_int()
+ self.darea.queue_draw()
+
+ def get_radial_orientate(self, event):
+ self.model.radial_orientate = self.checkbox_radial_orientate.get_active()
+ self.darea.queue_draw()
+
+ def get_base_polygon(self, event):
+ self.model.show_base_polygon = self.checkbox_base_polygon.get_active()
+ self.darea.queue_draw()
+
+ def get_base_polygon_radius(self, event):
+ self.model.base_polygon_radius = self.spin_base_polygon_radius.get_value_as_int()
+ self.darea.queue_draw()
+
+ def get_draw_elements(self, event):
+ self.model.show_elements = self.checkbox_elements.get_active()
+ self.darea.queue_draw()
+
+ def get_draw_stars(self, event):
+ self.model.show_stars = self.checkbox_stars.get_active()
+ self.darea.queue_draw()
+
+ def get_draw_labels(self, event):
+ self.model.show_labels = self.checkbox_labels.get_active()
+ self.darea.queue_draw()
+
+ def get_global_angle(self, event):
+ angle = self.normalize_angle(self.spin_global_angle.get_value_as_int())
+ self.spin_global_angle.set_value(angle)
+ self.model.base_polygon_angle = math.radians(angle)
+ self.darea.queue_draw()
+
+ def get_element_angle(self, event):
+ angle = self.normalize_angle(self.spin_element_angle.get_value_as_int())
+ self.spin_element_angle.set_value(angle)
+ self.model.element_angle_offset = math.radians(angle)
+ self.darea.queue_draw()
+
+ def on_export_diagram(self, event):
+ dialog = Gtk.FileChooserDialog("Export Diagram", self,
+ Gtk.FileChooserAction.SAVE,
+ (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
+ dialog.set_property('do-overwrite-confirmation', True)
+
+ filter_svg = Gtk.FileFilter()
+ filter_svg.set_name("SVG files")
+ filter_svg.add_mime_type("image/svg+xml")
+ dialog.add_filter(filter_svg)
+
+ filter_png = Gtk.FileFilter()
+ filter_png.set_name("PNG files")
+ filter_png.add_mime_type("image/png")
+ dialog.add_filter(filter_png)
+
+ response = dialog.run()
+ if response == Gtk.ResponseType.OK:
+ filename = dialog.get_filename()
+ if filename.endswith(".svg"):
+ self.diagram.save_svg(filename[:-4])
+ elif filename.endswith(".png"):
+ self.diagram.save_png(filename[:-4])
+
+ dialog.destroy()
+
+ def draw(self, darea, cr):
+ self.diagram.draw(self.model)
+
+ src_surface = self.diagram.surface
+ cr.set_source_surface(src_surface, 0, 0)
+ cr.paint()
+
+
+class RadialSymmetryApp(Gtk.Application):
+
+ def __init__(self):
+ Gtk.Application.__init__(self,
+ application_id=APPLICATION_ID,
+ flags=Gio.ApplicationFlags.FLAGS_NONE)
+ self.connect("activate", self.activateCb)
+
+ def activateCb(self, app):
+ window = RadialSymmetryWindow()
+ app.add_window(window)
+ window.show_all()
+
+if __name__ == "__main__":
+ app = RadialSymmetryApp()
+ exit_status = app.run(sys.argv)
+ sys.exit(exit_status)
--- /dev/null
+#!/usr/bin/env python
+#
+# A study on radial symmetry based
+#
+# Copyright (C) 2015 Antonio Ospite <ao2@ao2.it>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from math import *
+import colorsys
+import Diagram
+
+
+class RadialSymmetryModel():
+ def __init__(self, base_polygon_sides=6, base_polygon_radius=None,
+ base_polygon_angle=0, show_base_polygon=False,
+ element_sides=3, element_radius=None, element_angle_offset=0,
+ radial_orientation=True, show_elements=True, show_stars=False,
+ show_labels=False):
+ self.base_polygon_sides = base_polygon_sides
+ self.base_polygon_radius = base_polygon_radius
+ self.base_polygon_angle = base_polygon_angle
+ self.show_base_polygon = show_base_polygon
+ self.element_sides = element_sides
+ self.element_radius = element_radius
+ self.element_angle_offset = element_angle_offset
+ self.radial_orientate = radial_orientation
+ self.show_elements = show_elements
+ self.show_stars = show_stars
+ self.show_labels = show_labels
+
+
+class RadialSymmetryDiagram(Diagram.Diagram):
+
+ def __init__(self, width, height, background=[1, 1, 1]):
+ Diagram.Diagram.__init__(self, width, height, background)
+
+ def draw(self, model):
+ cx = self.width / 2.0
+ cy = self.height / 2.0
+
+ # internal radius
+ if model.base_polygon_radius is None:
+ model.base_polygon_radius = min(self.width, self.height) / 4.0
+
+ if model.element_radius is None:
+ model.element_radius = model.base_polygon_radius
+
+ self.clear()
+ self.draw_elements(cx, cy, model)
+
+ def draw_elements(self, cx, cy, model):
+ central_angle = 2 * pi / model.base_polygon_sides
+
+ base_polygon_orientation = central_angle + model.base_polygon_angle
+
+ verts = self.get_regular_polygon(cx, cy, model.base_polygon_sides, model.base_polygon_radius,
+ base_polygon_orientation)
+
+ if model.show_base_polygon:
+ self.draw_polygon(verts, [0, 0, 0])
+
+ for i, v in enumerate(verts[:]):
+ radial_orientation_angle = (i + 1) * central_angle
+ rotated_radial_orientation_angle = radial_orientation_angle + model.base_polygon_angle
+
+ # the element orientation may depend the base polygon rotation
+ if model.radial_orientate:
+ element_angle = rotated_radial_orientation_angle
+ else:
+ element_angle = 2 * pi / model.element_sides
+
+ element_angle += model.element_angle_offset
+
+ # the element color depends oly on the element index
+ a = self.normalized_angle_01(radial_orientation_angle)
+ color = colorsys.hsv_to_rgb(a, 1.0, 1.0)
+
+ self.draw_element(v[0], v[1], model, element_angle, color)
+
+ if model.show_labels:
+ ta = self.normalized_angle_01(rotated_radial_orientation_angle)
+ text = ("%.2f" % (ta * 360)).rstrip('0').rstrip('.')
+ color = colorsys.hsv_to_rgb(a, 1.0, 1.0)
+ self.draw_centered_text(v[0], v[1], text,
+ rotated_radial_orientation_angle, color)
+
+ def draw_element(self, x, y, model, theta, color=[0, 0, 0]):
+ if model.show_elements:
+ tverts = self.get_regular_polygon(x, y, model.element_sides, model.element_radius, theta)
+ self.draw_polygon(tverts, color)
+
+ if model.show_stars:
+ apothem = model.element_radius * cos(pi / model.element_sides)
+ apothem_angle = theta + pi / model.element_sides
+
+ sverts = self.get_regular_polygon(x, y, model.element_sides, apothem,
+ apothem_angle)
+ self.draw_star(x, y, sverts, color)
+
+
+if __name__ == '__main__':
+ h = RadialSymmetryDiagram(800, 600)
+ h.draw(RadialSymmetryModel(base_polygon_sides=10, element_sides=4, show_stars=True, show_labels=False))
+ h.draw(RadialSymmetryModel(base_polygon_sides=31, element_sides=4, show_stars=False, show_labels=False))
+ h.show()
+ h.save_svg("radial_symmetry_test")
+ h.save_png("radial_symmetry_test")
--- /dev/null
+- Saving to SVG only saves a bitmap version of the diagram