diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f332c3..12b06b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +### 0.7.0 + +- SVG generation support +- Keyword arguments supported in addition to dictionary + +### 0.6.0 + +- Enhanced line chart +- Enhanced scatter plot +- Add scatter plot hist + + ### 0.5.0 diff --git a/README.md b/README.md index 806b341..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) @@ -676,6 +794,65 @@ while hapi.is_running: hapi.event_loop() ``` +#### scatter plot hist + + +![](assets/scatter_chart_hist.png) + +```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) + + +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": "b" + }, + ) + +while hapi.is_running: + bg_col = (255, 255, 255) + + hapi.flip_display() + hapi.event_loop() +``` + # All Demos - [Buttons.py](https://github.com/Abdur-rahmaanJ/hooman/tree/master/hooman/demos/Buttons.py) @@ -1266,33 +1443,33 @@ hapi.barchart(x, y, w, h, params) ```python params = { - "ticks_y": 10, - "ticks_x": 10, - "tick_size": 5, - "range_y": [0, 100], - "range_x": [0, 100], - "lines":[{ - "label": "---", - "color": (255, 0, 0), - "data": [[1,1]], - "values_window": 200 - }], - "labels": ["apple", "", "", "tree"], - "line_color": (200, 200, 200), - "text_color": (100, 100, 100), - "mouse_line": False, - "mouse_line_color": (255, 0, 0), - "graph_color": (0, 0, 0), - "show_axes": True, - "show_ticks_x": True, - "show_ticks_y": True, - "x_axis_label": "x_axis_label", - "y_axis_label": "y_axis_label", - "plot_background": True, - "plot_background_grid": True, - "plot_background_color": (234,234,242), - "plot_background_grid_color": 255 -} + "ticks_y": 10, + "ticks_x": 10, + "tick_size": 5, + "range_y": [0, 100], + "range_x": [0, 100], + "lines":[{ + "label": "---", + "color": (255, 0, 0), + "data": [[1,1]], + "values_window": 200 + }], + "labels": ["apple", "", "", "tree"], + "line_color": (200, 200, 200), + "text_color": (100, 100, 100), + "mouse_line": False, + "mouse_line_color": (255, 0, 0), + "graph_color": (0, 0, 0), + "show_axes": True, + "show_ticks_x": True, + "show_ticks_y": True, + "x_axis_label": "x_axis_label", + "y_axis_label": "y_axis_label", + "plot_background": True, + "plot_grid": True, + "plot_background_color": (234,234,242), + "plot_grid_color": 255 + } hapi.linechart(x, y, w, h, params) ``` @@ -1301,43 +1478,47 @@ hapi.linechart(x, y, w, h, params) ```python params = { - "ticks_y": 10, - "ticks_x": 10, - "tick_size": 5, - "range_y": [0, 100], - "range_x": [0, 100], - "data": { # just add in the format : values - "carat": [0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, 0.23], - "cut": ["Ideal", "Premium", "Good", "Premium", "Good", "Very Good", "Very Good", "Very Good", "Fair"], - "color": ["E", "E", "E", "I", "J", "J", "I", "H", "E", "H"], - "clarity": ["SI2", "SI1", "VS1", "VS2", "SI2", "VVS2", "VVS1", "SI1", "VS2", "VS1"], - "depth": [61.5, 59.8, 56.9, 62.4, 63.3, 62.8, 62.3, 61.9, 65.1, 59.4], - "table": [55, 61, 65, 58, 58, 57, 57, 55, 61, 61], - "price": [326, 326, 327, 334, 335, 336, 336, 337, 337, 338], - "x": [3.95, 3.89, 4.05, 4.2, 4.34, 3.94, 3.95, 4.07, 3.87, 4], - "y": [3.98, 3.84, 4.07, 4.23, 4.35, 3.96, 3.98, 4.11, 3.78, 4.05], - "z": [2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, 2.39] - }, - "hue": "clarity", - "size": "depth", - "text_color": (100, 100, 100), - "mouse_line": False, - "mouse_line_color": (255, 0, 0), - "graph_color": (0, 0, 0), - "show_axes": True, - "show_ticks_x": True, - "show_ticks_y": True, - "x": "price", - "y": "carat", - "plot_background": True, - "plot_background_grid": True, - "plot_background_color": (234,234,242), - "plot_background_grid_color": 255, - "line_color": 200, - "strong_color": (107, 107, 255), - "light_color": (235, 235, 255) -} -hapi.scatterchart(hapi, x, y, w, h, params) + "ticks_y": 10, + "ticks_x": 10, + "tick_size": 5, + "range_y": [0, 100], + "range_x": [0, 100], + "data": { + "carat": [0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, 0.23], + "cut": ["Ideal", "Premium", "Good", "Premium", "Good", "Very Good", "Very Good", "Very Good", "Fair"], + "color": ["E", "E", "E", "I", "J", "J", "I", "H", "E", "H"], + "clarity": ["SI2", "SI1", "VS1", "VS2", "SI2", "VVS2", "VVS1", "SI1", "VS2", "VS1"], + "depth": [61.5, 59.8, 56.9, 62.4, 63.3, 62.8, 62.3, 61.9, 65.1, 59.4], + "table": [55, 61, 65, 58, 58, 57, 57, 55, 61, 61], + "price": [326, 326, 327, 334, 335, 336, 336, 337, 337, 338], + "x": [3.95, 3.89, 4.05, 4.2, 4.34, 3.94, 3.95, 4.07, 3.87, 4], + "y": [3.98, 3.84, 4.07, 4.23, 4.35, 3.96, 3.98, 4.11, 3.78, 4.05], + "z": [2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, 2.39] + }, + "hue": None, + "hue_order": [], + "size": None, + "text_color": (100, 100, 100), + "mouse_line": False, + "mouse_line_color": (255, 0, 0), + "graph_color": (0, 0, 0), + "show_axes": True, + "show_ticks_x": True, + "show_ticks_y": True, + "x": "price", + "y": "carat", + "plot_background": True, + "plot_grid": True, + "plot_background_color": (234,234,242), + "plot_grid_color": 255, + "line_color": 200, + "strong_color": (107, 107, 255), + "light_color": (235, 235, 255), + "type": "normal", + "kind": "rect", + "hist_color": "b", + "hist_color_invert": False + } ``` diff --git a/assets/scatter_chart_hist.png b/assets/scatter_chart_hist.png new file mode 100644 index 0000000..a8bf7b7 Binary files /dev/null and b/assets/scatter_chart_hist.png differ diff --git a/hooman/__init__.py b/hooman/__init__.py index e100071..f830da7 100644 --- a/hooman/__init__.py +++ b/hooman/__init__.py @@ -1,5 +1,5 @@ from .hooman import Hooman from .formula import * -version_info = (0, 5, 0) +version_info = (0, 7, 1) __version__ = ".".join([str(v) for v in version_info]) diff --git a/hooman/charts.py b/hooman/charts.py index ed65436..fb1b1dd 100644 --- a/hooman/charts.py +++ b/hooman/charts.py @@ -1,5 +1,8 @@ import copy +from .check import check_params + + def barchart(hapi, x, y, w, h, params): options = { "ticks_y": 10, @@ -11,6 +14,7 @@ def barchart(hapi, x, y, w, h, params): "text_color": (100, 100, 100), "mouse_line": False, } + check_params(params, options, "bar chart") options.update(params) hapi.stroke_size(2) hapi.stroke(options["line_color"]) @@ -66,12 +70,14 @@ def linechart(hapi, x, y, w, h, params): "tick_size": 5, "range_y": [0, 100], "range_x": [0, 100], - "lines":[{ + "lines": [ + { "label": "---", "color": (255, 0, 0), - "data": [[1,1]], - "values_window": 200 - }], + "data": [[1, 1]], + "values_window": 200, + } + ], "labels": ["apple", "", "", "tree"], "line_color": (200, 200, 200), "text_color": (100, 100, 100), @@ -84,14 +90,14 @@ def linechart(hapi, x, y, w, h, params): "x_axis_label": "x_axis_label", "y_axis_label": "y_axis_label", "plot_background": True, - "plot_background_grid": True, - "plot_background_color": (234,234,242), - "plot_background_grid_color": 255 + "plot_grid": True, + "plot_background_color": (234, 234, 242), + "plot_grid_color": 255, } + check_params(params, options, "line chart") options.update(params) hapi.stroke_size(2) - # --- plot background --- if options["plot_background"]: @@ -101,40 +107,36 @@ def linechart(hapi, x, y, w, h, params): # --- plot background grid --- + if options["plot_grid"]: - if options["plot_background_grid"]: - - hapi.stroke(options["plot_background_grid_color"]) + hapi.stroke(options["plot_grid_color"]) # --- vertical - bg_grid_x1 = x + bg_grid_x1 = x bg_grid_y1 = y - bg_grid_x2 = x + bg_grid_x2 = x bg_grid_y2 = y + h line_num = 1 - while(bg_grid_x1 <= x+w): - if (1 < line_num < options['ticks_x']+1): + while bg_grid_x1 <= x + w: + if 1 < line_num < options["ticks_x"] + 1: hapi.line(bg_grid_x1, bg_grid_y1, bg_grid_x2, bg_grid_y2) - bg_grid_x1 += (w // options['ticks_x']) - bg_grid_x2 += (w // options['ticks_x']) + bg_grid_x1 += w // options["ticks_x"] + bg_grid_x2 += w // options["ticks_x"] line_num += 1 - # --- horizontal - bg_grid_x1 = x + bg_grid_x1 = x bg_grid_y1 = y - bg_grid_x2 = x + w + bg_grid_x2 = x + w bg_grid_y2 = y line_num = 1 - while(bg_grid_y1 <= y+h): - if (1 < line_num < options['ticks_y']+1): + while bg_grid_y1 <= y + h: + if 1 < line_num < options["ticks_y"] + 1: hapi.line(bg_grid_x1, bg_grid_y1, bg_grid_x2, bg_grid_y2) - bg_grid_y1 += (h // options['ticks_y']) - bg_grid_y2 += (h // options['ticks_y']) + bg_grid_y1 += h // options["ticks_y"] + bg_grid_y2 += h // options["ticks_y"] line_num += 1 - - # --- axes --- if options["show_axes"]: @@ -144,16 +146,22 @@ def linechart(hapi, x, y, w, h, params): hapi.fill(options["text_color"]) - # --- axes labels - hapi.push_matrix() hapi.rotate(90) - hapi.text(options["x_axis_label"], x-35, y + (h-(hapi._font.size(options["x_axis_label"])[0])//2)) + hapi.text( + options["x_axis_label"], + x - 35, + y + (h - (hapi._font.size(options["x_axis_label"])[0]) // 2), + ) hapi.pop_matrix() - hapi.text(options["y_axis_label"], x + (w-(hapi._font.size(options["y_axis_label"])[0])//2), y+h+35) + hapi.text( + options["y_axis_label"], + x + (w - (hapi._font.size(options["y_axis_label"])[0]) // 2), + y + h + 35, + ) y_top_val = options["range_y"][1] for t in range(options["ticks_y"]): @@ -186,15 +194,20 @@ def linechart(hapi, x, y, w, h, params): for l_index, line in enumerate(copy.deepcopy(options["lines"])): for i, d in enumerate(copy.deepcopy(line["data"])): - + try: x1 = hapi.constrain( d[0], options["range_x"][0], options["range_x"][1], x, x + w ) - y1 = y + h -hapi.constrain( + y1 = ( + y + + h + - hapi.constrain( d[1], options["range_y"][0], options["range_y"][1], y, y + h - ) + y - + ) + + y + ) + x2 = hapi.constrain( line["data"][i + 1][0], options["range_x"][0], @@ -202,14 +215,19 @@ def linechart(hapi, x, y, w, h, params): x, x + w, ) - y2 = y + h -hapi.constrain( + y2 = ( + y + + h + - hapi.constrain( line["data"][i + 1][1], options["range_y"][0], options["range_y"][1], y, y + h, - ) + y - + ) + + y + ) + # 60 is arbitrary value # hapi.fill(hapi.color['yellow']) # print(x1, y1) @@ -241,22 +259,23 @@ def linechart(hapi, x, y, w, h, params): hapi.line(x, limit_y, x + w, limit_y) hapi.line(limit_x, y, limit_x, y + h) - #tex = "{} {}".format(limit_x, limit_y) - #hapi.text(tex, 100, 100) + # tex = "{} {}".format(limit_x, limit_y) + # hapi.text(tex, 100, 100) # legend legend_x_start = x + w + 10 - legend_y_start = y + legend_y_start = y legend_y = legend_y_start - for line in options['lines']: - hapi.fill(line['color']) + for line in options["lines"]: + hapi.fill(line["color"]) hapi.ellipse(legend_x_start, legend_y, 10, 10) hapi.font_size(15) - hapi.text(line['label'], legend_x_start+15, legend_y) + hapi.text(line["label"], legend_x_start + 15, legend_y) legend_y += 10 + def piechart(hapi, x, y, radius, data, start_rad=0): first_run = True total = 0 @@ -266,9 +285,9 @@ def piechart(hapi, x, y, radius, data, start_rad=0): first_run = False previous_a = 0 - for i,d in enumerate(data): + for i, d in enumerate(data): hapi.fill(d[2]) - end_a = previous_a + (d[1]/total)*360 + end_a = previous_a + (d[1] / total) * 360 hapi.fill_arc(x, y, radius, previous_a, end_a, start_rad=start_rad) previous_a = end_a @@ -281,19 +300,41 @@ def scatterchart(hapi, x, y, w, h, params): "range_y": [0, 100], "range_x": [0, 100], "data": { - "carat": [0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, 0.23], - "cut": ["Ideal", "Premium", "Good", "Premium", "Good", "Very Good", "Very Good", "Very Good", "Fair"], + "carat": [0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, 0.23], + "cut": [ + "Ideal", + "Premium", + "Good", + "Premium", + "Good", + "Very Good", + "Very Good", + "Very Good", + "Fair", + ], "color": ["E", "E", "E", "I", "J", "J", "I", "H", "E", "H"], - "clarity": ["SI2", "SI1", "VS1", "VS2", "SI2", "VVS2", "VVS1", "SI1", "VS2", "VS1"], + "clarity": [ + "SI2", + "SI1", + "VS1", + "VS2", + "SI2", + "VVS2", + "VVS1", + "SI1", + "VS2", + "VS1", + ], "depth": [61.5, 59.8, 56.9, 62.4, 63.3, 62.8, 62.3, 61.9, 65.1, 59.4], "table": [55, 61, 65, 58, 58, 57, 57, 55, 61, 61], "price": [326, 326, 327, 334, 335, 336, 336, 337, 337, 338], "x": [3.95, 3.89, 4.05, 4.2, 4.34, 3.94, 3.95, 4.07, 3.87, 4], "y": [3.98, 3.84, 4.07, 4.23, 4.35, 3.96, 3.98, 4.11, 3.78, 4.05], - "z": [2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, 2.39] + "z": [2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, 2.39], }, - "hue": "clarity", - "size": "depth", + "hue": None, + "hue_order": [], + "size": None, "text_color": (100, 100, 100), "mouse_line": False, "mouse_line_color": (255, 0, 0), @@ -304,19 +345,24 @@ def scatterchart(hapi, x, y, w, h, params): "x": "price", "y": "carat", "plot_background": True, - "plot_background_grid": True, - "plot_background_color": (234,234,242), - "plot_background_grid_color": 255, + "plot_grid": True, + "plot_background_color": (234, 234, 242), + "plot_grid_color": 255, "line_color": 200, "strong_color": (107, 107, 255), - "light_color": (235, 235, 255) + "light_color": (235, 235, 255), + "type": "normal", + "kind": "rect", + "hist_color": "b", + "hist_color_invert": False, } + + check_params(params, options, "scatter chart") options.update(params) hapi.stroke_size(2) hapi.font_size(15) - # --- plot background --- if options["plot_background"]: @@ -324,46 +370,253 @@ def scatterchart(hapi, x, y, w, h, params): hapi.fill(options["plot_background_color"]) hapi.rect(x, y, w, h) - # --- plot background grid --- + # --- mouse line --- + + if options["mouse_line"]: + hapi.stroke_size(2) + hapi.stroke(options["mouse_line_color"]) + limit_y = hapi.mouseY() + limit_x = hapi.mouseX() + if limit_y < y: + limit_y = y + elif limit_y > y + h: + limit_y = y + h + if limit_x < x: + limit_x = x + elif limit_x > x + w: + limit_x = x + w + + hapi.line(x, limit_y, x + w, limit_y) + hapi.line(limit_x, y, limit_x, y + h) + + # --- scatter --- + + xvals = options["data"][options["x"]] + yvals = options["data"][options["y"]] + # print(xvals[:10], yvals[:10]) + # hapi.set_alpha(100) + + if options["hue"]: + strong_blue = options["strong_color"] + light_blue = options["light_color"] + strong_blue_hls = hapi.rgb_to_hls(*strong_blue) + light_blue_hls = hapi.rgb_to_hls(*light_blue) + l_strong = strong_blue_hls[1] + l_light = light_blue_hls[1] + + delta_l = l_strong - l_light + steps = len(options["hue_order"]) + delta_change = delta_l / steps + + c_map = {} + + color_l = l_strong + + legend_y = y + 10 + hapi.fill(0) + hapi.text(options["hue"], x + w + 10, y) + for c in options["hue_order"]: + color_hue = hapi.hls_to_rgb(strong_blue_hls[0], color_l, strong_blue_hls[2]) + c_map[c] = color_hue + color_l += delta_change + + # --- legend + + # --- --- hue + + hapi.fill(color_hue) + hapi.ellipse(x + w + 10, legend_y, 10, 10) + hapi.text(c, x + w + 10 + 15, legend_y) + legend_y += 10 + + hue = options["hue"] + + # --- --- size + + if options["size"]: + min_size = min(options["data"][options["size"]]) + max_size = max(options["data"][options["size"]]) + + hapi.fill(0) + legend_y += 10 + hapi.text(options["size"], x + w + 10, legend_y) + legend_y += 10 + + d_size = (max_size - min_size) / 5 + start_size = min_size + ellipse_size = 0 + for i in range(5): + ellipse_size = i + 1 + hapi.ellipse(x + w + 10, legend_y, ellipse_size, ellipse_size) + hapi.text(round(start_size, 2), x + w + 15, legend_y) + start_size += d_size + legend_y += 10 + + ellipse_size += 1 + hapi.ellipse(x + w + 10, legend_y, ellipse_size, ellipse_size) + hapi.text(round(start_size, 2), x + w + 15, legend_y) + + # /--- legend + + # --- actual plotting + + if options["type"] == "normal": + for i, vx in enumerate(xvals): + if options["hue"]: + current_hue = options["data"][options["hue"]][i] + current_color = c_map[current_hue] + else: + current_color = options["graph_color"] + + hapi.fill(current_color) + + ex = hapi.constrain( + vx, + options["range_x"][0], + options["range_x"][1], + x, + x + w, + ) + ey = ( + y + + h + - hapi.constrain( + yvals[i], + options["range_y"][0], + options["range_y"][1], + y, + y + h, + ) + + y + ) + if options["size"]: + size = options["data"][options["size"]][i] + ellipse_size = (size / max_size) * 5 + else: + ellipse_size = 5 + hapi.ellipse(ex, ey, ellipse_size, ellipse_size) + elif options["type"] == "hist": + + range_x = ( + max(options["data"][options["x"]]) - min(options["data"][options["x"]]) + ) // options["ticks_x"] + range_y = ( + max(options["data"][options["y"]]) - min(options["data"][options["y"]]) + ) // options["ticks_y"] + + m = {} + + # for xi in range(options['ticks_x']+1): + # for yi in range(options['ticks_y']+1): + # m[str(xi*range_x)+'-'+str(yi*range_y)] = 0 + + total = 0 + for x_point, y_point in zip( + options["data"][options["x"]], options["data"][options["y"]] + ): + xi = x_point / ( + (options["range_x"][1] - options["range_x"][0]) / options["ticks_x"] + ) + yi = y_point / ( + (options["range_y"][1] - options["range_y"][0]) / options["ticks_y"] + ) + + yi = options["ticks_y"] - yi + + xi = int(xi) + yi = int(yi) + + if (xi, yi) not in m: + m[(xi, yi)] = 1 + total += 1 + else: + m[(xi, yi)] += 1 + total += 1 + + li = [[k, m[k]] for k in m] + + sorted_li = sorted(li, key=lambda l: l[1], reverse=True) + + rect_w = w // options["ticks_x"] + rect_h = h // options["ticks_y"] + + max_key = max(m, key=m.get) + max_val = m[max_key] + for xi in range(options["ticks_x"]): + for yi in range(options["ticks_y"]): + if (xi, yi) in m: + + if options["hist_color_invert"]: + col = (m[(xi, yi)] / max_val) * 255 + else: + col = (((max_val - m[(xi, yi)]) / max_val)) * 255 + + check("scatterchart", "hist_color", options["hist_color"]) + if options["hist_color"] == "r": + col_rect = (int(col), 0, 0) + if options["hist_color"] == "g": + col_rect = (0, int(col), 0) + if options["hist_color"] == "b": + col_rect = (0, 0, int(col)) + hapi.fill(col_rect) + else: + hapi.fill(255) + + if options["kind"] == "rect": + hapi.rect(x + xi * rect_w, y + yi * rect_h, rect_w, rect_h) + if options["kind"] == "round": + hapi.ellipse(x + xi * rect_w, y + yi * rect_h, rect_w, rect_h) + elif options["kind"] == "hex": + err = 1.8 * rect_w + if yi % 2 == 0: + hapi.regular_polygon( + x + xi * rect_w, y + yi * rect_h, rect_w - err, rect_h, 6 + ) + else: + hapi.regular_polygon( + x + xi * rect_w + (err // 4), + y + yi * rect_h, + rect_w - err, + rect_h, + 6, + ) + + # --- plot grid --- hapi.stroke_size(1) - if options["plot_background_grid"]: + if options["plot_grid"]: - hapi.stroke(options["plot_background_grid_color"]) + hapi.stroke(options["plot_grid_color"]) # --- vertical - bg_grid_x1 = x + bg_grid_x1 = x bg_grid_y1 = y - bg_grid_x2 = x + bg_grid_x2 = x bg_grid_y2 = y + h line_num = 1 - while(bg_grid_x1 <= x+w): - if (1 < line_num < options['ticks_x']+1): + while bg_grid_x1 <= x + w: + if 1 < line_num < options["ticks_x"] + 1: hapi.line(bg_grid_x1, bg_grid_y1, bg_grid_x2, bg_grid_y2) - bg_grid_x1 += (w // options['ticks_x']) - bg_grid_x2 += (w // options['ticks_x']) + bg_grid_x1 += w // options["ticks_x"] + bg_grid_x2 += w // options["ticks_x"] line_num += 1 - # --- horizontal - bg_grid_x1 = x + bg_grid_x1 = x bg_grid_y1 = y - bg_grid_x2 = x + w + bg_grid_x2 = x + w bg_grid_y2 = y line_num = 1 - while(bg_grid_y1 <= y+h): - if (1 < line_num < options['ticks_y']+1): + while bg_grid_y1 <= y + h: + if 1 < line_num < options["ticks_y"] + 1: hapi.line(bg_grid_x1, bg_grid_y1, bg_grid_x2, bg_grid_y2) - bg_grid_y1 += (h // options['ticks_y']) - bg_grid_y2 += (h // options['ticks_y']) + bg_grid_y1 += h // options["ticks_y"] + bg_grid_y2 += h // options["ticks_y"] line_num += 1 - - # --- axes --- - if options["show_axes"]: hapi.stroke(options["line_color"]) hapi.line(x, y, x, y + h) @@ -371,16 +624,16 @@ def scatterchart(hapi, x, y, w, h, params): hapi.fill(options["text_color"]) - # --- axes labels hapi.push_matrix() hapi.rotate(90) - hapi.text(options["y"], x-35, y + (h-(hapi._font.size(options["y"])[0])//2)) + hapi.text(options["y"], x - 35, y + (h - (hapi._font.size(options["y"])[0]) // 2)) hapi.pop_matrix() - hapi.text(options["x"], x + (w-(hapi._font.size(options["x"])[0])//2), y+h+35) - + hapi.text( + options["x"], x + (w - (hapi._font.size(options["x"])[0]) // 2), y + h + 35 + ) # --- axes @@ -411,122 +664,6 @@ def scatterchart(hapi, x, y, w, h, params): hapi.rotate(270) hapi.text(round(x_val, 2), x + (t * (w // options["ticks_x"])), y + h + 5) hapi.pop_matrix() - x_val += ((options["range_x"][1] - options["range_x"][0]) / options["ticks_x"]) + x_val += (options["range_x"][1] - options["range_x"][0]) / options["ticks_x"] # print(x_val, ((options["range_x"][1] - options["range_x"][0]) / options["ticks_x"])) - - - # --- mouse line --- - - if options["mouse_line"]: - hapi.stroke_size(2) - hapi.stroke(options["mouse_line_color"]) - limit_y = hapi.mouseY() - limit_x = hapi.mouseX() - if limit_y < y: - limit_y = y - elif limit_y > y + h: - limit_y = y + h - if limit_x < x: - limit_x = x - elif limit_x > x + w: - limit_x = x + w - - hapi.line(x, limit_y, x + w, limit_y) - hapi.line(limit_x, y, limit_x, y + h) - - - # --- scatter --- - - xvals = options['data'][options['x']] - yvals = options['data'][options['y']] - # print(xvals[:10], yvals[:10]) - # hapi.set_alpha(100) - - strong_blue = options['strong_color'] - light_blue = options['light_color'] - strong_blue_hls = hapi.rgb_to_hls(*strong_blue) - light_blue_hls = hapi.rgb_to_hls(*light_blue) - l_strong = strong_blue_hls[1] - l_light = light_blue_hls[1] - - delta_l = l_strong - l_light - steps = len(options["hue_order"]) - delta_change = delta_l / steps - - c_map = {} - - color_l = l_strong - - legend_y = y+10 - - hapi.fill(0) - hapi.text(options['hue'], x+w+10, y) - - for c in options['hue_order']: - color_hue = hapi.hls_to_rgb(strong_blue_hls[0], color_l, strong_blue_hls[2]) - c_map[c] = color_hue - color_l += delta_change - - # --- legend - - # --- --- hue - hapi.fill(color_hue) - hapi.ellipse(x+w+10, legend_y, 10, 10) - hapi.text(c, x+w+10+15, legend_y) - legend_y += 10 - - - hue = options['hue'] - - min_size = min(options['data'][options['size']]) - max_size = max(options['data'][options['size']]) - - - # --- --- size - hapi.fill(0) - legend_y += 10 - hapi.text(options['size'], x+w+10, legend_y) - legend_y += 10 - - d_size = (max_size - min_size)/5 - start_size = min_size - ellipse_size = 0 - for i in range(5): - ellipse_size = i+1 - hapi.ellipse(x+w+10, legend_y, ellipse_size, ellipse_size) - hapi.text(round(start_size, 2), x+w+15, legend_y) - start_size += d_size - legend_y += 10 - - ellipse_size += 1 - hapi.ellipse(x+w+10, legend_y, ellipse_size, ellipse_size) - hapi.text(round(start_size, 2), x+w+15, legend_y) - - # /--- legend - - - for i, vx in enumerate(xvals): - current_hue = options['data'][options['hue']][i] - current_color = c_map[current_hue] - - hapi.fill(current_color) - depth = options['data'][options['size']][i] - ex = hapi.constrain( - vx, - options["range_x"][0], - options["range_x"][1], - x, - x + w, - ) - ey = y + h -hapi.constrain( - yvals[i], - options["range_y"][0], - options["range_y"][1], - y, - y + h, - ) + y - - # print(ex, ey, vx, yvals[i]) - hapi.ellipse(ex, ey, (depth/max_size)*5, (depth/max_size)*5) - # print('---') \ No newline at end of file diff --git a/hooman/check.py b/hooman/check.py new file mode 100644 index 0000000..1e2f5ad --- /dev/null +++ b/hooman/check.py @@ -0,0 +1,129 @@ +import sys +from .result import Result +import inspect +import traceback + + +def check_scatter_chart(option, value): + if option == "hist_color": + if value not in ["r", "g", "b"]: + raise Exception('"hist_color" takes only values r or g or b') + + +def check(function, option, value): + if function == "scatterchart": + check_scatter_chart(option, value) + + +def check_params(params, options, funcname): + for k in params: + if k not in options: + keys_list = ", ".join(list(options.keys())) + sys.exit( + f'Option "{k}" for {funcname} is not valid, available options: ' + + keys_list + ) + + +def check_color(col): + if isinstance(col, int) or isinstance(col, float): + return Result.Ok((col, col, col)) + elif isinstance(col, list) or isinstance(col, tuple): + if not len(col) in [1, 3]: + return Result.Fail("Fill takes only 1 or 3 parameters when using a list") + if len(col) == 1: + return Result.Ok((col[0], col[0], col[0])) + else: + return Result.Ok((col[0], col[1], col[2])) + else: + return Result.Fail("Fill should be of type int, float, list or tuple") + + +def check_value(val, type_, value_name, range_=None): + if not isinstance(value_name, str): + return Result.Fail("Value name should be of type string") + else: + if not isinstance(type_, list): + if not isinstance(val, type_): + return Result.Fail( + f'Value "{val}" for {value_name} should be of type {type_}' + ) + if type_ in [int, float]: + if type(range_) in [list, tuple]: + if len(range_) == 2: + if not range_[0] < val < range_[1]: + return Result.Fail( + f"Value of {value_name} should be between {range_[0]} and {range_[1]}" + ) + elif len(range_) == 0: + pass + else: + return Result.Fail("Range takes [n1, n2] or []") + else: + return Result.Fail("Range should be of type list or tuple") + elif isinstance(type_, list): + results = [] + for t in type_: + if not isinstance(val, t): + results.append(0) + else: + results.append(1) + if not any(results): + return Result.Fail( + f'Value "{val}" for {value_name} should be of type {type_}' + ) + + return Result.Ok(val) + + +def verify_func_param(method, param_types, locals_): + try: + param_names = inspect.getfullargspec(method)[0] + for param in param_names: + if param not in ["self"]: + verif = check_value( + locals_[param], + param_types[param][0], + param, + range_=param_types[param][1], + ) + if not verif.success: + raise Exception(verif.error) + except Exception as e: + filename = traceback.extract_stack()[0].filename + line = traceback.extract_stack()[0].line + lineno = traceback.extract_stack()[0].lineno + print( + "Hooman", + type(e).__name__, # TypeError + "in file", + filename, # /tmp/example.py + "at line", + lineno, # 2 + ) + print(">>>", line) + print(str(e)) + sys.exit() + + +def verify_color(colors=[]): + for color in colors: + try: + verif = check_color(color) + if not verif.success: + raise Exception(verif.error) + except Exception as e: + filename = traceback.extract_stack()[0].filename + line = traceback.extract_stack()[0].line + lineno = traceback.extract_stack()[0].lineno + print( + "Hooman", + type(e).__name__, # TypeError + "in file", + filename, # /tmp/example.py + "at line", + lineno, # 2 + ) + print(">>>", line) + print(str(e)) + sys.exit() diff --git a/hooman/demos/Buttons.py b/hooman/demos/Buttons.py index 5cb3e6c..df9bbec 100644 --- a/hooman/demos/Buttons.py +++ b/hooman/demos/Buttons.py @@ -49,7 +49,8 @@ def button_hover_exit(this): button2 = hapi.button( 150, 250, - 100, 50, + 100, + 50, "No Click Me", { "background_color": (200, 200, 200), @@ -100,4 +101,4 @@ def handle_events(event): clock.tick(60) -pygame.quit() \ No newline at end of file +pygame.quit() diff --git a/hooman/demos/Template.py b/hooman/demos/Template.py index d8bde2c..632b862 100644 --- a/hooman/demos/Template.py +++ b/hooman/demos/Template.py @@ -1,4 +1,4 @@ -from hooman import Hooman # import hooman +from hooman import Hooman # import hooman # create the hooman api (hapi) @@ -12,9 +12,8 @@ # this is the game loop # everything in here occurs once every frame - #set the background to white, this should always be the first thing you draw to the screen - hapi.background(hapi.color['white']) - + # set the background to white, this should always be the first thing you draw to the screen + hapi.background(hapi.color["white"]) # get any new events like a mouse click or a key press, this handles the events # and should only be called once every frame diff --git a/hooman/demos/data/penguins.csv b/hooman/demos/data/penguins.csv new file mode 100644 index 0000000..51fd0fe --- /dev/null +++ b/hooman/demos/data/penguins.csv @@ -0,0 +1,345 @@ +species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g,sex +Adelie,Torgersen,39.1,18.7,181,3750,MALE +Adelie,Torgersen,39.5,17.4,186,3800,FEMALE +Adelie,Torgersen,40.3,18,195,3250,FEMALE +Adelie,Torgersen,,,,, +Adelie,Torgersen,36.7,19.3,193,3450,FEMALE +Adelie,Torgersen,39.3,20.6,190,3650,MALE +Adelie,Torgersen,38.9,17.8,181,3625,FEMALE +Adelie,Torgersen,39.2,19.6,195,4675,MALE +Adelie,Torgersen,34.1,18.1,193,3475, +Adelie,Torgersen,42,20.2,190,4250, +Adelie,Torgersen,37.8,17.1,186,3300, +Adelie,Torgersen,37.8,17.3,180,3700, +Adelie,Torgersen,41.1,17.6,182,3200,FEMALE +Adelie,Torgersen,38.6,21.2,191,3800,MALE +Adelie,Torgersen,34.6,21.1,198,4400,MALE +Adelie,Torgersen,36.6,17.8,185,3700,FEMALE +Adelie,Torgersen,38.7,19,195,3450,FEMALE +Adelie,Torgersen,42.5,20.7,197,4500,MALE +Adelie,Torgersen,34.4,18.4,184,3325,FEMALE +Adelie,Torgersen,46,21.5,194,4200,MALE +Adelie,Biscoe,37.8,18.3,174,3400,FEMALE +Adelie,Biscoe,37.7,18.7,180,3600,MALE +Adelie,Biscoe,35.9,19.2,189,3800,FEMALE +Adelie,Biscoe,38.2,18.1,185,3950,MALE +Adelie,Biscoe,38.8,17.2,180,3800,MALE +Adelie,Biscoe,35.3,18.9,187,3800,FEMALE +Adelie,Biscoe,40.6,18.6,183,3550,MALE +Adelie,Biscoe,40.5,17.9,187,3200,FEMALE +Adelie,Biscoe,37.9,18.6,172,3150,FEMALE +Adelie,Biscoe,40.5,18.9,180,3950,MALE +Adelie,Dream,39.5,16.7,178,3250,FEMALE +Adelie,Dream,37.2,18.1,178,3900,MALE +Adelie,Dream,39.5,17.8,188,3300,FEMALE +Adelie,Dream,40.9,18.9,184,3900,MALE +Adelie,Dream,36.4,17,195,3325,FEMALE +Adelie,Dream,39.2,21.1,196,4150,MALE +Adelie,Dream,38.8,20,190,3950,MALE +Adelie,Dream,42.2,18.5,180,3550,FEMALE +Adelie,Dream,37.6,19.3,181,3300,FEMALE +Adelie,Dream,39.8,19.1,184,4650,MALE +Adelie,Dream,36.5,18,182,3150,FEMALE +Adelie,Dream,40.8,18.4,195,3900,MALE +Adelie,Dream,36,18.5,186,3100,FEMALE +Adelie,Dream,44.1,19.7,196,4400,MALE +Adelie,Dream,37,16.9,185,3000,FEMALE +Adelie,Dream,39.6,18.8,190,4600,MALE +Adelie,Dream,41.1,19,182,3425,MALE +Adelie,Dream,37.5,18.9,179,2975, +Adelie,Dream,36,17.9,190,3450,FEMALE +Adelie,Dream,42.3,21.2,191,4150,MALE +Adelie,Biscoe,39.6,17.7,186,3500,FEMALE +Adelie,Biscoe,40.1,18.9,188,4300,MALE +Adelie,Biscoe,35,17.9,190,3450,FEMALE +Adelie,Biscoe,42,19.5,200,4050,MALE +Adelie,Biscoe,34.5,18.1,187,2900,FEMALE +Adelie,Biscoe,41.4,18.6,191,3700,MALE +Adelie,Biscoe,39,17.5,186,3550,FEMALE +Adelie,Biscoe,40.6,18.8,193,3800,MALE +Adelie,Biscoe,36.5,16.6,181,2850,FEMALE +Adelie,Biscoe,37.6,19.1,194,3750,MALE +Adelie,Biscoe,35.7,16.9,185,3150,FEMALE +Adelie,Biscoe,41.3,21.1,195,4400,MALE +Adelie,Biscoe,37.6,17,185,3600,FEMALE +Adelie,Biscoe,41.1,18.2,192,4050,MALE +Adelie,Biscoe,36.4,17.1,184,2850,FEMALE +Adelie,Biscoe,41.6,18,192,3950,MALE +Adelie,Biscoe,35.5,16.2,195,3350,FEMALE +Adelie,Biscoe,41.1,19.1,188,4100,MALE +Adelie,Torgersen,35.9,16.6,190,3050,FEMALE +Adelie,Torgersen,41.8,19.4,198,4450,MALE +Adelie,Torgersen,33.5,19,190,3600,FEMALE +Adelie,Torgersen,39.7,18.4,190,3900,MALE +Adelie,Torgersen,39.6,17.2,196,3550,FEMALE +Adelie,Torgersen,45.8,18.9,197,4150,MALE +Adelie,Torgersen,35.5,17.5,190,3700,FEMALE +Adelie,Torgersen,42.8,18.5,195,4250,MALE +Adelie,Torgersen,40.9,16.8,191,3700,FEMALE +Adelie,Torgersen,37.2,19.4,184,3900,MALE +Adelie,Torgersen,36.2,16.1,187,3550,FEMALE +Adelie,Torgersen,42.1,19.1,195,4000,MALE +Adelie,Torgersen,34.6,17.2,189,3200,FEMALE +Adelie,Torgersen,42.9,17.6,196,4700,MALE +Adelie,Torgersen,36.7,18.8,187,3800,FEMALE +Adelie,Torgersen,35.1,19.4,193,4200,MALE +Adelie,Dream,37.3,17.8,191,3350,FEMALE +Adelie,Dream,41.3,20.3,194,3550,MALE +Adelie,Dream,36.3,19.5,190,3800,MALE +Adelie,Dream,36.9,18.6,189,3500,FEMALE +Adelie,Dream,38.3,19.2,189,3950,MALE +Adelie,Dream,38.9,18.8,190,3600,FEMALE +Adelie,Dream,35.7,18,202,3550,FEMALE +Adelie,Dream,41.1,18.1,205,4300,MALE +Adelie,Dream,34,17.1,185,3400,FEMALE +Adelie,Dream,39.6,18.1,186,4450,MALE +Adelie,Dream,36.2,17.3,187,3300,FEMALE +Adelie,Dream,40.8,18.9,208,4300,MALE +Adelie,Dream,38.1,18.6,190,3700,FEMALE +Adelie,Dream,40.3,18.5,196,4350,MALE +Adelie,Dream,33.1,16.1,178,2900,FEMALE +Adelie,Dream,43.2,18.5,192,4100,MALE +Adelie,Biscoe,35,17.9,192,3725,FEMALE +Adelie,Biscoe,41,20,203,4725,MALE +Adelie,Biscoe,37.7,16,183,3075,FEMALE +Adelie,Biscoe,37.8,20,190,4250,MALE +Adelie,Biscoe,37.9,18.6,193,2925,FEMALE +Adelie,Biscoe,39.7,18.9,184,3550,MALE +Adelie,Biscoe,38.6,17.2,199,3750,FEMALE +Adelie,Biscoe,38.2,20,190,3900,MALE +Adelie,Biscoe,38.1,17,181,3175,FEMALE +Adelie,Biscoe,43.2,19,197,4775,MALE +Adelie,Biscoe,38.1,16.5,198,3825,FEMALE +Adelie,Biscoe,45.6,20.3,191,4600,MALE +Adelie,Biscoe,39.7,17.7,193,3200,FEMALE +Adelie,Biscoe,42.2,19.5,197,4275,MALE +Adelie,Biscoe,39.6,20.7,191,3900,FEMALE +Adelie,Biscoe,42.7,18.3,196,4075,MALE +Adelie,Torgersen,38.6,17,188,2900,FEMALE +Adelie,Torgersen,37.3,20.5,199,3775,MALE +Adelie,Torgersen,35.7,17,189,3350,FEMALE +Adelie,Torgersen,41.1,18.6,189,3325,MALE +Adelie,Torgersen,36.2,17.2,187,3150,FEMALE +Adelie,Torgersen,37.7,19.8,198,3500,MALE +Adelie,Torgersen,40.2,17,176,3450,FEMALE +Adelie,Torgersen,41.4,18.5,202,3875,MALE +Adelie,Torgersen,35.2,15.9,186,3050,FEMALE +Adelie,Torgersen,40.6,19,199,4000,MALE +Adelie,Torgersen,38.8,17.6,191,3275,FEMALE +Adelie,Torgersen,41.5,18.3,195,4300,MALE +Adelie,Torgersen,39,17.1,191,3050,FEMALE +Adelie,Torgersen,44.1,18,210,4000,MALE +Adelie,Torgersen,38.5,17.9,190,3325,FEMALE +Adelie,Torgersen,43.1,19.2,197,3500,MALE +Adelie,Dream,36.8,18.5,193,3500,FEMALE +Adelie,Dream,37.5,18.5,199,4475,MALE +Adelie,Dream,38.1,17.6,187,3425,FEMALE +Adelie,Dream,41.1,17.5,190,3900,MALE +Adelie,Dream,35.6,17.5,191,3175,FEMALE +Adelie,Dream,40.2,20.1,200,3975,MALE +Adelie,Dream,37,16.5,185,3400,FEMALE +Adelie,Dream,39.7,17.9,193,4250,MALE +Adelie,Dream,40.2,17.1,193,3400,FEMALE +Adelie,Dream,40.6,17.2,187,3475,MALE +Adelie,Dream,32.1,15.5,188,3050,FEMALE +Adelie,Dream,40.7,17,190,3725,MALE +Adelie,Dream,37.3,16.8,192,3000,FEMALE +Adelie,Dream,39,18.7,185,3650,MALE +Adelie,Dream,39.2,18.6,190,4250,MALE +Adelie,Dream,36.6,18.4,184,3475,FEMALE +Adelie,Dream,36,17.8,195,3450,FEMALE +Adelie,Dream,37.8,18.1,193,3750,MALE +Adelie,Dream,36,17.1,187,3700,FEMALE +Adelie,Dream,41.5,18.5,201,4000,MALE +Chinstrap,Dream,46.5,17.9,192,3500,FEMALE +Chinstrap,Dream,50,19.5,196,3900,MALE +Chinstrap,Dream,51.3,19.2,193,3650,MALE +Chinstrap,Dream,45.4,18.7,188,3525,FEMALE +Chinstrap,Dream,52.7,19.8,197,3725,MALE +Chinstrap,Dream,45.2,17.8,198,3950,FEMALE +Chinstrap,Dream,46.1,18.2,178,3250,FEMALE +Chinstrap,Dream,51.3,18.2,197,3750,MALE +Chinstrap,Dream,46,18.9,195,4150,FEMALE +Chinstrap,Dream,51.3,19.9,198,3700,MALE +Chinstrap,Dream,46.6,17.8,193,3800,FEMALE +Chinstrap,Dream,51.7,20.3,194,3775,MALE +Chinstrap,Dream,47,17.3,185,3700,FEMALE +Chinstrap,Dream,52,18.1,201,4050,MALE +Chinstrap,Dream,45.9,17.1,190,3575,FEMALE +Chinstrap,Dream,50.5,19.6,201,4050,MALE +Chinstrap,Dream,50.3,20,197,3300,MALE +Chinstrap,Dream,58,17.8,181,3700,FEMALE +Chinstrap,Dream,46.4,18.6,190,3450,FEMALE +Chinstrap,Dream,49.2,18.2,195,4400,MALE +Chinstrap,Dream,42.4,17.3,181,3600,FEMALE +Chinstrap,Dream,48.5,17.5,191,3400,MALE +Chinstrap,Dream,43.2,16.6,187,2900,FEMALE +Chinstrap,Dream,50.6,19.4,193,3800,MALE +Chinstrap,Dream,46.7,17.9,195,3300,FEMALE +Chinstrap,Dream,52,19,197,4150,MALE +Chinstrap,Dream,50.5,18.4,200,3400,FEMALE +Chinstrap,Dream,49.5,19,200,3800,MALE +Chinstrap,Dream,46.4,17.8,191,3700,FEMALE +Chinstrap,Dream,52.8,20,205,4550,MALE +Chinstrap,Dream,40.9,16.6,187,3200,FEMALE +Chinstrap,Dream,54.2,20.8,201,4300,MALE +Chinstrap,Dream,42.5,16.7,187,3350,FEMALE +Chinstrap,Dream,51,18.8,203,4100,MALE +Chinstrap,Dream,49.7,18.6,195,3600,MALE +Chinstrap,Dream,47.5,16.8,199,3900,FEMALE +Chinstrap,Dream,47.6,18.3,195,3850,FEMALE +Chinstrap,Dream,52,20.7,210,4800,MALE +Chinstrap,Dream,46.9,16.6,192,2700,FEMALE +Chinstrap,Dream,53.5,19.9,205,4500,MALE +Chinstrap,Dream,49,19.5,210,3950,MALE +Chinstrap,Dream,46.2,17.5,187,3650,FEMALE +Chinstrap,Dream,50.9,19.1,196,3550,MALE +Chinstrap,Dream,45.5,17,196,3500,FEMALE +Chinstrap,Dream,50.9,17.9,196,3675,FEMALE +Chinstrap,Dream,50.8,18.5,201,4450,MALE +Chinstrap,Dream,50.1,17.9,190,3400,FEMALE +Chinstrap,Dream,49,19.6,212,4300,MALE +Chinstrap,Dream,51.5,18.7,187,3250,MALE +Chinstrap,Dream,49.8,17.3,198,3675,FEMALE +Chinstrap,Dream,48.1,16.4,199,3325,FEMALE +Chinstrap,Dream,51.4,19,201,3950,MALE +Chinstrap,Dream,45.7,17.3,193,3600,FEMALE +Chinstrap,Dream,50.7,19.7,203,4050,MALE +Chinstrap,Dream,42.5,17.3,187,3350,FEMALE +Chinstrap,Dream,52.2,18.8,197,3450,MALE +Chinstrap,Dream,45.2,16.6,191,3250,FEMALE +Chinstrap,Dream,49.3,19.9,203,4050,MALE +Chinstrap,Dream,50.2,18.8,202,3800,MALE +Chinstrap,Dream,45.6,19.4,194,3525,FEMALE +Chinstrap,Dream,51.9,19.5,206,3950,MALE +Chinstrap,Dream,46.8,16.5,189,3650,FEMALE +Chinstrap,Dream,45.7,17,195,3650,FEMALE +Chinstrap,Dream,55.8,19.8,207,4000,MALE +Chinstrap,Dream,43.5,18.1,202,3400,FEMALE +Chinstrap,Dream,49.6,18.2,193,3775,MALE +Chinstrap,Dream,50.8,19,210,4100,MALE +Chinstrap,Dream,50.2,18.7,198,3775,FEMALE +Gentoo,Biscoe,46.1,13.2,211,4500,FEMALE +Gentoo,Biscoe,50,16.3,230,5700,MALE +Gentoo,Biscoe,48.7,14.1,210,4450,FEMALE +Gentoo,Biscoe,50,15.2,218,5700,MALE +Gentoo,Biscoe,47.6,14.5,215,5400,MALE +Gentoo,Biscoe,46.5,13.5,210,4550,FEMALE +Gentoo,Biscoe,45.4,14.6,211,4800,FEMALE +Gentoo,Biscoe,46.7,15.3,219,5200,MALE +Gentoo,Biscoe,43.3,13.4,209,4400,FEMALE +Gentoo,Biscoe,46.8,15.4,215,5150,MALE +Gentoo,Biscoe,40.9,13.7,214,4650,FEMALE +Gentoo,Biscoe,49,16.1,216,5550,MALE +Gentoo,Biscoe,45.5,13.7,214,4650,FEMALE +Gentoo,Biscoe,48.4,14.6,213,5850,MALE +Gentoo,Biscoe,45.8,14.6,210,4200,FEMALE +Gentoo,Biscoe,49.3,15.7,217,5850,MALE +Gentoo,Biscoe,42,13.5,210,4150,FEMALE +Gentoo,Biscoe,49.2,15.2,221,6300,MALE +Gentoo,Biscoe,46.2,14.5,209,4800,FEMALE +Gentoo,Biscoe,48.7,15.1,222,5350,MALE +Gentoo,Biscoe,50.2,14.3,218,5700,MALE +Gentoo,Biscoe,45.1,14.5,215,5000,FEMALE +Gentoo,Biscoe,46.5,14.5,213,4400,FEMALE +Gentoo,Biscoe,46.3,15.8,215,5050,MALE +Gentoo,Biscoe,42.9,13.1,215,5000,FEMALE +Gentoo,Biscoe,46.1,15.1,215,5100,MALE +Gentoo,Biscoe,44.5,14.3,216,4100, +Gentoo,Biscoe,47.8,15,215,5650,MALE +Gentoo,Biscoe,48.2,14.3,210,4600,FEMALE +Gentoo,Biscoe,50,15.3,220,5550,MALE +Gentoo,Biscoe,47.3,15.3,222,5250,MALE +Gentoo,Biscoe,42.8,14.2,209,4700,FEMALE +Gentoo,Biscoe,45.1,14.5,207,5050,FEMALE +Gentoo,Biscoe,59.6,17,230,6050,MALE +Gentoo,Biscoe,49.1,14.8,220,5150,FEMALE +Gentoo,Biscoe,48.4,16.3,220,5400,MALE +Gentoo,Biscoe,42.6,13.7,213,4950,FEMALE +Gentoo,Biscoe,44.4,17.3,219,5250,MALE +Gentoo,Biscoe,44,13.6,208,4350,FEMALE +Gentoo,Biscoe,48.7,15.7,208,5350,MALE +Gentoo,Biscoe,42.7,13.7,208,3950,FEMALE +Gentoo,Biscoe,49.6,16,225,5700,MALE +Gentoo,Biscoe,45.3,13.7,210,4300,FEMALE +Gentoo,Biscoe,49.6,15,216,4750,MALE +Gentoo,Biscoe,50.5,15.9,222,5550,MALE +Gentoo,Biscoe,43.6,13.9,217,4900,FEMALE +Gentoo,Biscoe,45.5,13.9,210,4200,FEMALE +Gentoo,Biscoe,50.5,15.9,225,5400,MALE +Gentoo,Biscoe,44.9,13.3,213,5100,FEMALE +Gentoo,Biscoe,45.2,15.8,215,5300,MALE +Gentoo,Biscoe,46.6,14.2,210,4850,FEMALE +Gentoo,Biscoe,48.5,14.1,220,5300,MALE +Gentoo,Biscoe,45.1,14.4,210,4400,FEMALE +Gentoo,Biscoe,50.1,15,225,5000,MALE +Gentoo,Biscoe,46.5,14.4,217,4900,FEMALE +Gentoo,Biscoe,45,15.4,220,5050,MALE +Gentoo,Biscoe,43.8,13.9,208,4300,FEMALE +Gentoo,Biscoe,45.5,15,220,5000,MALE +Gentoo,Biscoe,43.2,14.5,208,4450,FEMALE +Gentoo,Biscoe,50.4,15.3,224,5550,MALE +Gentoo,Biscoe,45.3,13.8,208,4200,FEMALE +Gentoo,Biscoe,46.2,14.9,221,5300,MALE +Gentoo,Biscoe,45.7,13.9,214,4400,FEMALE +Gentoo,Biscoe,54.3,15.7,231,5650,MALE +Gentoo,Biscoe,45.8,14.2,219,4700,FEMALE +Gentoo,Biscoe,49.8,16.8,230,5700,MALE +Gentoo,Biscoe,46.2,14.4,214,4650, +Gentoo,Biscoe,49.5,16.2,229,5800,MALE +Gentoo,Biscoe,43.5,14.2,220,4700,FEMALE +Gentoo,Biscoe,50.7,15,223,5550,MALE +Gentoo,Biscoe,47.7,15,216,4750,FEMALE +Gentoo,Biscoe,46.4,15.6,221,5000,MALE +Gentoo,Biscoe,48.2,15.6,221,5100,MALE +Gentoo,Biscoe,46.5,14.8,217,5200,FEMALE +Gentoo,Biscoe,46.4,15,216,4700,FEMALE +Gentoo,Biscoe,48.6,16,230,5800,MALE +Gentoo,Biscoe,47.5,14.2,209,4600,FEMALE +Gentoo,Biscoe,51.1,16.3,220,6000,MALE +Gentoo,Biscoe,45.2,13.8,215,4750,FEMALE +Gentoo,Biscoe,45.2,16.4,223,5950,MALE +Gentoo,Biscoe,49.1,14.5,212,4625,FEMALE +Gentoo,Biscoe,52.5,15.6,221,5450,MALE +Gentoo,Biscoe,47.4,14.6,212,4725,FEMALE +Gentoo,Biscoe,50,15.9,224,5350,MALE +Gentoo,Biscoe,44.9,13.8,212,4750,FEMALE +Gentoo,Biscoe,50.8,17.3,228,5600,MALE +Gentoo,Biscoe,43.4,14.4,218,4600,FEMALE +Gentoo,Biscoe,51.3,14.2,218,5300,MALE +Gentoo,Biscoe,47.5,14,212,4875,FEMALE +Gentoo,Biscoe,52.1,17,230,5550,MALE +Gentoo,Biscoe,47.5,15,218,4950,FEMALE +Gentoo,Biscoe,52.2,17.1,228,5400,MALE +Gentoo,Biscoe,45.5,14.5,212,4750,FEMALE +Gentoo,Biscoe,49.5,16.1,224,5650,MALE +Gentoo,Biscoe,44.5,14.7,214,4850,FEMALE +Gentoo,Biscoe,50.8,15.7,226,5200,MALE +Gentoo,Biscoe,49.4,15.8,216,4925,MALE +Gentoo,Biscoe,46.9,14.6,222,4875,FEMALE +Gentoo,Biscoe,48.4,14.4,203,4625,FEMALE +Gentoo,Biscoe,51.1,16.5,225,5250,MALE +Gentoo,Biscoe,48.5,15,219,4850,FEMALE +Gentoo,Biscoe,55.9,17,228,5600,MALE +Gentoo,Biscoe,47.2,15.5,215,4975,FEMALE +Gentoo,Biscoe,49.1,15,228,5500,MALE +Gentoo,Biscoe,47.3,13.8,216,4725, +Gentoo,Biscoe,46.8,16.1,215,5500,MALE +Gentoo,Biscoe,41.7,14.7,210,4700,FEMALE +Gentoo,Biscoe,53.4,15.8,219,5500,MALE +Gentoo,Biscoe,43.3,14,208,4575,FEMALE +Gentoo,Biscoe,48.1,15.1,209,5500,MALE +Gentoo,Biscoe,50.5,15.2,216,5000,FEMALE +Gentoo,Biscoe,49.8,15.9,229,5950,MALE +Gentoo,Biscoe,43.5,15.2,213,4650,FEMALE +Gentoo,Biscoe,51.5,16.3,230,5500,MALE +Gentoo,Biscoe,46.2,14.1,217,4375,FEMALE +Gentoo,Biscoe,55.1,16,230,5850,MALE +Gentoo,Biscoe,44.5,15.7,217,4875, +Gentoo,Biscoe,48.8,16.2,222,6000,MALE +Gentoo,Biscoe,47.2,13.7,214,4925,FEMALE +Gentoo,Biscoe,,,,, +Gentoo,Biscoe,46.8,14.3,215,4850,FEMALE +Gentoo,Biscoe,50.4,15.7,222,5750,MALE +Gentoo,Biscoe,45.2,14.8,212,5200,FEMALE +Gentoo,Biscoe,49.9,16.1,213,5400,MALE diff --git a/hooman/demos/doughnut.py b/hooman/demos/doughnut.py index f1d27ea..721aeba 100644 --- a/hooman/demos/doughnut.py +++ b/hooman/demos/doughnut.py @@ -27,8 +27,15 @@ hapi.background(bg_col) hapi.fill(hapi.color["red"]) - hapi.fill_arc(hapi.center_x, hapi.center_y, 100, start_angle.value(), end_angle.value(), start_rad=30) - hapi.text('{}-{}'.format(start_angle.value(), end_angle.value()), 50, 450) + hapi.fill_arc( + hapi.center_x, + hapi.center_y, + 100, + start_angle.value(), + end_angle.value(), + start_rad=30, + ) + hapi.text("{}-{}".format(start_angle.value(), end_angle.value()), 50, 450) start_angle.update() end_angle.update() diff --git a/hooman/demos/eat_the_apple_game.py b/hooman/demos/eat_the_apple_game.py index a7bc04a..c522475 100644 --- a/hooman/demos/eat_the_apple_game.py +++ b/hooman/demos/eat_the_apple_game.py @@ -42,7 +42,7 @@ def handle_events(event): if hapi.mouseY() > (apple[1] + 24): hapi.fill(hapi.color["green"]) hapi.ellipse((hapi.mouseX() - 25), (hapi.mouseY() - 35), 50, 50) - + hapi.fill((255, 255, 255)) hapi.ellipse((hapi.mouseX() - 25), (hapi.mouseY() - 25), 50, 50) @@ -50,15 +50,28 @@ def handle_events(event): hapi.font_size(30) hapi.text(score, 5, 5) - if (hapi.mouseX() < (apple[0] + 24)) and (hapi.mouseX() > (apple[0] - 24)) and (hapi.mouseY() < (apple[1] + 24)) and (hapi.mouseY() > (apple[1] - 24)): + if ( + (hapi.mouseX() < (apple[0] + 24)) + and (hapi.mouseX() > (apple[0] - 24)) + and (hapi.mouseY() < (apple[1] + 24)) + and (hapi.mouseY() > (apple[1] - 24)) + ): hapi.fill(hapi.color["red"]) hapi.ellipse((apple[0] - 5), (apple[1] - 5), 10, 10) - if (hapi.mouseX() < (apple[0] + 9)) and (hapi.mouseX() > (apple[0] - 9)) and (hapi.mouseY() < (apple[1] + 9)) and (hapi.mouseY() > (apple[1] - 9)): - apple = (random.randint(10, (hapi.WIDTH - 10)), random.randint(10, (hapi.HEIGHT - 10))) + if ( + (hapi.mouseX() < (apple[0] + 9)) + and (hapi.mouseX() > (apple[0] - 9)) + and (hapi.mouseY() < (apple[1] + 9)) + and (hapi.mouseY() > (apple[1] - 9)) + ): + apple = ( + random.randint(10, (hapi.WIDTH - 10)), + random.randint(10, (hapi.HEIGHT - 10)), + ) score += 1 hapi.flip_display() hapi.event_loop() -pygame.quit() \ No newline at end of file +pygame.quit() 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/linegraph.py b/hooman/demos/linegraph.py index 76a3e07..60c2d98 100644 --- a/hooman/demos/linegraph.py +++ b/hooman/demos/linegraph.py @@ -5,19 +5,19 @@ mouse_x = { - "label": "mouse x", - "color": (0, 255, 0), - "data": [[1,1]], - "values_window": 200 - } + "label": "mouse x", + "color": (0, 255, 0), + "data": [[1, 1]], + "values_window": 200, +} mouse_y = { - "label": "mouse y", - "color": (255, 0, 0), - "data": [[1,1]], - "values_window": 200 - } + "label": "mouse y", + "color": (255, 0, 0), + "data": [[1, 1]], + "values_window": 200, +} time_unit = 0 @@ -34,7 +34,7 @@ def max_data(data, index): bg_col = (255, 255, 255) hapi.background(255) - max_range_y = max([max_data(mouse_y['data'], 1), max_data(mouse_x['data'], 1)]) + max_range_y = max([max_data(mouse_y["data"], 1), max_data(mouse_x["data"], 1)]) hapi.linechart( 40, @@ -42,24 +42,22 @@ def max_data(data, index): 300, 400, { - "lines":[ - mouse_x, - mouse_y], + "lines": [mouse_x, mouse_y], "mouse_line": False, "range_y": [0, max_range_y], - "range_x": [0, max_data(mouse_x['data'], 0)], + "range_x": [0, max_data(mouse_x["data"], 0)], "show_axes": False, "tick_size": 10, "show_ticks_x": False, "show_ticks_y": False, "x_axis_label": "mouse position", - "y_axis_label": "unit time" + "y_axis_label": "unit time", }, ) hapi.fill(hapi.color["blue"]) - mouse_x['data'].append([time_unit, hapi.mouseX()]) - mouse_y['data'].append([time_unit, hapi.mouseY()]) + mouse_x["data"].append([time_unit, hapi.mouseX()]) + mouse_y["data"].append([time_unit, hapi.mouseY()]) time_unit += 1 hapi.flip_display() diff --git a/hooman/demos/pie_chart_from_doughnut.py b/hooman/demos/pie_chart_from_doughnut.py index 943bd53..2c0dd3a 100644 --- a/hooman/demos/pie_chart_from_doughnut.py +++ b/hooman/demos/pie_chart_from_doughnut.py @@ -8,7 +8,6 @@ bg_col = (255, 255, 255) - while hapi.is_running: hapi.background(bg_col) diff --git a/hooman/demos/piechart.py b/hooman/demos/piechart.py index d2cc5d7..cd0054c 100644 --- a/hooman/demos/piechart.py +++ b/hooman/demos/piechart.py @@ -10,20 +10,31 @@ while hapi.is_running: hapi.background(bg_col) - hapi.piechart(hapi.center_x-100, hapi.center_y, 100, [ - ['a', 20, hapi.color['red']], - ['b', 30, hapi.color['blue']], - ['c', 40, hapi.color['yellow']], - ['d', 60, hapi.color['green']], - ['e', 30, hapi.color['black']] - ]) - hapi.piechart(hapi.center_x+100, hapi.center_y, 100, [ - ['a', 20, hapi.color['red']], - ['b', 30, hapi.color['blue']], - ['c', 40, hapi.color['yellow']], - ['d', 60, hapi.color['green']], - ['e', 30, hapi.color['black']] - ], start_rad=30) + hapi.piechart( + hapi.center_x - 100, + hapi.center_y, + 100, + [ + ["a", 20, hapi.color["red"]], + ["b", 30, hapi.color["blue"]], + ["c", 40, hapi.color["yellow"]], + ["d", 60, hapi.color["green"]], + ["e", 30, hapi.color["black"]], + ], + ) + hapi.piechart( + hapi.center_x + 100, + hapi.center_y, + 100, + [ + ["a", 20, hapi.color["red"]], + ["b", 30, hapi.color["blue"]], + ["c", 40, hapi.color["yellow"]], + ["d", 60, hapi.color["green"]], + ["e", 30, hapi.color["black"]], + ], + start_rad=30, + ) hapi.event_loop() hapi.flip_display() diff --git a/hooman/demos/scatter_chart.py b/hooman/demos/scatter_chart.py index 60e9c9b..554e469 100644 --- a/hooman/demos/scatter_chart.py +++ b/hooman/demos/scatter_chart.py @@ -11,45 +11,40 @@ base_path = os.path.dirname(os.path.abspath(__file__)) df = pd.read_csv(os.path.join(base_path, "data", "diamonds.csv")) -data = {k:list(df[k]) for k in df.columns.values.tolist()} +data = {k: list(df[k]) for k in df.columns.values.tolist()} hapi.background(255) -colx = 'carat' -coly = 'price' +colx = "carat" +coly = "price" clarity_ranking = ["I1", "SI2", "SI1", "VS2", "VS1", "VVS2", "VVS1", "IF"] hapi.scatterchart( - 40, - 30, - 500, - 500, - { + 40, + 30, + 500, + 500, + { "data": data, - "ticks_y": 6, - "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, - "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 - }, - ) + "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_grid": False, + "line_color": 200, + }, +) while hapi.is_running: bg_col = (255, 255, 255) - hapi.flip_display() hapi.event_loop() diff --git a/hooman/demos/scatter_chart_hist.py b/hooman/demos/scatter_chart_hist.py new file mode 100644 index 0000000..a191ca4 --- /dev/null +++ b/hooman/demos/scatter_chart_hist.py @@ -0,0 +1,51 @@ +# 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) + + +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", + }, +) + +while hapi.is_running: + bg_col = (255, 255, 255) + + hapi.flip_display() + hapi.event_loop() diff --git a/hooman/demos/scrolling_window.py b/hooman/demos/scrolling_window.py index ad15c15..5162df5 100644 --- a/hooman/demos/scrolling_window.py +++ b/hooman/demos/scrolling_window.py @@ -8,14 +8,14 @@ bg_col = (255, 255, 255) -scroll = hapi.scroll({'range_y': 400}) +scroll = hapi.scroll({"range_y": 400}) -slider = hapi.slider(100, 850, 300, 30, {'curve': 0.8}) +slider = hapi.slider(100, 850, 300, 30, {"curve": 0.8}) while hapi.is_running: hapi.background(bg_col) - hapi.fill(hapi.color['red']) + hapi.fill(hapi.color["red"]) dy = scroll[1] hapi.rect(100, 600 + dy, 100, 100) hapi.rect(200, 400 + dy, 100, 200) diff --git a/hooman/demos/snake.py b/hooman/demos/snake.py index 84d2a72..b3f8fbd 100644 --- a/hooman/demos/snake.py +++ b/hooman/demos/snake.py @@ -6,15 +6,19 @@ hapi = hooman.Hooman(WIDTH, HEIGHT) -btn_style = {'outline': True, - 'background_color': (255, 0, 0), - 'curve': 0.7, - 'outline_thickness': 3} +btn_style = { + "outline": True, + "background_color": (255, 0, 0), + "curve": 0.7, + "outline_thickness": 3, +} + +slider_style = { + "background_color": (200, 200, 200), + "slider_height": 60, + "slider_color": (240, 240, 240), +} -slider_style = {'background_color': (200, 200, 200), - 'slider_height': 60, - 'slider_color': (240, 240, 240) - } class Game: def __init__(self): @@ -23,28 +27,28 @@ def __init__(self): self.menu_btn_start = hapi.button(200, 200, 200, 50, "Start", btn_style) self.menu_btn_quit = hapi.button(200, 400, 200, 50, "Quit", btn_style) - self.settings_btn = hapi.button(200, 300, 200, 50, 'Settings', btn_style) + self.settings_btn = hapi.button(200, 300, 200, 50, "Settings", btn_style) - slider_style.update({'value_range': [5, 30], - 'step': 1, - 'starting_value': 20}) + slider_style.update({"value_range": [5, 30], "step": 1, "starting_value": 20}) self.rows_slider = hapi.slider(300, 200, 200, 30, slider_style) self.rows_slider = hapi.slider_with_text(self.rows_slider) - - slider_style.update({'value_range': [0.01, 1], - 'step': 0, - 'starting_value': 0.1}) + + slider_style.update( + {"value_range": [0.01, 1], "step": 0, "starting_value": 0.1} + ) self.speed_slider = hapi.slider(300, 300, 200, 30, slider_style) - self.speed_slider = hapi.slider_with_text(self.speed_slider, {'accuracy': 2}) - self.slider_names = ['num of rows', 'speed'] + self.speed_slider = hapi.slider_with_text(self.speed_slider, {"accuracy": 2}) + self.slider_names = ["num of rows", "speed"] self.back_btn = hapi.button(200, 400, 200, 50, "Back", btn_style) - self.size = WIDTH//self.rows_slider.value() + self.size = WIDTH // self.rows_slider.value() self.start_pos = 5, 5 self.head = [x * self.size for x in self.start_pos] self.body = [] - self.food_pos = [random.randint(0, WIDTH) // self.size * self.size for x in range(2)] + self.food_pos = [ + random.randint(0, WIDTH) // self.size * self.size for x in range(2) + ] self.direction = [0, 0] self.move = hapi.timer(seconds=self.speed_slider.value()) @@ -56,40 +60,49 @@ def Start(self): hapi.event_loop() def Main(self): - hapi.background(hapi.color['black']) - hapi.fill(hapi.color['yellow']) - hapi.rect(self.head[0]+1, self.head[1]+1, self.size-2, self.size-2) + hapi.background(hapi.color["black"]) + hapi.fill(hapi.color["yellow"]) + hapi.rect(self.head[0] + 1, self.head[1] + 1, self.size - 2, self.size - 2) for x, y in self.body: - hapi.rect(x+1, y+1, self.size-2, self.size-2) + hapi.rect(x + 1, y + 1, self.size - 2, self.size - 2) if self.move: - for i in range(len(self.body) -1, 0, -1): + for i in range(len(self.body) - 1, 0, -1): self.body[i] = self.body[i - 1] if len(self.body) > 0: - self.body[0] = self.head + self.body[0] = self.head self.head = [self.head[i] + self.direction[i] for i in range(2)] self.move = hapi.timer(seconds=self.speed_slider.value()) if self.head in self.body: self.Died() - elif self.head[0] >= WIDTH or self.head[0] < 0 or self.head[1] < 0 or self.head[1] >= HEIGHT: - self.Died() + elif ( + self.head[0] >= WIDTH + or self.head[0] < 0 + or self.head[1] < 0 + or self.head[1] >= HEIGHT + ): + self.Died() if self.head == self.food_pos: self.body.append(self.head[:]) - self.food_pos = [random.randint(0, WIDTH) // self.size * self.size for x in range(2)] - hapi.fill(hapi.color['green']) - hapi.rect(self.food_pos[0], self.food_pos[1], self.size-1, self.size-1) + self.food_pos = [ + random.randint(0, WIDTH) // self.size * self.size for x in range(2) + ] + hapi.fill(hapi.color["green"]) + hapi.rect(self.food_pos[0], self.food_pos[1], self.size - 1, self.size - 1) def Died(self): self.head = [x * self.size for x in self.start_pos] self.body = [] - self.food_pos = [random.randint(0, WIDTH) // self.size * self.size for x in range(2)] + self.food_pos = [ + random.randint(0, WIDTH) // self.size * self.size for x in range(2) + ] self.direction = [0, 0] self.current_screen = self.Main_menu def Settings(self): - hapi.background(hapi.color['white']) + hapi.background(hapi.color["white"]) self.speed_slider.update() self.rows_slider.update() - hapi.fill(hapi.color['black']) + hapi.fill(hapi.color["black"]) hapi.font_size(30) hapi.text(self.slider_names[0], 100, 200) hapi.text(self.slider_names[1], 100, 300) @@ -97,8 +110,8 @@ def Settings(self): self.current_screen = self.Main_menu def Main_menu(self): - hapi.background(hapi.color['white']) - hapi.fill(hapi.color['black']) + hapi.background(hapi.color["white"]) + hapi.fill(hapi.color["black"]) hapi.font_size(50) hapi.text("Snake", 220, 30) if self.menu_btn_quit.update(): @@ -121,6 +134,7 @@ def Events(self, event): elif event.unicode == "s" or event.key == 274: self.direction = [0, self.size] -if __name__ == '__main__': + +if __name__ == "__main__": game = Game() game.Start() diff --git a/hooman/demos/test.py b/hooman/demos/test.py new file mode 100644 index 0000000..437eae5 --- /dev/null +++ b/hooman/demos/test.py @@ -0,0 +1,37 @@ +from hooman import Hooman + +import pygame + +window_width, window_height = 500, 500 +hapi = Hooman(window_width, window_height) + + +def handle_events(event): + if event.type == pygame.QUIT: + hapi.is_running = False + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + hapi.is_running = False + + +hapi.handle_events = handle_events + +clock = pygame.time.Clock() + +hapi.set_background(hapi.color["white"]) + +while hapi.is_running: + + hapi.fill(100) + hapi.stroke(0) + hapi.stroke_size(4) + hapi.regular_polygon(100, 200, 50, 50, hapi.mouseX() // 50) + hapi.rotate(hapi.mouseY() // 2) + + hapi.event_loop() + + hapi.flip_display() + + clock.tick(60) + +pygame.quit() diff --git a/hooman/formula.py b/hooman/formula.py index 7c8a963..84ef63a 100644 --- a/hooman/formula.py +++ b/hooman/formula.py @@ -1,6 +1,7 @@ import math import colorsys + def constrain(val, start, end, realstart, realend): # mouseX 0 width 0 255 # v = (mouseX / (end-start)) * (realend-realstart) @@ -19,22 +20,24 @@ def constrain(val, start, end, realstart, realend): def round_to_num(x, base=1): return base * round(x / base) + def distance(coord1, coord2): x1 = coord1[0] y1 = coord1[1] x2 = coord2[0] y2 = coord2[1] - squared_sums = math.pow(x2-x1, 2) + math.pow(y2-y1, 2) + squared_sums = math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) d = math.pow(squared_sums, 0.5) return d def rgb_to_hls(r, g, b): - r, g, b = [x/255.0 for x in (r, g, b)] + r, g, b = [x / 255.0 for x in (r, g, b)] h, l, s = colorsys.rgb_to_hls(r, g, b) return h, l, s + def hls_to_rgb(h, l, s): r, g, b = colorsys.hls_to_rgb(h, l, s) - r, g, b = [x*255.0 for x in (r, g, b)] + r, g, b = [x * 255.0 for x in (r, g, b)] return r, g, b diff --git a/hooman/hooman.py b/hooman/hooman.py index 44cfe98..1bdfe73 100644 --- a/hooman/hooman.py +++ b/hooman/hooman.py @@ -1,3 +1,5 @@ +import sys + import pygame import pygame.gfxdraw @@ -40,15 +42,22 @@ from .charts import piechart from .charts import scatterchart +from .svg import SVG + +from .check import check_color +from .check import verify_color +from .check import check_value +from .check import verify_func_param + class Hooman: - def __init__(self, WIDTH, HEIGHT): + def __init__(self, WIDTH, HEIGHT, svg=False): pygame.init() self.WIDTH = WIDTH self.HEIGHT = HEIGHT self.PI = pi - self.center_x = WIDTH//2 - self.center_y = HEIGHT//2 + self.center_x = WIDTH // 2 + self.center_y = HEIGHT // 2 self.sin = sin self.cos = cos self.constrain = constrain @@ -114,54 +123,60 @@ def __init__(self, WIDTH, HEIGHT): self._piechart = piechart self._scatterchart = scatterchart - self.hls_to_rgb = hls_to_rgb self.rgb_to_hls = rgb_to_hls + self._svg = svg + self._svg_commands = [] + # # colors # def fill(self, col): """The color to fill drawn shapes with""" - if isinstance(col, int): - self._fill = (col, col, col) - elif isinstance(col, list) or isinstance(col, tuple): - if len(col) == 1: - self._fill = (col[0], col[0], col[0]) - else: - self._fill = (col[0], col[1], col[2]) + verify_color([col]) + self._fill = check_color(col).value def stroke(self, col): """The color to draw lines/strokes with""" - if isinstance(col, int): - self._stroke = (col, col, col) - elif isinstance(col, list) or isinstance(col, tuple): - if len(col) == 1: - self._stroke = (col[0], col[0], col[0]) - else: - self._stroke = (col[0], col[1], col[2]) + verify_color([col]) + self._stroke = check_color(col).value def background(self, col): """Fill the screen with a color""" - if isinstance(col, int) or isinstance(col, float): - self.screen.fill((col, col, col)) - elif isinstance(col, list) or isinstance(col, tuple): - if len(col) == 1: - self.screen.fill((col[0], col[0], col[0])) - else: - self.screen.fill((col[0], col[1], col[2])) + + verify_color([col]) + self.screen.fill(check_color(col).value) + + # check_col = check_ def gradient(self, w, h, start_col, end_col, direction=0): """returns a pygame.Surface with a gradient between 2 colors""" - return self._gradient(w, h, start_col, end_col, direction) + param_types = { + "w": [int, []], + "h": [int, []], + "direction": [int, []], + } + verify_color([start_col, end_col]) + verify_func_param(self.gradient, param_types, locals()) + return self._gradient( + w, h, check_color(start_col).value, check_color(end_col).value, direction + ) def set_background(self, col): """this calls hapi.background every frame with the given color""" - self.bg_col = col + + verify_color([col]) + self.bg_col = check_color(col).value def stroke_size(self, weight): """The thickness of drawn lines""" + param_types = { + "weight": [int, []], + } + verify_func_param(self.stroke_size, param_types, locals()) + self._stroke_weight = weight def set_alpha(self, alpha): @@ -176,6 +191,12 @@ def no_stroke(self): self._stroke_weight = 0 def font_size(self, font_size): + param_types = { + "font_size": [int, []], + } + + verify_func_param(self.font_size, param_types, locals()) + self._font_size = font_size # @@ -183,6 +204,10 @@ def font_size(self, font_size): # def rotate(self, angle): + param_types = { + "angle": [int, []], + } + verify_func_param(self.rotate, param_types, locals()) self._rotation = angle % 360 def push_matrix(self): @@ -196,23 +221,96 @@ def pop_matrix(self): # def ellipse(self, x, y, width, height): + param_types = { + "x": [[int, float], []], + "y": [[int, float], []], + "width": [[int, float], []], + "height": [[int, float], []], + } + verify_func_param(self.ellipse, param_types, locals()) + 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): + param_types = { + "x": [[int, float], []], + "y": [[int, float], []], + "width": [[int, float], []], + "height": [[int, float], []], + } + verify_func_param(self.rect, param_types, locals()) 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) def text(self, letters, x, y): if not isinstance(letters, str): letters = str(letters) + + param_types = { + "x": [[int, float], []], + "y": [[int, float], []], + "letters": [str, []], + } + verify_func_param(self.text, param_types, locals()) + font = pygame.font.SysFont(self.sysfont, self._font_size) text = font.render(letters, True, self._fill) 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): + param_types = { + "x": [[int, float], []], + "y": [[int, float], []], + "width": [[int, float], []], + "height": [[int, float], []], + "start_angle": [int, []], + "end_angle": [int, []], + } + verify_func_param(self.arc, param_types, locals()) pygame.draw.arc( self.screen, self._fill, @@ -231,26 +329,85 @@ 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): + param_types = { + "x1": [[int, float], []], + "y1": [[int, float], []], + "x2": [[int, float], []], + "y2": [[int, float], []], + } + verify_func_param(self.line, param_types, locals()) 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) def alpha_ellipse(self, x, y, w, h): + param_types = { + "x": [[int, float], []], + "y": [[int, float], []], + "w": [int, []], + "h": [int, []], + } + verify_func_param(self.alpha_ellipse, param_types, locals()) self._alpha_ellipse(self, x, y, w, h) def curve_rect(self, x, y, w, h, curve): @@ -309,7 +466,9 @@ def gradient_rect(self, x, y, w, h, start_col, end_col, direction=0): def fill_arc(self, x, y, radius, startangle, endangle, start_rad=0): for r in range(start_rad, radius): - pygame.gfxdraw.arc(self.screen, x, y, r, int(startangle), int(endangle), self._fill) + pygame.gfxdraw.arc( + self.screen, x, y, r, int(startangle), int(endangle), self._fill + ) # # interactivity @@ -376,10 +535,11 @@ def slider_with_text(self, slider, params={}) -> slider_with_text: self._all_widgets.append(s) return s - def scroll(self, param_options = {}) -> Scroll: + def scroll(self, param_options={}) -> Scroll: s = Scroll(self, param_options) self._all_widgets.append(s) return s + # # time # @@ -412,14 +572,16 @@ 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): - ''' + """ data in the format: [ ['a', 20, hapi.color['red']], @@ -428,8 +590,17 @@ def piechart(self, x, y, radius, data, start_rad=0): ['d', 60, hapi.color['green']], ['e', 30, hapi.color['black']] ] - ''' + """ 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/result.py b/hooman/result.py new file mode 100644 index 0000000..e279150 --- /dev/null +++ b/hooman/result.py @@ -0,0 +1,68 @@ +class Result: + # Thanks Aaron Luna + """Represent the outcome of an operation.""" + + def __init__(self, success, value, error): + """Represent the outcome of an operation.""" + self.success = success + self.error = error + self.value = value + + def __str__(self): + """Informal string representation of a result.""" + if self.success: + return "[Success]" + else: + return f"[Failure] {self.error}" + + def __repr__(self): + """Official string representation of a result.""" + if self.success: + return f"" + else: + return f'' + + @property + def failure(self): + """Flag that indicates if the operation failed.""" + return not self.success + + def on_success(self, func, *args, **kwargs): + """Pass result of successful operation (if any) to subsequent function.""" + if self.failure: + return self + if self.value: + return func(self.value, *args, **kwargs) + return func(*args, **kwargs) + + def on_failure(self, func, *args, **kwargs): + """Pass error message from failed operation to subsequent function.""" + if self.success: + return self.value if self.value else None + if self.error: + return func(self.error, *args, **kwargs) + return func(*args, **kwargs) + + def on_both(self, func, *args, **kwargs): + """Pass result (either succeeded/failed) to subsequent function.""" + if self.value: + return func(self.value, *args, **kwargs) + return func(*args, **kwargs) + + @staticmethod + def Fail(error_message): + """Create a Result object for a failed operation.""" + return Result(False, value=None, error=error_message) + + @staticmethod + def Ok(value=None): + """Create a Result object for a successful operation.""" + return Result(True, value=value, error=None) + + @staticmethod + def Combine(results): + """Return a Result object based on the outcome of a list of Results.""" + if all(result.success for result in results): + return Result.Ok() + errors = [result.error for result in results if result.failure] + return Result.Fail("\n".join(errors)) diff --git a/hooman/svg.py b/hooman/svg.py new file mode 100644 index 0000000..bd5e2be --- /dev/null +++ b/hooman/svg.py @@ -0,0 +1,36 @@ +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/hooman/ui.py b/hooman/ui.py index 482a0ff..4bd739c 100644 --- a/hooman/ui.py +++ b/hooman/ui.py @@ -6,10 +6,11 @@ import pygame from .formula import constrain from typing import Union +from .check import check_params class Button: - def __init__(self, x, y, w, h, text, param_options={}): + def __init__(self, x, y, w, h, text, params={}): self.x = x self.y = y self.w = w @@ -41,9 +42,10 @@ def __init__(self, x, y, w, h, text, param_options={}): "curve": 0, "padding_x": 0, "padding_y": 0, - "centered": False + "centered": False, } - options.update(param_options) + check_params(params, options, "button") + options.update(params) for key, val in options.items(): if key not in options: @@ -73,7 +75,7 @@ def __init__(self, x, y, w, h, text, param_options={}): self.hover_image = options["hover_image"] self.enlarge = options["enlarge"] self.enlarge_amount = options["enlarge_amount"] - self.center = options['centered'] + self.center = options["centered"] # if no surface is supplied, try getting main screen if self.surface is None: @@ -83,6 +85,7 @@ def __init__(self, x, y, w, h, text, param_options={}): if self.hover_bg_colour is None: self.hover_bg_colour = self.background_color + self.font = pygame.font.Font(pygame.font.match_font(font), font_size) self.image = image.copy() if image else None @@ -112,8 +115,8 @@ def __init__(self, x, y, w, h, text, param_options={}): ) self._Generate_images() if self.center: - self.x -= self.w//2 - self.y -= self.h//2 + self.x -= self.w // 2 + self.y -= self.h // 2 def _Generate_images(self): # generate images @@ -332,7 +335,7 @@ def curve_square(width, height, curve, color=(0, 0, 0)): class Slider: - def __init__(self, hapi, x, y, w, h, params): + def __init__(self, hapi, x, y, w, h, params={}): options = { "background_color": (100, 100, 100), "slider_width": None, @@ -342,14 +345,11 @@ def __init__(self, hapi, x, y, w, h, params): "slider_height": None, "step": 0, "image": None, - 'direction': 'horizontal', - 'resize_slider': False, - 'curve': 0 + "direction": "horizontal", + "resize_slider": False, + "curve": 0, } - for key, val in params.items(): - if key not in options: - raise TypeError(key + " is not an option, have you spelt it correctly") - + check_params(params, options, "slider") options.update(params) self.x = x @@ -357,36 +357,38 @@ def __init__(self, hapi, x, y, w, h, params): self.w = w self.h = h self.hapi = hapi - self.direction = options['direction'] - if self.direction not in ['horizontal', 'vertical']: - raise ValueError('option \'direction\' is not a direction, (%d)' % (self.direction)) + self.direction = options["direction"] + if self.direction not in ["horizontal", "vertical"]: + raise ValueError( + "option 'direction' is not a direction, (%d)" % (self.direction) + ) self.bg = options["background_color"] - #if the user gives an image for slider background, use that instead of drawing one + # if the user gives an image for slider background, use that instead of drawing one if options["image"] is not None: self.image = pygame.Surface((self.w, self.h)) self.image.blit(options["image"], (0, 0)) else: self.image = None self.val_range = options["value_range"] - self.curve = options['curve'] - self.resize = options['resize_slider'] + self.curve = options["curve"] + self.resize = options["resize_slider"] val_dif = self.val_range[1] - self.val_range[0] self.slider_bg = options["slider_color"] self.slider_h = options["slider_height"] if self.slider_h is None: - if self.direction == 'horizontal': + if self.direction == "horizontal": self.slider_h = h else: self.slider_h = w self.step = options["step"] - if self.direction == 'horizontal': + if self.direction == "horizontal": self.slider_w = ( options["slider_width"] if options["slider_width"] is not None else h ) else: self.slider_w = ( options["slider_width"] if options["slider_width"] is not None else w - ) + ) if options["starting_value"] is not None: self.val = constrain( options["starting_value"], self.val_range[0], self.val_range[1], 0, 1 @@ -395,14 +397,14 @@ def __init__(self, hapi, x, y, w, h, params): self.val = 0.5 if self.resize: range_ = self.val_range[1] - self.val_range[0] - if self.direction == 'horizontal': + if self.direction == "horizontal": if range_ < self.w: self.slider_w = self.w - range_ else: if range_ < self.h: - self.slider_h = self.h - range_ + self.slider_h = self.h - range_ self.screen = pygame.display.get_surface() - if self.direction == 'horizontal': + if self.direction == "horizontal": self.slider_rect = pygame.Rect( self.x + self.val * (self.w - self.slider_w), self.y + (self.h - self.slider_h) // 2, @@ -419,7 +421,7 @@ def __init__(self, hapi, x, y, w, h, params): self.clicked_on = False self.prev_click = False - #draw the slider + # draw the slider def _draw(self): if self.image is not None: self.hapi.screen.blit(self.image, (self.x, self.y)) @@ -428,15 +430,18 @@ def _draw(self): self.hapi.screen, self.bg, (self.x, self.y, self.w, self.h) ) else: - self.hapi.screen.blit(curve_square(self.w, self.h, self.curve, self.bg), - (self.x, self.y)) + self.hapi.screen.blit( + curve_square(self.w, self.h, self.curve, self.bg), (self.x, self.y) + ) if self.curve == 0: pygame.draw.rect(self.hapi.screen, self.slider_bg, self.slider_rect) else: - self.hapi.screen.blit(curve_square(self.slider_w, self.slider_h, - self.curve, self.slider_bg), self.slider_rect) + self.hapi.screen.blit( + curve_square(self.slider_w, self.slider_h, self.curve, self.slider_bg), + self.slider_rect, + ) - #updates the slider, this should be called every frame + # updates the slider, this should be called every frame def update(self): mouse_pos = pygame.mouse.get_pos() click = pygame.mouse.get_pressed()[0] @@ -445,7 +450,7 @@ def update(self): if click and not self.prev_click: self.clicked_on = True if self.clicked_on: - if self.direction == 'horizontal': + if self.direction == "horizontal": self.val = (mouse_pos[0] - self.x) / self.w self.val = max(min(self.val, 1), 0) self.val = self._get_val(self.val) @@ -456,16 +461,16 @@ def update(self): self.val = self._get_val(self.val) self.slider_rect.y = self.y + self.val * (self.h - self.slider_h) if not click: - self.clicked_on = False + self.clicked_on = False self.prev_click = click self._draw() - def Move(self, x = 0, y = 0, dx = 0, dy = 0): + def Move(self, x=0, y=0, dx=0, dy=0): self.x = x if x != 0 else self.x self.y = y if y != 0 else self.y self.x += dx self.y += dy - if self.direction == 'horizontal': + if self.direction == "horizontal": self.slider_rect = pygame.Rect( self.x + self.val * (self.w - self.slider_w), self.y + (self.h - self.slider_h) // 2, @@ -495,7 +500,7 @@ def set_value(self, val: Union[float, int]): else: self.slider_rect.y = self.y + self.val * (self.h - self.slider_h) - #if the slider has a step and is not contineous, round to nearest step size + # if the slider has a step and is not contineous, round to nearest step size def _get_val(self, val): if self.step == 0: return val @@ -508,7 +513,7 @@ def _get_val(self, val): class TextBox: - def __init__(self, x, y, w, h=0, param_options={}): + def __init__(self, x, y, w, h=0, params={}): options = { "max_lines": 1000, "text": "", @@ -522,7 +527,8 @@ def __init__(self, x, y, w, h=0, param_options={}): "on_enter": None, "calculate_size": False, } - options.update(param_options) + check_params(params, options, "text box") + options.update(params) self.x = x self.y = y self.w = w @@ -568,24 +574,29 @@ def _get_font_height(self): obj = self.font.render(" ", True, (0, 0, 0)) return obj.get_height() - def wrapper(self, change_cur = False): + def wrapper(self, change_cur=False): for cur_line, line in enumerate(self.text): - for i in range(len(''.join(line))): - length = self._get_text_width(''.join(line[:i])) + for i in range(len("".join(line))): + length = self._get_text_width("".join(line[:i])) if length > self.w: - indexs = [i for i, e in enumerate(self.text[cur_line][:i]) if e == " "] + indexs = [ + i for i, e in enumerate(self.text[cur_line][:i]) if e == " " + ] if cur_line < self.lines - 1: if len(indexs) == 0: - indexs.append(i-1) + indexs.append(i - 1) if change_cur: self.current_line += 1 self.current_col = len(self.text[cur_line]) - indexs[-1] - 1 if cur_line < len(self.text): - self.text.append(self.text[cur_line][indexs[-1]+1:]) + self.text.append(self.text[cur_line][indexs[-1] + 1 :]) else: - self.text[cur_line + 1] = self.text[cur_line][indexs[-1]+1:] + self.text[cur_line] - self.text[cur_line] = self.text[cur_line][:indexs[-1]] - break + self.text[cur_line + 1] = ( + self.text[cur_line][indexs[-1] + 1 :] + + self.text[cur_line] + ) + self.text[cur_line] = self.text[cur_line][: indexs[-1]] + break # call this when the user presses a key down, supply the event from `pygame.event.get()` def key_down(self, e: pygame.event.Event): @@ -620,7 +631,7 @@ def key_down(self, e: pygame.event.Event): + self.text[self.current_line][self.current_col :] ) self.current_col += 1 - #wrapper + # wrapper if self._get_text_width(self.text[self.current_line]) > self.w: self.wrapper(True) # if the down arrow is pressed @@ -725,9 +736,7 @@ def __init__(self, hapi, slider, params={}): if not isinstance(slider, Slider): raise TypeError("'Slider' argument is not a slider widget") - for key, item in params.items(): - if key not in options: - raise TypeError(key + " is not an option, have you spelt it correctly") + check_params(params, options, "slider with text") options.update(params) @@ -791,42 +800,59 @@ def _get_text_height(self): def value(self) -> int: return self.slider.value() + class Scroll: - def __init__(self, hapi, param_options = {}): - options = {'starting_x': 0, - 'starting_y': 0, - 'range_x': 0, - 'range_y': 0, - 'bar_color': (200, 200, 200), - 'slider_color': (150, 150, 150)} - - options.update(param_options) + def __init__(self, hapi, params={}): + options = { + "starting_x": 0, + "starting_y": 0, + "range_x": 0, + "range_y": 0, + "bar_color": (200, 200, 200), + "slider_color": (150, 150, 150), + } + check_params(params, options, "scroll") + options.update(params) screen_size = pygame.display.get_surface().get_size() self.w, self.h = screen_size - #create sliders for x and y axis + # create sliders for x and y axis self.x_slider = None self.y_slider = None - if options['range_x'] > 0: - self.x_slider = Slider(hapi, 0, self.h - 20, self.w - 20, 20, - {'starting_value': options['starting_x'], - 'value_range': [0, options['range_x']], - 'slider_color': options['slider_color'], - 'background_color': options['bar_color'], - 'resize_slider': True - }) - if options['range_y'] > 0: - self.y_slider = Slider(hapi, self.w - 20, 0, 20, self.h - 20, - {'starting_value': options['starting_y'], - 'value_range': [0, options['range_y']], - 'slider_color': options['slider_color'], - 'background_color': options['bar_color'], - 'direction': 'vertical', - 'resize_slider': True - }) - - #this is updates the sliders, this should be called every frame + if options["range_x"] > 0: + self.x_slider = Slider( + hapi, + 0, + self.h - 20, + self.w - 20, + 20, + { + "starting_value": options["starting_x"], + "value_range": [0, options["range_x"]], + "slider_color": options["slider_color"], + "background_color": options["bar_color"], + "resize_slider": True, + }, + ) + if options["range_y"] > 0: + self.y_slider = Slider( + hapi, + self.w - 20, + 0, + 20, + self.h - 20, + { + "starting_value": options["starting_y"], + "value_range": [0, options["range_y"]], + "slider_color": options["slider_color"], + "background_color": options["bar_color"], + "direction": "vertical", + "resize_slider": True, + }, + ) + + # this is updates the sliders, this should be called every frame def update(self): if self.x_slider is not None: self.x_slider.update() @@ -839,4 +865,4 @@ def __getitem__(self, index : int): return -self.x_slider.value() elif index == 1: return -self.y_slider.value() - return (-self.x_slider.value(), -self.y_slider.value()) \ No newline at end of file + return (-self.x_slider.value(), -self.y_slider.value()) 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