From 0fd4e25d6b1b4ba7146a897f3a4bb22adfa1f60e Mon Sep 17 00:00:00 2001 From: LudoBroche Date: Tue, 7 Jul 2026 16:41:39 +0200 Subject: [PATCH 1/6] Issue #15 - First path object implementation with small example --- examples/cubic_bezier_link.py | 104 ++++++++++++++++++ examples/cubic_bezier_link.svg | 33 ++++++ .../css_styles/css_link_cubic_bezier.css | 8 ++ src/ewoksdraw/css_styles/css_task_line.css | 3 +- src/ewoksdraw/geometry/__init__.py | 3 + src/ewoksdraw/geometry/cubic_bezier_path.py | 97 ++++++++++++++++ src/ewoksdraw/svg/svg_element.py | 4 +- src/ewoksdraw/svg/svg_link_cubic_bezier.py | 56 ++++++++++ 8 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 examples/cubic_bezier_link.py create mode 100644 examples/cubic_bezier_link.svg create mode 100644 src/ewoksdraw/css_styles/css_link_cubic_bezier.css create mode 100644 src/ewoksdraw/geometry/__init__.py create mode 100644 src/ewoksdraw/geometry/cubic_bezier_path.py create mode 100644 src/ewoksdraw/svg/svg_link_cubic_bezier.py diff --git a/examples/cubic_bezier_link.py b/examples/cubic_bezier_link.py new file mode 100644 index 0000000..b81df05 --- /dev/null +++ b/examples/cubic_bezier_link.py @@ -0,0 +1,104 @@ +import copy +from pathlib import Path + +from ewoksdraw.geometry.cubic_bezier_path import CubicBezierPath +from ewoksdraw.geometry.cubic_bezier_path import CubicBezierSegment +from ewoksdraw.svg.svg_canvas import SvgCanvas +from ewoksdraw.svg.svg_group import SvgGroup +from ewoksdraw.svg.svg_link_cubic_bezier import SvgLinkCubicBezier + +output_path = Path(__file__).with_suffix(".svg") + +letter_e = CubicBezierPath( + start=(10, 130), + segments=[ + CubicBezierSegment((40, 115), (75, 95), (105, 90)), + CubicBezierSegment((125, 75), (55, 50), (25, 115)), + CubicBezierSegment((0, 165), (70, 170), (115, 140)), + ], +) +letter_w = CubicBezierPath( + start=(135, 84), + segments=[ + CubicBezierSegment((142, 124), (145, 154), (158, 156)), + CubicBezierSegment((169, 158), (176, 102), (187, 102)), + CubicBezierSegment((198, 102), (196, 157), (210, 157)), + CubicBezierSegment((224, 157), (236, 105), (244, 84)), + ], +) + +letter_o = CubicBezierPath( + start=(302, 113), + segments=[ + CubicBezierSegment((302, 86), (260, 80), (253, 112)), + CubicBezierSegment((247, 141), (286, 155), (304, 128)), + CubicBezierSegment((317, 108), (307, 89), (290, 87)), + ], +) + +letter_k = CubicBezierPath( + start=(348, 70), + segments=[ + CubicBezierSegment((342, 104), (340, 128), (338, 158)), + CubicBezierSegment((353, 130), (374, 102), (394, 83)), + CubicBezierSegment((373, 107), (369, 127), (394, 154)), + ], +) + +letter_s = CubicBezierPath( + start=(469, 84), + segments=[ + CubicBezierSegment((434, 72), (421, 102), (453, 115)), + CubicBezierSegment((491, 130), (482, 162), (439, 151)), + CubicBezierSegment((420, 146), (427, 130), (448, 133)), + ], +) + +underline = CubicBezierPath.from_points( + points=[ + (40, 175), + (240, 175), + (240, 205), + (480, 205), + (480, 25), + (320, 25), + (320, 205), + ], + radius=30, +) +links_group = SvgGroup() +links_group.add_elements( + [ + SvgLinkCubicBezier(letter_e), + SvgLinkCubicBezier( + letter_w, + color="#00c2a8", + stroke_width=4, + stroke_dash="5 10", + ), + SvgLinkCubicBezier( + letter_o, + color="#ffcc00", + stroke_width=6, + stroke_dash="1 5", + ), + SvgLinkCubicBezier(letter_k, color="#7c5cff", stroke_width=4), + SvgLinkCubicBezier( + underline, + color="#ff00aa", + stroke_width=3, + stroke_dash="12 6", + ), + ] +) + +links_group_2 = copy.copy(links_group) +links_group_2.translate(5, 5) + +canvas = SvgCanvas(width=520, height=220) +canvas.add_background() +canvas.add_element(links_group) +canvas.add_element(links_group_2) + +canvas.draw(output_path) +print(f"Wrote {output_path}") diff --git a/examples/cubic_bezier_link.svg b/examples/cubic_bezier_link.svg new file mode 100644 index 0000000..9236ffe --- /dev/null +++ b/examples/cubic_bezier_link.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/ewoksdraw/css_styles/css_link_cubic_bezier.css b/src/ewoksdraw/css_styles/css_link_cubic_bezier.css new file mode 100644 index 0000000..e33e3ee --- /dev/null +++ b/src/ewoksdraw/css_styles/css_link_cubic_bezier.css @@ -0,0 +1,8 @@ +.link_cubic_bezier { + fill: none; + stroke: #ffffff; + stroke-width: 1.5; + stroke-linecap: round; + stroke-linejoin: round; + stroke-dasharray: none; +} \ No newline at end of file diff --git a/src/ewoksdraw/css_styles/css_task_line.css b/src/ewoksdraw/css_styles/css_task_line.css index 51cdbf8..c917e09 100644 --- a/src/ewoksdraw/css_styles/css_task_line.css +++ b/src/ewoksdraw/css_styles/css_task_line.css @@ -1,4 +1,5 @@ .task_line { - stroke: rgb(255, 255, 255); + stroke: #ffffffff; stroke-width: 1; + stroke-dasharray: none; } \ No newline at end of file diff --git a/src/ewoksdraw/geometry/__init__.py b/src/ewoksdraw/geometry/__init__.py new file mode 100644 index 0000000..5800bde --- /dev/null +++ b/src/ewoksdraw/geometry/__init__.py @@ -0,0 +1,3 @@ +from .cubic_bezier_path import CubicBezierPath # noqa: F401 +from .cubic_bezier_path import CubicBezierSegment # noqa: F401 +from .cubic_bezier_path import Point # noqa: F401 diff --git a/src/ewoksdraw/geometry/cubic_bezier_path.py b/src/ewoksdraw/geometry/cubic_bezier_path.py new file mode 100644 index 0000000..3056485 --- /dev/null +++ b/src/ewoksdraw/geometry/cubic_bezier_path.py @@ -0,0 +1,97 @@ +from dataclasses import dataclass +from typing import Sequence + +Point = tuple[float, float] +Vector = tuple[float, float] + + +@dataclass(frozen=True) +class CubicBezierSegment: + control1: Point + control2: Point + end: Point + + +@dataclass(frozen=True) +class CubicBezierPath: + start: Point + segments: Sequence[CubicBezierSegment] + + @classmethod + def from_points(cls, points: Sequence[Point], radius: float) -> "CubicBezierPath": + """ + Create a rounded cubic Bezier path from horizontal and vertical points. + + :param points: The polyline points to convert. + :param radius: The turn radius around each intermediate point. + """ + + segments = [] + current = points[0] + + for index in range(1, len(points) - 1): + previous_point = points[index - 1] + corner_point = points[index] + next_point = points[index + 1] + + previous_direction = _direction(previous_point, corner_point) + next_direction = _direction(corner_point, next_point) + + # we might not have the space to turn if distance is too small + turn_radius = min( + radius, + _distance(previous_point, corner_point) / 2, + _distance(corner_point, next_point) / 2, + ) + + corner_start = _move(corner_point, previous_direction, -turn_radius) + corner_end = _move(corner_point, next_direction, turn_radius) + + segments.append(_straight_segment(current, corner_start)) + segments.append( + CubicBezierSegment( + control1=_move(corner_start, previous_direction, turn_radius / 2), + control2=_move(corner_end, next_direction, -turn_radius / 2), + end=corner_end, + ) + ) + current = corner_end + + segments.append(_straight_segment(current, points[-1])) + return cls(start=points[0], segments=segments) + + +def _straight_segment(start: Point, end: Point) -> CubicBezierSegment: + """Create a cubic Bezier segment that renders as a straight line.""" + start_x, start_y = start + end_x, end_y = end + + # Control points are set to 1/2; 2/3 arbitrarly so they are not combine with + # start and end points. + return CubicBezierSegment( + control1=(start_x + (end_x - start_x) / 3, start_y + (end_y - start_y) / 3), + control2=( + start_x + 2 * (end_x - start_x) / 3, + start_y + 2 * (end_y - start_y) / 3, + ), + end=end, + ) + + +def _direction(start: Point, end: Point) -> Vector: + """ + Return the horizontal or vertical direction from start to end. + Example : (1, 0) right; (-1, 0) left ... + """ + if start[0] == end[0]: + return (0, 1 if end[1] > start[1] else -1) + return (1 if end[0] > start[0] else -1, 0) + + +def _distance(start: Point, end: Point) -> float: + return abs(end[0] - start[0]) + abs(end[1] - start[1]) + + +def _move(point: Point, direction: Vector, distance: float) -> Point: + """Move a point along a direction by a distance.""" + return (point[0] + direction[0] * distance, point[1] + direction[1] * distance) diff --git a/src/ewoksdraw/svg/svg_element.py b/src/ewoksdraw/svg/svg_element.py index 0ca1c50..a3ac90e 100644 --- a/src/ewoksdraw/svg/svg_element.py +++ b/src/ewoksdraw/svg/svg_element.py @@ -16,12 +16,12 @@ class SvgElement: def __init__( self, - tag: Literal["rect", "circle", "text", "line"], + tag: Literal["rect", "circle", "text", "line", "path"], css_class: Optional[str] = None, attr: Optional[dict] = None, text: Optional[str] = None, ): - if tag not in ("rect", "circle", "text", "line"): + if tag not in ("rect", "circle", "text", "line", "path"): raise ValueError( f"Invalid SVG tag: {tag}. Supported tags are 'rect', 'circle', 'text'," " 'line'." diff --git a/src/ewoksdraw/svg/svg_link_cubic_bezier.py b/src/ewoksdraw/svg/svg_link_cubic_bezier.py new file mode 100644 index 0000000..34a520c --- /dev/null +++ b/src/ewoksdraw/svg/svg_link_cubic_bezier.py @@ -0,0 +1,56 @@ +from ..geometry.cubic_bezier_path import CubicBezierPath +from .svg_element import SvgElement + + +class SvgLinkCubicBezier(SvgElement): + """ + Represents an SVG path element for a cubic Bezier link. + + :param path: The cubic Bezier path coordinates. + :param color: The stroke color of the link. + :param stroke_width: The stroke width of the link. + :param stroke_dash: The SVG stroke-dasharray value of the link. + """ + + def __init__( + self, + path: CubicBezierPath, + color: str | None = None, + stroke_width: float | None = None, + stroke_dash: str | None = None, + ): + string_svg = self._convert_path_data_to_svg_attribute(path) + + attr = {"d": string_svg} + styles = [] + + if color is not None: + styles.append(f"stroke:{color}") + + if stroke_width is not None: + styles.append(f"stroke-width:{stroke_width:g}") + + if stroke_dash is not None: + styles.append(f"stroke-dasharray:{stroke_dash}") + + if styles: + attr["style"] = ";".join(styles) + + super().__init__(tag="path", css_class="link_cubic_bezier", attr=attr) + + def _convert_path_data_to_svg_attribute(self, path: CubicBezierPath) -> str: + if not path.segments: + raise ValueError("A cubic Bezier path needs at least one segment.") + + start_x, start_y = path.start + commands = [f"M {start_x},{start_y}"] + + for segment in path.segments: + control1_x, control1_y = segment.control1 + control2_x, control2_y = segment.control2 + end_x, end_y = segment.end + commands.append( + f"C {control1_x},{control1_y} {control2_x},{control2_y} {end_x},{end_y}" + ) + + return " ".join(commands) From 7fdf37e75db3c1353e228b4b5daa961cbdc23e10 Mon Sep 17 00:00:00 2001 From: LudoBroche Date: Tue, 7 Jul 2026 17:12:13 +0200 Subject: [PATCH 2/6] Issue #15 - Oh no ! I forgot the S --- .gitignore | 3 +++ examples/cubic_bezier_link.py | 1 + examples/cubic_bezier_link.svg | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 7973ac4..1c64863 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ __pycache__/ .eggs/ /doc/_generated .svg +# pixi environments +.pixi/* +!.pixi/config.toml diff --git a/examples/cubic_bezier_link.py b/examples/cubic_bezier_link.py index b81df05..8a7202c 100644 --- a/examples/cubic_bezier_link.py +++ b/examples/cubic_bezier_link.py @@ -83,6 +83,7 @@ stroke_dash="1 5", ), SvgLinkCubicBezier(letter_k, color="#7c5cff", stroke_width=4), + SvgLinkCubicBezier(letter_s, color="#54a068", stroke_width=10), SvgLinkCubicBezier( underline, color="#ff00aa", diff --git a/examples/cubic_bezier_link.svg b/examples/cubic_bezier_link.svg index 9236ffe..2eee251 100644 --- a/examples/cubic_bezier_link.svg +++ b/examples/cubic_bezier_link.svg @@ -21,6 +21,7 @@ + @@ -28,6 +29,7 @@ + From 9c47ebf3e3ddb053e51ff2fec6222a1f503db0b6 Mon Sep 17 00:00:00 2001 From: LudoBroche Date: Tue, 7 Jul 2026 17:31:19 +0200 Subject: [PATCH 3/6] Issue #15 - Ignoring position when svg element is path --- example_cubic_bezier.svg | 14 + pixi.lock | 643 +++++++++++++++++++++++++++++++ pixi.toml | 15 + src/ewoksdraw/svg/svg_element.py | 4 + 4 files changed, 676 insertions(+) create mode 100644 example_cubic_bezier.svg create mode 100644 pixi.lock create mode 100644 pixi.toml diff --git a/example_cubic_bezier.svg b/example_cubic_bezier.svg new file mode 100644 index 0000000..3bd9997 --- /dev/null +++ b/example_cubic_bezier.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..2f96754 --- /dev/null +++ b/pixi.lock @@ -0,0 +1,643 @@ +version: 6 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.3-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/06/04/0efe339b14b339c10802a99723a693750bd9961e0e6166e8e4d3143140bd/ewokscore-4.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/d6/693e1f65bed1eb18d6f3454652fe12fc288f84df90ad42a1ccacd1a6a83b/ewoksutils-1.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/44/aa8bc0a660581663fb957d9005851d23736757cae3e58231a8a1030c9f9e/fabio-2026.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/bb/49/7467c2946ccd9617f7da38187071bdc45bb9a95df51f4d63d6622432ce4e/filelock-3.29.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d7/0b/855e50e27eab8338c71c3157672c065cd2a2ab38887b3ea4bf128cbd89a1/hdf5plugin-7.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a3/07/70085c17a369605f15e301d10ab902115019b1126c7253d964afc230c7d6/reportlab-5.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/49/f0dd91ff7d95dd4be65c754fd926cf914bb201eb0ba63c4055755fddeb97/silx-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl + - pypi: ./ +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 28948 + timestamp: 1770939786096 +- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + name: annotated-types + version: 0.7.0 + sha256: 1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 + requires_dist: + - typing-extensions>=4.0.0 ; python_full_version < '3.9' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + sha256: f8e3c730fa14ee3f170493779f06522c4acf89169f43db4f039727709b6419cf + md5: a9965dd99f683c5f444428f896635716 + depends: + - __unix + license: ISC + purls: [] + size: 128866 + timestamp: 1781708962055 +- pypi: https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: charset-normalizer + version: 3.4.7 + sha256: bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/06/04/0efe339b14b339c10802a99723a693750bd9961e0e6166e8e4d3143140bd/ewokscore-4.0.2-py3-none-any.whl + name: ewokscore + version: 4.0.2 + sha256: 610acda07f799987a329a3683186072d9bc96c1597848090f64eaafc5f769ad5 + requires_dist: + - numpy>=1.15 + - networkx>=2 + - silx>=1 + - pyyaml>=5.1 + - h5py>=2.8 + - packaging + - ewoksutils>=1.9.1 + - importlib-metadata ; python_full_version < '3.9' + - pydantic>=2 + - papermill ; extra == 'notebooks' + - ewokscore[notebooks] ; extra == 'test' + - pytest>=7 ; extra == 'test' + - matplotlib>=3 ; extra == 'test' + - ipykernel ; extra == 'test' + - ewokscore[test] ; extra == 'dev' + - black[jupyter]>=25 ; extra == 'dev' + - flake8>=4 ; extra == 'dev' + - flake8-nb>=0.3.1 ; extra == 'dev' + - isort ; extra == 'dev' + - ewokscore[test] ; extra == 'doc' + - sphinx>=4.5 ; extra == 'doc' + - sphinxcontrib-mermaid>=0.7 ; extra == 'doc' + - sphinx-autodoc-typehints>=1.16 ; extra == 'doc' + - nbsphinx ; extra == 'doc' + - docutils<0.21 ; extra == 'doc' + - nbsphinx-link ; extra == 'doc' + - pydata-sphinx-theme ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + requires_python: '>=3.8' +- pypi: ./ + name: ewoksdraw + version: 0.0.1 + sha256: 2e146b4fd5039229b251cd6d2be4d9aced912344bf6c76aee167ca662ea3b4a5 + requires_dist: + - ewokscore<5 + - reportlab + - xmltodict + - pytest>=7 ; extra == 'test' + - pytest>=7 ; extra == 'dev' + - ruff ; extra == 'dev' + - mypy ; extra == 'dev' + - sphinx>=4.5 ; extra == 'doc' + - sphinx-autodoc-typehints>=1.16 ; extra == 'doc' + - pydata-sphinx-theme ; extra == 'doc' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5e/d6/693e1f65bed1eb18d6f3454652fe12fc288f84df90ad42a1ccacd1a6a83b/ewoksutils-1.10.0-py3-none-any.whl + name: ewoksutils + version: 1.10.0 + sha256: 5e96d097b3fc5e76b8f500070c0a40140761ddde407b39e62c9173dbcbc77690 + requires_dist: + - click ; extra == 'full' + - ewoksutils[full] ; extra == 'test' + - pytest>=7 ; extra == 'test' + - ewoksutils[test] ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx>=4.5 ; extra == 'doc' + - sphinx-autodoc-typehints>=1.16 ; extra == 'doc' + - pydata-sphinx-theme ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/8f/44/aa8bc0a660581663fb957d9005851d23736757cae3e58231a8a1030c9f9e/fabio-2026.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: fabio + version: 2026.6.0 + sha256: fac184e9fc0d05bf0ffce8f0fc37835426585593818ac41b50f5df64be608dd9 + requires_dist: + - numpy + - h5py + - hdf5plugin + - lxml + - pillow + - filelock + - qtpy ; extra == 'gui' + - pyside6 ; extra == 'gui' + - matplotlib ; extra == 'gui' + - packaging ; extra == 'gui' + - qtpy ; extra == 'all' + - pyside6 ; extra == 'all' + - matplotlib ; extra == 'all' + - packaging ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/bb/49/7467c2946ccd9617f7da38187071bdc45bb9a95df51f4d63d6622432ce4e/filelock-3.29.6-py3-none-any.whl + name: filelock + version: 3.29.6 + sha256: 14d5f5597d2e0c4dbd774cfb6d8132da1db44da83732aab679d54f7dcf97ab65 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl + name: h5py + version: 3.16.0 + sha256: e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d7/0b/855e50e27eab8338c71c3157672c065cd2a2ab38887b3ea4bf128cbd89a1/hdf5plugin-7.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: hdf5plugin + version: 7.0.0 + sha256: 4ad4ab0d3367699d132e61b1cc382a0c640a7dba56536e5105508205ebbe8762 + requires_dist: + - h5py>=3.0.0 + - ipython ; extra == 'doc' + - nbsphinx ; extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - packaging ; extra == 'test' + - numpy<2 ; python_full_version == '3.9.*' and extra == 'test' + - blosc2>=2.5.1 ; extra == 'test' + - blosc2-grok>=0.2.2 ; extra == 'test' + - hdf5plugin[doc,test] ; extra == 'dev' + - mypy ; extra == 'dev' + - ruff ; extra == 'dev' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 728002 + timestamp: 1774197446916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + sha256: 16feffd9ddbbe5b718515d38ee376c685ba95491cd901244e24671d20b952a77 + md5: b24d3c612f71e7aa74158d92106318b2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + size: 77856 + timestamp: 1781203599810 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + sha256: 8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46 + md5: 57736f29cc2b0ec0b6c2952d3f101b6a + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.2.0=*_19 + - libgomp 15.2.0 he0feb66_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1041084 + timestamp: 1778269013026 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + sha256: 5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b + md5: faac990cb7aedc7f3a2224f2c9b0c26c + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 603817 + timestamp: 1778268942614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d + md5: b88d90cad08e6bc8ad540cb310a761fb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + size: 113478 + timestamp: 1775825492909 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 + md5: 2c21e66f50753a083cbe6b80f38268fa + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 92400 + timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.3-h0c1763c_0.conda + sha256: 365376f4815e5e80def2b3462a2419708b7c292da0da85278386c2618621fff4 + md5: 4aed8e657e9ff156bdbe849b4df44389 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 962119 + timestamp: 1782519076616 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + sha256: 9b1bdce27a7e31f7d241aeecff67a1f3101d52a2b1e33ccc2cdf2613072bf81f + md5: 01bb81d12c957de066ea7362007df642 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 40017 + timestamp: 1781625522462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 63629 + timestamp: 1774072609062 +- pypi: https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: lxml + version: 6.1.1 + sha256: 9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 + md5: fc21868a1a5aacc937e7a18747acb8a5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: X11 AND BSD-3-Clause + purls: [] + size: 918956 + timestamp: 1777422145199 +- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + name: networkx + version: 3.6.1 + sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 + requires_dist: + - asv ; extra == 'benchmarking' + - virtualenv ; extra == 'benchmarking' + - numpy>=1.25 ; extra == 'default' + - scipy>=1.11.2 ; extra == 'default' + - matplotlib>=3.8 ; extra == 'default' + - pandas>=2.0 ; extra == 'default' + - pre-commit>=4.1 ; extra == 'developer' + - mypy>=1.15 ; extra == 'developer' + - sphinx>=8.0 ; extra == 'doc' + - pydata-sphinx-theme>=0.16 ; extra == 'doc' + - sphinx-gallery>=0.18 ; extra == 'doc' + - numpydoc>=1.8.0 ; extra == 'doc' + - pillow>=10 ; extra == 'doc' + - texext>=0.6.7 ; extra == 'doc' + - myst-nb>=1.1 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - osmnx>=2.0.0 ; extra == 'example' + - momepy>=0.7.2 ; extra == 'example' + - contextily>=1.6 ; extra == 'example' + - seaborn>=0.13 ; extra == 'example' + - cairocffi>=1.7 ; extra == 'example' + - igraph>=0.11 ; extra == 'example' + - scikit-learn>=1.5 ; extra == 'example' + - iplotx>=0.9.0 ; extra == 'example' + - lxml>=4.6 ; extra == 'extra' + - pygraphviz>=1.14 ; extra == 'extra' + - pydot>=3.0.1 ; extra == 'extra' + - sympy>=1.10 ; extra == 'extra' + - build>=0.10 ; extra == 'release' + - twine>=4.0 ; extra == 'release' + - wheel>=0.40 ; extra == 'release' + - changelist==0.5 ; extra == 'release' + - pytest>=7.2 ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pytest-mpl ; extra == 'test-extras' + - pytest-randomly ; extra == 'test-extras' + requires_python: '>=3.11,!=3.14.1' +- pypi: https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.5.1 + sha256: 54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21 + requires_python: '>=3.12' +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda + sha256: d48f5c22b9897c01e4dff3680f1f57ceb02711ab9c62f74339b080419dfad34b + md5: 79dd2074b5cd5c5c6b2930514a11e22d + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3159683 + timestamp: 1781069855778 +- pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl + name: packaging + version: '26.2' + sha256: 5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: pillow + version: 12.3.0 + sha256: 251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - setuptools ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl + name: pydantic + version: 2.13.4 + sha256: 45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba + requires_dist: + - annotated-types>=0.6.0 + - pydantic-core==2.46.4 + - typing-extensions>=4.14.1 + - typing-inspection>=0.4.2 + - email-validator>=2.0.0 ; extra == 'email' + - tzdata ; python_full_version >= '3.9' and sys_platform == 'win32' and extra == 'timezone' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: pydantic-core + version: 2.46.4 + sha256: 7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_100_cp314.conda + build_number: 100 + sha256: 6d28ac2b061179deb434d3d57afa98ffd20ec3c5d44ab8048a1ca33424b22d38 + md5: 0b9b2f83b5b600e1ac38becde8d0dd44 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.53.2,<4.0a0 + - libuuid >=2.42.1,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.7,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 36717183 + timestamp: 1781255094700 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + build_number: 8 + sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 + md5: 0539938c55b6b1a59b560e843ad864a4 + constrains: + - python 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6989 + timestamp: 1752805904792 +- pypi: https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 345073 + timestamp: 1765813471974 +- pypi: https://files.pythonhosted.org/packages/a3/07/70085c17a369605f15e301d10ab902115019b1126c7253d964afc230c7d6/reportlab-5.0.0-py3-none-any.whl + name: reportlab + version: 5.0.0 + sha256: 9d5a3affa84919e1111ede580031266a570e93b1ce388219621347965ff1d93c + requires_dist: + - pillow>=9.0.0 + - charset-normalizer + - rl-accel>=0.9.0,<1.1 ; extra == 'accel' + - rlpycairo>=0.2.0,<1 ; extra == 'pycairo' + - freetype-py>=2.3.0,<2.4 ; extra == 'pycairo' + - rlbidi ; extra == 'bidi' + - uharfbuzz ; extra == 'shaping' + requires_python: '>=3.9,<4' +- pypi: https://files.pythonhosted.org/packages/49/49/f0dd91ff7d95dd4be65c754fd926cf914bb201eb0ba63c4055755fddeb97/silx-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: silx + version: 3.0.1 + sha256: fddc7e0a0f6fc9cc68758542f5ca579c53d888abaa8ebdb019df41e0b836caf1 + requires_dist: + - numpy + - packaging + - h5py>=3 + - fabio + - pydantic>=2 + - pyopencl ; extra == 'opencl' + - mako ; extra == 'opencl' + - silx[opencl] ; extra == 'full-no-qt' + - qtconsole ; extra == 'full-no-qt' + - matplotlib>=3.6 ; extra == 'full-no-qt' + - pyopengl ; extra == 'full-no-qt' + - python-dateutil ; extra == 'full-no-qt' + - scipy ; extra == 'full-no-qt' + - pooch ; extra == 'full-no-qt' + - pillow ; extra == 'full-no-qt' + - pint ; extra == 'full-no-qt' + - hdf5plugin ; extra == 'full-no-qt' + - qtawesome ; extra == 'full-no-qt' + - silx[full-no-qt] ; extra == 'full' + - pyside6>=6.5 ; extra == 'full' + - h5pyd>=0.20.0 ; extra == 'h5pyd' + - silx[full] ; extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - pydata-sphinx-theme ; extra == 'doc' + - nbsphinx ; extra == 'doc' + - pandoc ; extra == 'doc' + - silx[full,h5pyd] ; extra == 'test' + - bitshuffle ; extra == 'test' + - pytest ; extra == 'test' + - pytest-xvfb ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-httpserver ; extra == 'test' + - silx[doc,full,test] ; extra == 'dev' + - build ; extra == 'dev' + - wheel ; extra == 'dev' + - meson-python ; extra == 'dev' + - ninja ; extra == 'dev' + - cython ; extra == 'dev' + - tomli>=1.0.0 ; python_full_version < '3.11' and extra == 'dev' + - black[jupyter] ; extra == 'dev' + - flake8 ; extra == 'dev' + - pyinstaller>=6.0.0 ; sys_platform == 'win32' and extra == 'dev' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac + md5: cffd3bdd58090148f4cfcd831f4b26ab + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3301196 + timestamp: 1769460227866 +- pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl + name: typing-extensions + version: 4.16.0 + sha256: 481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl + name: typing-inspection + version: 0.4.2 + sha256: 4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 + requires_dist: + - typing-extensions>=4.12.0 + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain + purls: [] + size: 119135 + timestamp: 1767016325805 +- pypi: https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl + name: xmltodict + version: 1.0.4 + sha256: a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a + requires_dist: + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 601375 + timestamp: 1764777111296 diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 0000000..e6397cb --- /dev/null +++ b/pixi.toml @@ -0,0 +1,15 @@ +[workspace] +authors = ["LudoBroche "] +channels = ["conda-forge"] +name = "ewoksdraw" +platforms = ["linux-64"] +version = "0.1.0" + +[dependencies] +python = ">=3.14.6,<3.15" + +[pypi-dependencies] +ewoksdraw = { path = ".", editable = true } + +[tasks] +lint = { cmd = "ruff check --fix . && ruff format .", description = "Auto-fix Ruff lint issues, sort imports, and format Python code" } \ No newline at end of file diff --git a/src/ewoksdraw/svg/svg_element.py b/src/ewoksdraw/svg/svg_element.py index a3ac90e..5992e7a 100644 --- a/src/ewoksdraw/svg/svg_element.py +++ b/src/ewoksdraw/svg/svg_element.py @@ -41,6 +41,10 @@ def set_position( :param x: The x-coordinate to set. If None, the x attribute is not changed. :param y: The y-coordinate to set. If None, the y attribute is not changed. """ + + if self._tag == "path": + return + if self._tag == "circle": attr_x = "cx" attr_y = "cy" From e94627f84a22396da62eb9ac91a3953def5783b7 Mon Sep 17 00:00:00 2001 From: LudoBroche Date: Tue, 7 Jul 2026 17:36:29 +0200 Subject: [PATCH 4/6] Issue #15 -Typo --- .gitignore | 5 +- example_cubic_bezier.svg | 14 - pixi.lock | 643 --------------------------------------- pixi.toml | 15 - 4 files changed, 1 insertion(+), 676 deletions(-) delete mode 100644 example_cubic_bezier.svg delete mode 100644 pixi.lock delete mode 100644 pixi.toml diff --git a/.gitignore b/.gitignore index 1c64863..3d27053 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,4 @@ __pycache__/ *.egg-info/ .eggs/ /doc/_generated -.svg -# pixi environments -.pixi/* -!.pixi/config.toml +.svg \ No newline at end of file diff --git a/example_cubic_bezier.svg b/example_cubic_bezier.svg deleted file mode 100644 index 3bd9997..0000000 --- a/example_cubic_bezier.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/pixi.lock b/pixi.lock deleted file mode 100644 index 2f96754..0000000 --- a/pixi.lock +++ /dev/null @@ -1,643 +0,0 @@ -version: 6 -environments: - default: - channels: - - url: https://conda.anaconda.org/conda-forge/ - indexes: - - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit - packages: - linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.3-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_100_cp314.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/06/04/0efe339b14b339c10802a99723a693750bd9961e0e6166e8e4d3143140bd/ewokscore-4.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/d6/693e1f65bed1eb18d6f3454652fe12fc288f84df90ad42a1ccacd1a6a83b/ewoksutils-1.10.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/44/aa8bc0a660581663fb957d9005851d23736757cae3e58231a8a1030c9f9e/fabio-2026.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/bb/49/7467c2946ccd9617f7da38187071bdc45bb9a95df51f4d63d6622432ce4e/filelock-3.29.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d7/0b/855e50e27eab8338c71c3157672c065cd2a2ab38887b3ea4bf128cbd89a1/hdf5plugin-7.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/07/70085c17a369605f15e301d10ab902115019b1126c7253d964afc230c7d6/reportlab-5.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/49/49/f0dd91ff7d95dd4be65c754fd926cf914bb201eb0ba63c4055755fddeb97/silx-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl - - pypi: ./ -packages: -- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda - build_number: 20 - sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 - md5: a9f577daf3de00bca7c3c76c0ecbd1de - depends: - - __glibc >=2.17,<3.0.a0 - - libgomp >=7.5.0 - constrains: - - openmp_impl <0.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 28948 - timestamp: 1770939786096 -- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - name: annotated-types - version: 0.7.0 - sha256: 1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 - requires_dist: - - typing-extensions>=4.0.0 ; python_full_version < '3.9' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 - md5: d2ffd7602c02f2b316fd921d39876885 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: bzip2-1.0.6 - license_family: BSD - purls: [] - size: 260182 - timestamp: 1771350215188 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - sha256: f8e3c730fa14ee3f170493779f06522c4acf89169f43db4f039727709b6419cf - md5: a9965dd99f683c5f444428f896635716 - depends: - - __unix - license: ISC - purls: [] - size: 128866 - timestamp: 1781708962055 -- pypi: https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: charset-normalizer - version: 3.4.7 - sha256: bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/06/04/0efe339b14b339c10802a99723a693750bd9961e0e6166e8e4d3143140bd/ewokscore-4.0.2-py3-none-any.whl - name: ewokscore - version: 4.0.2 - sha256: 610acda07f799987a329a3683186072d9bc96c1597848090f64eaafc5f769ad5 - requires_dist: - - numpy>=1.15 - - networkx>=2 - - silx>=1 - - pyyaml>=5.1 - - h5py>=2.8 - - packaging - - ewoksutils>=1.9.1 - - importlib-metadata ; python_full_version < '3.9' - - pydantic>=2 - - papermill ; extra == 'notebooks' - - ewokscore[notebooks] ; extra == 'test' - - pytest>=7 ; extra == 'test' - - matplotlib>=3 ; extra == 'test' - - ipykernel ; extra == 'test' - - ewokscore[test] ; extra == 'dev' - - black[jupyter]>=25 ; extra == 'dev' - - flake8>=4 ; extra == 'dev' - - flake8-nb>=0.3.1 ; extra == 'dev' - - isort ; extra == 'dev' - - ewokscore[test] ; extra == 'doc' - - sphinx>=4.5 ; extra == 'doc' - - sphinxcontrib-mermaid>=0.7 ; extra == 'doc' - - sphinx-autodoc-typehints>=1.16 ; extra == 'doc' - - nbsphinx ; extra == 'doc' - - docutils<0.21 ; extra == 'doc' - - nbsphinx-link ; extra == 'doc' - - pydata-sphinx-theme ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - requires_python: '>=3.8' -- pypi: ./ - name: ewoksdraw - version: 0.0.1 - sha256: 2e146b4fd5039229b251cd6d2be4d9aced912344bf6c76aee167ca662ea3b4a5 - requires_dist: - - ewokscore<5 - - reportlab - - xmltodict - - pytest>=7 ; extra == 'test' - - pytest>=7 ; extra == 'dev' - - ruff ; extra == 'dev' - - mypy ; extra == 'dev' - - sphinx>=4.5 ; extra == 'doc' - - sphinx-autodoc-typehints>=1.16 ; extra == 'doc' - - pydata-sphinx-theme ; extra == 'doc' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5e/d6/693e1f65bed1eb18d6f3454652fe12fc288f84df90ad42a1ccacd1a6a83b/ewoksutils-1.10.0-py3-none-any.whl - name: ewoksutils - version: 1.10.0 - sha256: 5e96d097b3fc5e76b8f500070c0a40140761ddde407b39e62c9173dbcbc77690 - requires_dist: - - click ; extra == 'full' - - ewoksutils[full] ; extra == 'test' - - pytest>=7 ; extra == 'test' - - ewoksutils[test] ; extra == 'dev' - - ruff ; extra == 'dev' - - sphinx>=4.5 ; extra == 'doc' - - sphinx-autodoc-typehints>=1.16 ; extra == 'doc' - - pydata-sphinx-theme ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/8f/44/aa8bc0a660581663fb957d9005851d23736757cae3e58231a8a1030c9f9e/fabio-2026.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: fabio - version: 2026.6.0 - sha256: fac184e9fc0d05bf0ffce8f0fc37835426585593818ac41b50f5df64be608dd9 - requires_dist: - - numpy - - h5py - - hdf5plugin - - lxml - - pillow - - filelock - - qtpy ; extra == 'gui' - - pyside6 ; extra == 'gui' - - matplotlib ; extra == 'gui' - - packaging ; extra == 'gui' - - qtpy ; extra == 'all' - - pyside6 ; extra == 'all' - - matplotlib ; extra == 'all' - - packaging ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/bb/49/7467c2946ccd9617f7da38187071bdc45bb9a95df51f4d63d6622432ce4e/filelock-3.29.6-py3-none-any.whl - name: filelock - version: 3.29.6 - sha256: 14d5f5597d2e0c4dbd774cfb6d8132da1db44da83732aab679d54f7dcf97ab65 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl - name: h5py - version: 3.16.0 - sha256: e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd - requires_dist: - - numpy>=1.21.2 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d7/0b/855e50e27eab8338c71c3157672c065cd2a2ab38887b3ea4bf128cbd89a1/hdf5plugin-7.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - name: hdf5plugin - version: 7.0.0 - sha256: 4ad4ab0d3367699d132e61b1cc382a0c640a7dba56536e5105508205ebbe8762 - requires_dist: - - h5py>=3.0.0 - - ipython ; extra == 'doc' - - nbsphinx ; extra == 'doc' - - sphinx ; extra == 'doc' - - sphinx-autodoc-typehints ; extra == 'doc' - - sphinx-rtd-theme ; extra == 'doc' - - packaging ; extra == 'test' - - numpy<2 ; python_full_version == '3.9.*' and extra == 'test' - - blosc2>=2.5.1 ; extra == 'test' - - blosc2-grok>=0.2.2 ; extra == 'test' - - hdf5plugin[doc,test] ; extra == 'dev' - - mypy ; extra == 'dev' - - ruff ; extra == 'dev' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c - md5: 18335a698559cdbcd86150a48bf54ba6 - depends: - - __glibc >=2.17,<3.0.a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - binutils_impl_linux-64 2.45.1 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 728002 - timestamp: 1774197446916 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda - sha256: 16feffd9ddbbe5b718515d38ee376c685ba95491cd901244e24671d20b952a77 - md5: b24d3c612f71e7aa74158d92106318b2 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - expat 2.8.1.* - license: MIT - license_family: MIT - purls: [] - size: 77856 - timestamp: 1781203599810 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 - md5: a360c33a5abe61c07959e449fa1453eb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 58592 - timestamp: 1769456073053 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - sha256: 8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46 - md5: 57736f29cc2b0ec0b6c2952d3f101b6a - depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - constrains: - - libgcc-ng ==15.2.0=*_19 - - libgomp 15.2.0 he0feb66_19 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 1041084 - timestamp: 1778269013026 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - sha256: 5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b - md5: faac990cb7aedc7f3a2224f2c9b0c26c - depends: - - __glibc >=2.17,<3.0.a0 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 603817 - timestamp: 1778268942614 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d - md5: b88d90cad08e6bc8ad540cb310a761fb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - xz 5.8.3.* - license: 0BSD - purls: [] - size: 113478 - timestamp: 1775825492909 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 - md5: 2c21e66f50753a083cbe6b80f38268fa - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 92400 - timestamp: 1769482286018 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.3-h0c1763c_0.conda - sha256: 365376f4815e5e80def2b3462a2419708b7c292da0da85278386c2618621fff4 - md5: 4aed8e657e9ff156bdbe849b4df44389 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.2,<2.0a0 - license: blessing - purls: [] - size: 962119 - timestamp: 1782519076616 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - sha256: 9b1bdce27a7e31f7d241aeecff67a1f3101d52a2b1e33ccc2cdf2613072bf81f - md5: 01bb81d12c957de066ea7362007df642 - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 40017 - timestamp: 1781625522462 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 - md5: d87ff7921124eccd67248aa483c23fec - depends: - - __glibc >=2.17,<3.0.a0 - constrains: - - zlib 1.3.2 *_2 - license: Zlib - license_family: Other - purls: [] - size: 63629 - timestamp: 1774072609062 -- pypi: https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - name: lxml - version: 6.1.1 - sha256: 9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13 - requires_dist: - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 - md5: fc21868a1a5aacc937e7a18747acb8a5 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: X11 AND BSD-3-Clause - purls: [] - size: 918956 - timestamp: 1777422145199 -- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - name: networkx - version: 3.6.1 - sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 - requires_dist: - - asv ; extra == 'benchmarking' - - virtualenv ; extra == 'benchmarking' - - numpy>=1.25 ; extra == 'default' - - scipy>=1.11.2 ; extra == 'default' - - matplotlib>=3.8 ; extra == 'default' - - pandas>=2.0 ; extra == 'default' - - pre-commit>=4.1 ; extra == 'developer' - - mypy>=1.15 ; extra == 'developer' - - sphinx>=8.0 ; extra == 'doc' - - pydata-sphinx-theme>=0.16 ; extra == 'doc' - - sphinx-gallery>=0.18 ; extra == 'doc' - - numpydoc>=1.8.0 ; extra == 'doc' - - pillow>=10 ; extra == 'doc' - - texext>=0.6.7 ; extra == 'doc' - - myst-nb>=1.1 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - osmnx>=2.0.0 ; extra == 'example' - - momepy>=0.7.2 ; extra == 'example' - - contextily>=1.6 ; extra == 'example' - - seaborn>=0.13 ; extra == 'example' - - cairocffi>=1.7 ; extra == 'example' - - igraph>=0.11 ; extra == 'example' - - scikit-learn>=1.5 ; extra == 'example' - - iplotx>=0.9.0 ; extra == 'example' - - lxml>=4.6 ; extra == 'extra' - - pygraphviz>=1.14 ; extra == 'extra' - - pydot>=3.0.1 ; extra == 'extra' - - sympy>=1.10 ; extra == 'extra' - - build>=0.10 ; extra == 'release' - - twine>=4.0 ; extra == 'release' - - wheel>=0.40 ; extra == 'release' - - changelist==0.5 ; extra == 'release' - - pytest>=7.2 ; extra == 'test' - - pytest-cov>=4.0 ; extra == 'test' - - pytest-xdist>=3.0 ; extra == 'test' - - pytest-mpl ; extra == 'test-extras' - - pytest-randomly ; extra == 'test-extras' - requires_python: '>=3.11,!=3.14.1' -- pypi: https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: numpy - version: 2.5.1 - sha256: 54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21 - requires_python: '>=3.12' -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda - sha256: d48f5c22b9897c01e4dff3680f1f57ceb02711ab9c62f74339b080419dfad34b - md5: 79dd2074b5cd5c5c6b2930514a11e22d - depends: - - __glibc >=2.17,<3.0.a0 - - ca-certificates - - libgcc >=14 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 3159683 - timestamp: 1781069855778 -- pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl - name: packaging - version: '26.2' - sha256: 5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: pillow - version: 12.3.0 - sha256: 251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b - requires_dist: - - furo ; extra == 'docs' - - olefile ; extra == 'docs' - - sphinx>=8.2 ; extra == 'docs' - - sphinx-autobuild ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-inline-tabs ; extra == 'docs' - - sphinxext-opengraph ; extra == 'docs' - - olefile ; extra == 'fpx' - - olefile ; extra == 'mic' - - arro3-compute ; extra == 'test-arrow' - - arro3-core ; extra == 'test-arrow' - - nanoarrow ; extra == 'test-arrow' - - pyarrow ; extra == 'test-arrow' - - coverage>=7.4.2 ; extra == 'tests' - - defusedxml ; extra == 'tests' - - markdown2 ; extra == 'tests' - - olefile ; extra == 'tests' - - packaging ; extra == 'tests' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-timeout ; extra == 'tests' - - pytest-xdist ; extra == 'tests' - - setuptools ; extra == 'tests' - - trove-classifiers>=2024.10.12 ; extra == 'tests' - - defusedxml ; extra == 'xmp' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - name: pydantic - version: 2.13.4 - sha256: 45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba - requires_dist: - - annotated-types>=0.6.0 - - pydantic-core==2.46.4 - - typing-extensions>=4.14.1 - - typing-inspection>=0.4.2 - - email-validator>=2.0.0 ; extra == 'email' - - tzdata ; python_full_version >= '3.9' and sys_platform == 'win32' and extra == 'timezone' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: pydantic-core - version: 2.46.4 - sha256: 7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b - requires_dist: - - typing-extensions>=4.14.1 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_100_cp314.conda - build_number: 100 - sha256: 6d28ac2b061179deb434d3d57afa98ffd20ec3c5d44ab8048a1ca33424b22d38 - md5: 0b9b2f83b5b600e1ac38becde8d0dd44 - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.8.1,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - liblzma >=5.8.3,<6.0a0 - - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.53.2,<4.0a0 - - libuuid >=2.42.1,<3.0a0 - - libzlib >=1.3.2,<2.0a0 - - ncurses >=6.6,<7.0a0 - - openssl >=3.5.7,<4.0a0 - - python_abi 3.14.* *_cp314 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - - zstd >=1.5.7,<1.6.0a0 - license: Python-2.0 - purls: [] - size: 36717183 - timestamp: 1781255094700 - python_site_packages_path: lib/python3.14/site-packages -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - build_number: 8 - sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 - md5: 0539938c55b6b1a59b560e843ad864a4 - constrains: - - python 3.14.* *_cp314 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 6989 - timestamp: 1752805904792 -- pypi: https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: pyyaml - version: 6.0.3 - sha256: c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 - md5: d7d95fc8287ea7bf33e0e7116d2b95ec - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - ncurses >=6.5,<7.0a0 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 345073 - timestamp: 1765813471974 -- pypi: https://files.pythonhosted.org/packages/a3/07/70085c17a369605f15e301d10ab902115019b1126c7253d964afc230c7d6/reportlab-5.0.0-py3-none-any.whl - name: reportlab - version: 5.0.0 - sha256: 9d5a3affa84919e1111ede580031266a570e93b1ce388219621347965ff1d93c - requires_dist: - - pillow>=9.0.0 - - charset-normalizer - - rl-accel>=0.9.0,<1.1 ; extra == 'accel' - - rlpycairo>=0.2.0,<1 ; extra == 'pycairo' - - freetype-py>=2.3.0,<2.4 ; extra == 'pycairo' - - rlbidi ; extra == 'bidi' - - uharfbuzz ; extra == 'shaping' - requires_python: '>=3.9,<4' -- pypi: https://files.pythonhosted.org/packages/49/49/f0dd91ff7d95dd4be65c754fd926cf914bb201eb0ba63c4055755fddeb97/silx-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - name: silx - version: 3.0.1 - sha256: fddc7e0a0f6fc9cc68758542f5ca579c53d888abaa8ebdb019df41e0b836caf1 - requires_dist: - - numpy - - packaging - - h5py>=3 - - fabio - - pydantic>=2 - - pyopencl ; extra == 'opencl' - - mako ; extra == 'opencl' - - silx[opencl] ; extra == 'full-no-qt' - - qtconsole ; extra == 'full-no-qt' - - matplotlib>=3.6 ; extra == 'full-no-qt' - - pyopengl ; extra == 'full-no-qt' - - python-dateutil ; extra == 'full-no-qt' - - scipy ; extra == 'full-no-qt' - - pooch ; extra == 'full-no-qt' - - pillow ; extra == 'full-no-qt' - - pint ; extra == 'full-no-qt' - - hdf5plugin ; extra == 'full-no-qt' - - qtawesome ; extra == 'full-no-qt' - - silx[full-no-qt] ; extra == 'full' - - pyside6>=6.5 ; extra == 'full' - - h5pyd>=0.20.0 ; extra == 'h5pyd' - - silx[full] ; extra == 'doc' - - sphinx ; extra == 'doc' - - sphinx-autodoc-typehints ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - sphinx-design ; extra == 'doc' - - pydata-sphinx-theme ; extra == 'doc' - - nbsphinx ; extra == 'doc' - - pandoc ; extra == 'doc' - - silx[full,h5pyd] ; extra == 'test' - - bitshuffle ; extra == 'test' - - pytest ; extra == 'test' - - pytest-xvfb ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-mock ; extra == 'test' - - pytest-httpserver ; extra == 'test' - - silx[doc,full,test] ; extra == 'dev' - - build ; extra == 'dev' - - wheel ; extra == 'dev' - - meson-python ; extra == 'dev' - - ninja ; extra == 'dev' - - cython ; extra == 'dev' - - tomli>=1.0.0 ; python_full_version < '3.11' and extra == 'dev' - - black[jupyter] ; extra == 'dev' - - flake8 ; extra == 'dev' - - pyinstaller>=6.0.0 ; sys_platform == 'win32' and extra == 'dev' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac - md5: cffd3bdd58090148f4cfcd831f4b26ab - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - constrains: - - xorg-libx11 >=1.8.12,<2.0a0 - license: TCL - license_family: BSD - purls: [] - size: 3301196 - timestamp: 1769460227866 -- pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl - name: typing-extensions - version: 4.16.0 - sha256: 481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - name: typing-inspection - version: 0.4.2 - sha256: 4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 - requires_dist: - - typing-extensions>=4.12.0 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c - md5: ad659d0a2b3e47e38d829aa8cad2d610 - license: LicenseRef-Public-Domain - purls: [] - size: 119135 - timestamp: 1767016325805 -- pypi: https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl - name: xmltodict - version: 1.0.4 - sha256: a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a - requires_dist: - - pytest ; extra == 'test' - - pytest-cov ; extra == 'test' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 - md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 - depends: - - __glibc >=2.17,<3.0.a0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 601375 - timestamp: 1764777111296 diff --git a/pixi.toml b/pixi.toml deleted file mode 100644 index e6397cb..0000000 --- a/pixi.toml +++ /dev/null @@ -1,15 +0,0 @@ -[workspace] -authors = ["LudoBroche "] -channels = ["conda-forge"] -name = "ewoksdraw" -platforms = ["linux-64"] -version = "0.1.0" - -[dependencies] -python = ">=3.14.6,<3.15" - -[pypi-dependencies] -ewoksdraw = { path = ".", editable = true } - -[tasks] -lint = { cmd = "ruff check --fix . && ruff format .", description = "Auto-fix Ruff lint issues, sort imports, and format Python code" } \ No newline at end of file From d2b0c45316ac23422404d2863ed66ffe6f96c322 Mon Sep 17 00:00:00 2001 From: LudoBroche Date: Thu, 9 Jul 2026 10:03:53 +0200 Subject: [PATCH 5/6] Issue #15 - Adding unitest --- src/ewoksdraw/tests/test_cubic_bezier_path.py | 88 +++++++++++++++++++ .../tests/test_svg_link_cubic_bezier.py | 69 +++++++++++++++ .../tests/test_svg_path_integration.py | 48 ++++++++++ 3 files changed, 205 insertions(+) create mode 100644 src/ewoksdraw/tests/test_cubic_bezier_path.py create mode 100644 src/ewoksdraw/tests/test_svg_link_cubic_bezier.py create mode 100644 src/ewoksdraw/tests/test_svg_path_integration.py diff --git a/src/ewoksdraw/tests/test_cubic_bezier_path.py b/src/ewoksdraw/tests/test_cubic_bezier_path.py new file mode 100644 index 0000000..7bdf483 --- /dev/null +++ b/src/ewoksdraw/tests/test_cubic_bezier_path.py @@ -0,0 +1,88 @@ +import pytest + +from ewoksdraw.geometry.cubic_bezier_path import CubicBezierPath +from ewoksdraw.geometry.cubic_bezier_path import _direction +from ewoksdraw.geometry.cubic_bezier_path import _distance +from ewoksdraw.geometry.cubic_bezier_path import _move +from ewoksdraw.geometry.cubic_bezier_path import _straight_segment + + +def test_straight_segment(): + segment = _straight_segment((0, 0), (9, 0)) + assert segment.control1 == pytest.approx((3, 0)) + assert segment.control2 == pytest.approx((6, 0)) + assert segment.end == (9, 0) + + +@pytest.mark.parametrize( + "start, end, expected", + [ + ((0, 0), (5, 0), (1, 0)), + ((5, 0), (0, 0), (-1, 0)), + ((0, 0), (0, 5), (0, 1)), + ((0, 5), (0, 0), (0, -1)), + ], +) +def test_direction(start, end, expected): + assert _direction(start, end) == expected + + +@pytest.mark.parametrize( + "start, end, expected", + [ + ((0, 0), (3, 4), 7), + ((0, 0), (0, 0), 0), + ((-1, -1), (1, 1), 4), + ], +) +def test_distance(start, end, expected): + assert _distance(start, end) == expected + + +@pytest.mark.parametrize( + "point, direction, distance, expected", + [ + ((0, 0), (1, 0), 5, (5, 0)), + ((0, 0), (-1, 0), 5, (-5, 0)), + ((0, 0), (0, 1), 5, (0, 5)), + ((0, 0), (0, 1), -5, (0, -5)), + ], +) +def test_move(point, direction, distance, expected): + assert _move(point, direction, distance) == expected + + +def test_from_points_two_points_is_a_single_straight_segment(): + path = CubicBezierPath.from_points(points=[(0, 0), (10, 0)], radius=2) + + assert path.start == (0, 0) + assert len(path.segments) == 1 + assert path.segments[0].end == (10, 0) + + +def test_from_points_single_corner_produces_three_segments(): + path = CubicBezierPath.from_points(points=[(0, 0), (10, 0), (10, 10)], radius=2) + + assert path.start == (0, 0) + assert len(path.segments) == 3 + + straight_before, corner, straight_after = path.segments + assert straight_before.end == (8, 0) + assert corner.end == (10, 2) + assert straight_after.end == (10, 10) + + +def test_from_points_radius_clamped_to_half_shorter_adjacent_segment(): + path = CubicBezierPath.from_points(points=[(0, 0), (2, 0), (2, 10)], radius=100) + + _, corner, _ = path.segments + assert corner.end == (2, 1) + + +def test_from_points_zero_radius_collapses_corner_to_point(): + path = CubicBezierPath.from_points(points=[(0, 0), (10, 0), (10, 10)], radius=0) + + straight_before, corner, straight_after = path.segments + assert straight_before.end == (10, 0) + assert corner.end == (10, 0) + assert straight_after.end == (10, 10) diff --git a/src/ewoksdraw/tests/test_svg_link_cubic_bezier.py b/src/ewoksdraw/tests/test_svg_link_cubic_bezier.py new file mode 100644 index 0000000..22769da --- /dev/null +++ b/src/ewoksdraw/tests/test_svg_link_cubic_bezier.py @@ -0,0 +1,69 @@ +import pytest + +from ewoksdraw.geometry.cubic_bezier_path import CubicBezierPath +from ewoksdraw.geometry.cubic_bezier_path import CubicBezierSegment +from ewoksdraw.svg.svg_link_cubic_bezier import SvgLinkCubicBezier + +SIMPLE_PATH = CubicBezierPath( + start=(0, 0), + segments=[CubicBezierSegment(control1=(1, 1), control2=(2, 2), end=(3, 3))], +) + +TWO_SEGMENT_PATH = CubicBezierPath( + start=(0, 0), + segments=[ + CubicBezierSegment(control1=(1, 1), control2=(2, 2), end=(3, 3)), + CubicBezierSegment(control1=(4, 4), control2=(5, 5), end=(6, 6)), + ], +) + + +def test_d_attribute_starts_with_move_command(): + link = SvgLinkCubicBezier(SIMPLE_PATH) + assert link.get_attr("d") == "M 0,0 C 1,1 2,2 3,3" + + +def test_d_attribute_has_one_curve_command_per_segment(): + link = SvgLinkCubicBezier(TWO_SEGMENT_PATH) + assert link.get_attr("d") == "M 0,0 C 1,1 2,2 3,3 C 4,4 5,5 6,6" + + +def test_raises_when_path_has_no_segments(): + empty_path = CubicBezierPath(start=(0, 0), segments=[]) + with pytest.raises(ValueError): + SvgLinkCubicBezier(empty_path) + + +@pytest.mark.parametrize( + "color, stroke_width, stroke_dash, expected_style", + [ + ("#ff0000", None, None, "stroke:#ff0000"), + (None, 4, None, "stroke-width:4"), + (None, None, "5 10", "stroke-dasharray:5 10"), + ( + "#ff0000", + 4, + "5 10", + "stroke:#ff0000;stroke-width:4;stroke-dasharray:5 10", + ), + (None, None, None, None), + ], +) +def test_style_attribute_combinations(color, stroke_width, stroke_dash, expected_style): + link = SvgLinkCubicBezier( + SIMPLE_PATH, color=color, stroke_width=stroke_width, stroke_dash=stroke_dash + ) + assert link.get_attr("style") == expected_style + + +def test_stroke_width_formatted_without_trailing_zero(): + link = SvgLinkCubicBezier(SIMPLE_PATH, stroke_width=4.0) + assert link.get_attr("style") == "stroke-width:4" + + +def test_xml_element_is_a_path_with_link_class(): + link = SvgLinkCubicBezier(SIMPLE_PATH) + element = link.xml_element + assert element.tag == "path" + assert element.get("class") == "link_cubic_bezier" + assert element.get("d") == "M 0,0 C 1,1 2,2 3,3" diff --git a/src/ewoksdraw/tests/test_svg_path_integration.py b/src/ewoksdraw/tests/test_svg_path_integration.py new file mode 100644 index 0000000..bd5c148 --- /dev/null +++ b/src/ewoksdraw/tests/test_svg_path_integration.py @@ -0,0 +1,48 @@ +from pathlib import Path +from xml.etree import ElementTree + +from ewoksdraw.geometry.cubic_bezier_path import CubicBezierPath +from ewoksdraw.geometry.cubic_bezier_path import CubicBezierSegment +from ewoksdraw.svg.svg_canvas import SvgCanvas +from ewoksdraw.svg.svg_group import SvgGroup +from ewoksdraw.svg.svg_link_cubic_bezier import SvgLinkCubicBezier + +PATH = CubicBezierPath( + start=(0, 0), + segments=[CubicBezierSegment(control1=(1, 1), control2=(2, 2), end=(3, 3))], +) + + +def test_group_translate_moves_path_via_transform_not_d_attribute(): + link = SvgLinkCubicBezier(PATH) + group = SvgGroup() + group.add_elements([link]) + group.translate(5, 5) + + group_element = group.xml_element + assert group_element.get("transform") == "translate(5,5)" + + path_element = group_element[0] + assert path_element.tag == "path" + assert path_element.get("d") == "M 0,0 C 1,1 2,2 3,3" + + +def test_canvas_draw_writes_valid_svg_with_path_element(tmp_path: Path): + output_path = tmp_path / "path.svg" + + link = SvgLinkCubicBezier(PATH, color="#ff0000") + group = SvgGroup() + group.add_elements([link]) + + canvas = SvgCanvas(width=100, height=100) + canvas.add_element(group) + canvas.draw(output_path) + + assert output_path.is_file() + + tree = ElementTree.parse(output_path) + root = tree.getroot() + + path_elements = [element for element in root.iter() if element.tag.endswith("path")] + assert len(path_elements) == 1 + assert path_elements[0].get("d") == "M 0,0 C 1,1 2,2 3,3" From f235daf9909044744806e7eba0e11f27b7231e73 Mon Sep 17 00:00:00 2001 From: LudoBroche Date: Mon, 13 Jul 2026 14:15:52 +0200 Subject: [PATCH 6/6] Issue #15 - Review changes --- .gitignore | 6 ++- src/ewoksdraw/__init__.py | 6 +-- src/ewoksdraw/css_styles/css_task_line.css | 2 +- src/ewoksdraw/geometry/__init__.py | 3 -- src/ewoksdraw/geometry/cubic_bezier_path.py | 3 +- src/ewoksdraw/svg/__init__.py | 10 ----- src/ewoksdraw/svg/svg_canvas.py | 2 +- src/ewoksdraw/svg/svg_element.py | 21 ++++++--- .../tests/test_svg_link_cubic_bezier.py | 44 ++++++++++--------- 9 files changed, 50 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 3d27053..45669d6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,8 @@ __pycache__/ *.egg-info/ .eggs/ /doc/_generated -.svg \ No newline at end of file +.svg + +# Pixi +pixi.toml +pixi.lock \ No newline at end of file diff --git a/src/ewoksdraw/__init__.py b/src/ewoksdraw/__init__.py index f699db1..1c3bb43 100644 --- a/src/ewoksdraw/__init__.py +++ b/src/ewoksdraw/__init__.py @@ -4,10 +4,10 @@ from ewokscore.graph.inputs import _get_all_node_inputs from ewokscore.graph.inputs import _get_all_task_output_names -from .svg import SvgCanvas -from .svg import SvgTask +from .svg.svg_canvas import SvgCanvas +from .svg.svg_task import SvgTask -GAP = 10 +GAP = 10.0 DEFAULT_HEIGHT = 500 diff --git a/src/ewoksdraw/css_styles/css_task_line.css b/src/ewoksdraw/css_styles/css_task_line.css index c917e09..05a295b 100644 --- a/src/ewoksdraw/css_styles/css_task_line.css +++ b/src/ewoksdraw/css_styles/css_task_line.css @@ -1,5 +1,5 @@ .task_line { - stroke: #ffffffff; + stroke: #ffffff; stroke-width: 1; stroke-dasharray: none; } \ No newline at end of file diff --git a/src/ewoksdraw/geometry/__init__.py b/src/ewoksdraw/geometry/__init__.py index 5800bde..e69de29 100644 --- a/src/ewoksdraw/geometry/__init__.py +++ b/src/ewoksdraw/geometry/__init__.py @@ -1,3 +0,0 @@ -from .cubic_bezier_path import CubicBezierPath # noqa: F401 -from .cubic_bezier_path import CubicBezierSegment # noqa: F401 -from .cubic_bezier_path import Point # noqa: F401 diff --git a/src/ewoksdraw/geometry/cubic_bezier_path.py b/src/ewoksdraw/geometry/cubic_bezier_path.py index 3056485..e9d22d5 100644 --- a/src/ewoksdraw/geometry/cubic_bezier_path.py +++ b/src/ewoksdraw/geometry/cubic_bezier_path.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Self from typing import Sequence Point = tuple[float, float] @@ -18,7 +19,7 @@ class CubicBezierPath: segments: Sequence[CubicBezierSegment] @classmethod - def from_points(cls, points: Sequence[Point], radius: float) -> "CubicBezierPath": + def from_points(cls, points: Sequence[Point], radius: float) -> Self: """ Create a rounded cubic Bezier path from horizontal and vertical points. diff --git a/src/ewoksdraw/svg/__init__.py b/src/ewoksdraw/svg/__init__.py index 7b90d0e..e69de29 100644 --- a/src/ewoksdraw/svg/__init__.py +++ b/src/ewoksdraw/svg/__init__.py @@ -1,10 +0,0 @@ -from .svg_background import SvgBackground # noqa: F401 -from .svg_canvas import SvgCanvas # noqa: F401 -from .svg_element import SvgElement # noqa: F401 -from .svg_group import SvgGroup # noqa: F401 -from .svg_task import SvgTask # noqa: F401 -from .svg_task_anchor_link import SvgTaskAnchorLink # noqa: F401 -from .svg_task_box import SvgTaskBox # noqa: F401 -from .svg_task_io import SvgTaskIO # noqa: F401 -from .svg_task_title import SvgTaskTitle # noqa: F401 -from .svg_text import SvgText # noqa: F401 diff --git a/src/ewoksdraw/svg/svg_canvas.py b/src/ewoksdraw/svg/svg_canvas.py index f6af204..058b92b 100644 --- a/src/ewoksdraw/svg/svg_canvas.py +++ b/src/ewoksdraw/svg/svg_canvas.py @@ -39,7 +39,7 @@ class SvgCanvas: SVG XML file. """ - def __init__(self, width: int, height: int): + def __init__(self, width: int | float, height: int | float): self.width = width self.height = height self.elements: List[Union[SvgElement, SvgGroup]] = [] diff --git a/src/ewoksdraw/svg/svg_element.py b/src/ewoksdraw/svg/svg_element.py index 5992e7a..ae48ea0 100644 --- a/src/ewoksdraw/svg/svg_element.py +++ b/src/ewoksdraw/svg/svg_element.py @@ -1,8 +1,14 @@ +import warnings from pathlib import Path +from typing import Final from typing import Literal from typing import Optional +from typing import get_args from xml.etree.ElementTree import Element +SvgTag = Literal["rect", "circle", "text", "line", "path"] +SUPPORTED_TAGS: Final[tuple[SvgTag, ...]] = get_args(SvgTag) + class SvgElement: """ @@ -16,16 +22,14 @@ class SvgElement: def __init__( self, - tag: Literal["rect", "circle", "text", "line", "path"], + tag: SvgTag, css_class: Optional[str] = None, attr: Optional[dict] = None, text: Optional[str] = None, ): - if tag not in ("rect", "circle", "text", "line", "path"): - raise ValueError( - f"Invalid SVG tag: {tag}. Supported tags are 'rect', 'circle', 'text'," - " 'line'." - ) + if tag not in SUPPORTED_TAGS: + supported = ", ".join(SUPPORTED_TAGS) + raise ValueError(f"Invalid SVG tag: {tag}. Supported tags are {supported}.") self._tag = tag self._css_class = css_class @@ -43,6 +47,11 @@ def set_position( """ if self._tag == "path": + warnings.warn( + "set_position() has no effect on 'path' elements; position is " + "defined by the path data instead.", + stacklevel=2, + ) return if self._tag == "circle": diff --git a/src/ewoksdraw/tests/test_svg_link_cubic_bezier.py b/src/ewoksdraw/tests/test_svg_link_cubic_bezier.py index 22769da..06064fc 100644 --- a/src/ewoksdraw/tests/test_svg_link_cubic_bezier.py +++ b/src/ewoksdraw/tests/test_svg_link_cubic_bezier.py @@ -34,33 +34,35 @@ def test_raises_when_path_has_no_segments(): SvgLinkCubicBezier(empty_path) -@pytest.mark.parametrize( - "color, stroke_width, stroke_dash, expected_style", - [ - ("#ff0000", None, None, "stroke:#ff0000"), - (None, 4, None, "stroke-width:4"), - (None, None, "5 10", "stroke-dasharray:5 10"), - ( - "#ff0000", - 4, - "5 10", - "stroke:#ff0000;stroke-width:4;stroke-dasharray:5 10", - ), - (None, None, None, None), - ], -) -def test_style_attribute_combinations(color, stroke_width, stroke_dash, expected_style): - link = SvgLinkCubicBezier( - SIMPLE_PATH, color=color, stroke_width=stroke_width, stroke_dash=stroke_dash - ) - assert link.get_attr("style") == expected_style +def test_style_attribute_has_only_color(): + link = SvgLinkCubicBezier(SIMPLE_PATH, color="#ff0000") + assert link.get_attr("style") == "stroke:#ff0000" -def test_stroke_width_formatted_without_trailing_zero(): +def test_style_attribute_has_only_stroke_width(): link = SvgLinkCubicBezier(SIMPLE_PATH, stroke_width=4.0) assert link.get_attr("style") == "stroke-width:4" +def test_style_attribute_has_only_stroke_dash(): + link = SvgLinkCubicBezier(SIMPLE_PATH, stroke_dash="5 10") + assert link.get_attr("style") == "stroke-dasharray:5 10" + + +def test_style_attribute_combines_color_stroke_width_and_stroke_dash(): + link = SvgLinkCubicBezier( + SIMPLE_PATH, color="#ff0000", stroke_width=4, stroke_dash="5 10" + ) + assert ( + link.get_attr("style") == "stroke:#ff0000;stroke-width:4;stroke-dasharray:5 10" + ) + + +def test_style_attribute_is_none_without_color_stroke_width_or_stroke_dash(): + link = SvgLinkCubicBezier(SIMPLE_PATH) + assert link.get_attr("style") is None + + def test_xml_element_is_a_path_with_link_class(): link = SvgLinkCubicBezier(SIMPLE_PATH) element = link.xml_element