From 5aed1f66308acf0c57ce86c77585062c3b122f18 Mon Sep 17 00:00:00 2001 From: Abdur-Rahmaan Janhangeer Date: Tue, 26 Apr 2022 23:52:15 +0400 Subject: [PATCH] bump: 0.7.0 --- CHANGELOG.md | 4 + README.md | 118 +++++++++++++++++++ hooman/__init__.py | 2 +- hooman/demos/file.svg | 205 ++++++++++++++++++++++++++++++++++ hooman/demos/scatter_chart.py | 4 +- hooman/demos/test.py | 65 ++++++++--- hooman/hooman.py | 114 ++++++++++++++++++- hooman/svg.py | 38 +++++++ setup.py | 2 + 9 files changed, 526 insertions(+), 26 deletions(-) create mode 100644 hooman/demos/file.svg create mode 100644 hooman/svg.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f9cf8..12b06b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 0.7.0 + +- SVG generation support +- Keyword arguments supported in addition to dictionary ### 0.6.0 diff --git a/README.md b/README.md index efbeb31..e948475 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ The package for clearer, shorter and cleaner PyGame codebases! Fun fact: Codementor.io [tweeted about Hooman](https://twitter.com/CodementorIO/status/1306295790441246725?s=20) tagged #LearnPython #100DaysOfCode +NEW: save to svg now supported + # hooman is powerful! See a snake game by [TheBigKahuna353](https://github.com/TheBigKahuna353) @@ -165,6 +167,122 @@ For about the same number of lines for a simple snake game, you get one complete - [Display most frequent words using PyGame](https://www.pythonkitchen.com/display-most-frequent-words-python-pygame/) - [Realtime CPU monitor using PyGame](https://www.pythonkitchen.com/realtime-cpu-monitor-using-pygame/) +# (new) save to svg + +```python +# https://github.com/mwaskom/seaborn-data/blob/master/penguins.csv + +from hooman import Hooman +import pandas as pd +import os + +window_width, window_height = 650, 600 +hapi = Hooman(window_width, window_height, svg=True) + + +base_path = os.path.dirname(os.path.abspath(__file__)) +df = pd.read_csv(os.path.join(base_path, "data", "penguins.csv")) +df = df.fillna(0) + +data = {k:list(df[k]) for k in df.columns.values.tolist()} + +hapi.background(255) + +colx = "bill_length_mm" +coly = "bill_depth_mm" + +hapi.scatterchart( + 40, + 30, + 500, + 500, + { + "data": data, + "ticks_y": 12, + "ticks_x": 12, + "range_y": [min(data[coly]), max(data[coly])], + "range_x": [min(data[colx]), max(data[colx])], + "show_axes": True, + "tick_size": 10, + "show_ticks_x": True, + "show_ticks_y": True, + "x": colx, + "y": coly, + "plot_background": False, + "plot_grid": False, + "line_color": 200, + "type": "hist", + "hist_color": "g" + }, + ) + +hapi.save_svg(os.path.join(base_path, 'file.svg')) +``` + +# (new) keyword argument same as dictionary + + +```python +hapi.scatterchart( + 40, + 30, + 500, + 500, + { + "data": data, + "ticks_x": 5, + "mouse_line": False, + "range_y": [min(data[coly]), max(data[coly])], + "range_x": [min(data[colx]), max(data[colx])], + "tick_size": 10, + "show_ticks_x": True, + "show_ticks_y": True, + "x": colx, + "y": coly, + "hue": "clarity", + "hue_order": clarity_ranking, + "size": "depth", + "plot_background": False, + "plot_background_grid": True, + "plot_background_color": (234,234,242), + "plot_background_grid_color": 200, + "line_color": 200 + } + ) + +# same as + +hapi.scatterchart( + 40, + 30, + 500, + 500, + { + "data": data, + "ticks_x": 5, + "mouse_line": False, + "range_y": [min(data[coly]), max(data[coly])], + "range_x": [min(data[colx]), max(data[colx])], + "tick_size": 10, + "show_ticks_x": True, + "show_ticks_y": True, + "hue": "clarity", + "hue_order": clarity_ranking, + "size": "depth", + "plot_background": False, + "plot_background_grid": True, + "plot_background_color": (234,234,242), + "plot_background_grid_color": 200, + "line_color": 200 + }, + x=colx, + y=coly + ) + +# i.e you can mix both or use one option over the other + +``` + # demos ![](assets/color_change.gif) diff --git a/hooman/__init__.py b/hooman/__init__.py index 92f4ffe..064604a 100644 --- a/hooman/__init__.py +++ b/hooman/__init__.py @@ -1,5 +1,5 @@ from .hooman import Hooman from .formula import * -version_info = (0, 6, 0) +version_info = (0, 7, 0) __version__ = ".".join([str(v) for v in version_info]) diff --git a/hooman/demos/file.svg b/hooman/demos/file.svg new file mode 100644 index 0000000..7e68246 --- /dev/null +++ b/hooman/demos/file.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +bill_depth_mm +bill_length_mm + +21.5 + +20.5 + +19.5 + +18.5 + +17.5 + +16.5 + +15.5 + +14.5 + +13.5 + +12.5 + +11.5 + +10.5 + +0.0 + +4.97 + +9.93 + +14.9 + +19.87 + +24.83 + +29.8 + +34.77 + +39.73 + +44.7 + +49.67 + +54.63 + +59.6 + + diff --git a/hooman/demos/scatter_chart.py b/hooman/demos/scatter_chart.py index cb36289..3059628 100644 --- a/hooman/demos/scatter_chart.py +++ b/hooman/demos/scatter_chart.py @@ -26,12 +26,10 @@ 500, { "data": data, - "ticks_y": 8, "ticks_x": 5, "mouse_line": False, "range_y": [min(data[coly]), max(data[coly])], "range_x": [min(data[colx]), max(data[colx])], - "show_axes": True, "tick_size": 10, "show_ticks_x": True, "show_ticks_y": True, @@ -45,7 +43,7 @@ "plot_background_color": (234,234,242), "plot_background_grid_color": 200, "line_color": 200 - }, + } ) while hapi.is_running: diff --git a/hooman/demos/test.py b/hooman/demos/test.py index c3a9a43..05d928d 100644 --- a/hooman/demos/test.py +++ b/hooman/demos/test.py @@ -1,18 +1,47 @@ -import seaborn as sns -import matplotlib.pyplot as plt -sns.set_theme(style="whitegrid") - -# Load the example diamonds dataset -diamonds = sns.load_dataset("diamonds") - -# Draw a scatter plot while assigning point colors and sizes to different -# variables in the dataset -f, ax = plt.subplots(figsize=(6.5, 6.5)) -sns.despine(f, left=True, bottom=True) -clarity_ranking = ["I1", "SI2", "SI1", "VS2", "VS1", "VVS2", "VVS1", "IF"] -sns.scatterplot(x="carat", y="price", - hue="clarity", size="depth", - palette="ch:r=-.2,d=.3_r", - hue_order=clarity_ranking, - sizes=(1, 8), linewidth=0, - data=diamonds, ax=ax) \ No newline at end of file +# https://github.com/mwaskom/seaborn-data/blob/master/penguins.csv + +from hooman import Hooman +import pandas as pd +import os + +window_width, window_height = 650, 600 +hapi = Hooman(window_width, window_height, svg=True) + + +base_path = os.path.dirname(os.path.abspath(__file__)) +df = pd.read_csv(os.path.join(base_path, "data", "penguins.csv")) +df = df.fillna(0) + +data = {k:list(df[k]) for k in df.columns.values.tolist()} + +hapi.background(255) + +colx = "bill_length_mm" +coly = "bill_depth_mm" + +hapi.scatterchart( + 40, + 30, + 500, + 500, + { + "data": data, + "ticks_y": 12, + "ticks_x": 12, + "range_y": [min(data[coly]), max(data[coly])], + "range_x": [min(data[colx]), max(data[colx])], + "show_axes": True, + "tick_size": 10, + "show_ticks_x": True, + "show_ticks_y": True, + "x": colx, + "y": coly, + "plot_background": False, + "plot_grid": False, + "line_color": 200, + "type": "hist", + "hist_color": "g" + }, + ) + +hapi.save_svg(os.path.join(base_path, 'file.svg')) diff --git a/hooman/hooman.py b/hooman/hooman.py index 44cfe98..c5ecd13 100644 --- a/hooman/hooman.py +++ b/hooman/hooman.py @@ -40,9 +40,11 @@ from .charts import piechart from .charts import scatterchart +from .svg import SVG + class Hooman: - def __init__(self, WIDTH, HEIGHT): + def __init__(self, WIDTH, HEIGHT, svg=False): pygame.init() self.WIDTH = WIDTH self.HEIGHT = HEIGHT @@ -118,6 +120,9 @@ def __init__(self, WIDTH, HEIGHT): self.hls_to_rgb = hls_to_rgb self.rgb_to_hls = rgb_to_hls + self._svg = svg + self._svg_commands = [] + # # colors # @@ -198,9 +203,37 @@ def pop_matrix(self): def ellipse(self, x, y, width, height): pygame.draw.ellipse(self.screen, self._fill, (x, y, width, height)) + if self._svg: + attributes = { + 'cx': x, + 'cy': y, + 'rx': width/2, + 'ry': height/2, + 'fill': f'rgb({self._fill[0]},{self._fill[1]},{self._fill[2]})', + 'stroke': f'rgb({self._stroke[0]},{self._stroke[1]},{self._stroke[2]})', + 'stroke-width': self._stroke_weight, + 'transform': f'rotate({self._rotation}, {x}, {y})' # "translate(30,40) rotate(45)' + } + svg_element = SVG.tag('ellipse', attributes=attributes, self_close=True) + self._svg_commands.append(svg_element) + def rect(self, x, y, width, height): if self._rotation % 360 == 0: pygame.draw.rect(self.screen, self._fill, (x, y, width, height)) + + if self._svg: + attributes = { + 'x': x, + 'y': y, + 'width': width, + 'height': height, + 'fill': f'rgb({self._fill[0]},{self._fill[1]},{self._fill[2]})', + 'stroke': f'rgb({self._stroke[0]},{self._stroke[1]},{self._stroke[2]})', + 'stroke-width': self._stroke_weight, + 'transform': f'rotate({self._rotation}, {x}, {y})' # "translate(30,40) rotate(45)' + } + svg_element = SVG.tag('rect', attributes=attributes, self_close=True) + self._svg_commands.append(svg_element) else: self.regular_polygon(x, y, width, height, 4, 45) @@ -212,6 +245,20 @@ def text(self, letters, x, y): text = pygame.transform.rotate(text, self._rotation) self.screen.blit(text, (x, y)) + + if self._svg: + attributes = { + 'x': x, + 'y': y, + 'font-size': self._font_size, + # 'fill': f'rgb({self._fill[0]},{self._fill[1]},{self._fill[2]})', + # 'stroke': f'rgb({self._stroke[0]},{self._stroke[1]},{self._stroke[2]})', + # 'stroke-width': self.stroke_size, + 'transform': f'rotate({self._rotation}, {x}, {y})' # "translate(30,40) rotate(45)' + } + svg_element = SVG.tag('text', content=letters, attributes=attributes) + self._svg_commands.append(svg_element) + def arc(self, x, y, width, height, start_angle, end_angle): pygame.draw.arc( self.screen, @@ -231,22 +278,68 @@ def vertex(self, coord): def end_shape(self, fill=1): if fill: pygame.draw.polygon(self.screen, self._fill, self._polygon_coords) + svg_fill = f'rgb({self._fill[0]},{self._fill[1]},{self._fill[2]})' else: pygame.draw.polygon( self.screen, self._fill, self._polygon_coords, self._stroke_weight ) + svg_fill = 'none' + + if self._svg: + points = [] + for p in self._polygon_coords: + points.append(f'{p[0]},{p[1]}') + attributes = { + 'points': ' '.join(points), + 'fill': svg_fill, + 'stroke': f'rgb({self._stroke[0]},{self._stroke[1]},{self._stroke[2]})', + 'stroke-width': self._stroke_weight, + 'transform': f'rotate({self._rotation})' # "translate(30,40) rotate(45)' + } + svg_element = SVG.tag('polygon', attributes=attributes, self_close=True) + self._svg_commands.append(svg_element) def polygon(self, coords, fill=1): if fill: pygame.draw.polygon(self.screen, self._fill, coords) + svg_fill = f'rgb({self._fill[0]},{self._fill[1]},{self._fill[2]})' else: pygame.draw.polygon(self.screen, self._fill, coords, self._stroke_weight) + svg_fill = 'none' + + if self._svg: + points = [] + for p in self._polygon_coords: + points.append(f'{p[0]},{p[1]}') + attributes = { + 'points': ' '.join(points), + 'fill': svg_fill, + 'stroke': f'rgb({self._stroke[0]},{self._stroke[1]},{self._stroke[2]})', + 'stroke-width': self._stroke_weight, + 'transform': f'rotate({self._rotation})' # "translate(30,40) rotate(45)' + } + svg_element = SVG.tag('polygon', attributes=attributes, self_close=True) + self._svg_commands.append(svg_element) def line(self, x1, y1, x2, y2): pygame.draw.line( self.screen, self._stroke, [x1, y1], [x2, y2], self._stroke_weight ) + + if self._svg: + attributes = { + 'x1': x1, + 'x2': x2, + 'y1': y1, + 'y2': y2, + 'stroke': f'rgb({self._stroke[0]},{self._stroke[1]},{self._stroke[2]})', + 'stroke-width': self._stroke_weight, + 'transform': f'rotate({self._rotation}, {x1}, {y1})' # "translate(30,40) rotate(45)' + } + svg_element = SVG.tag('line', attributes=attributes, self_close=True) + self._svg_commands.append(svg_element) + def star(self, x, y, r1, r2, npoints): self._star(self, x, y, r1, r2, npoints, self._rotation) @@ -412,10 +505,12 @@ def second(self) -> int: # charts # - def barchart(self, x, y, w, h, params): + def barchart(self, x, y, w, h, params, **kwargs): + params.update(kwargs) self._barchart(self, x, y, w, h, params) - def linechart(self, x, y, w, h, params): + def linechart(self, x, y, w, h, params, **kwargs): + params.update(kwargs) self._linechart(self, x, y, w, h, params) def piechart(self, x, y, radius, data, start_rad=0): @@ -431,5 +526,16 @@ def piechart(self, x, y, radius, data, start_rad=0): ''' self._piechart(self, x, y, radius, data, start_rad=start_rad) - def scatterchart(self, x, y, width, height, params): + def scatterchart(self, x, y, width, height, params, **kwargs): + params.update(kwargs) self._scatterchart(self, x, y, width, height, params) + + + # + # svg + # + + + def save_svg(self, path): + # print(self._svg_commands) + SVG.save(self._svg_commands, path, self.WIDTH, self.HEIGHT) diff --git a/hooman/svg.py b/hooman/svg.py new file mode 100644 index 0000000..20cdc61 --- /dev/null +++ b/hooman/svg.py @@ -0,0 +1,38 @@ +class SVG: + + + @classmethod + def tag(cls, tag_name: str, content: str='', attributes: dict={}, self_close=False) -> str: + attribs = [] + for k in attributes: + if attributes[k]: + attribs.append(f'{k}="{str(attributes[k])}"') + + attribs_str = ' '.join(attribs) + + if not self_close: + return f'<{tag_name} {attribs_str}>{content}' + else: + return f'<{tag_name} {attribs_str}/>' + + + @classmethod + def save(cls, commands, path, width, height): + commands_str = '\n'.join(commands) + string = f''' + + +{commands_str} + + +''' + + with open(path, 'w+') as f: + f.write(string) + + if len(commands) == 0: + print('warning! SVG mode not enabled!') + print('saved svg to', path) + diff --git a/setup.py b/setup.py index b936447..63f89fc 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,8 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], keywords="human pygame canvas api wrapper", # Optional # You can just specify package directories manually here if your project is