-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: #389 initial PoC of SVG backend
- Loading branch information
Showing
11 changed files
with
421 additions
and
55 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from ._svg_backend import SvgBackend | ||
|
||
__all__ = ["SvgBackend"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from abc import ABC, abstractmethod | ||
|
||
from lxml import etree | ||
|
||
from pysketcher._drawable import Drawable | ||
|
||
|
||
class SvgAdapter(ABC): | ||
@staticmethod | ||
@abstractmethod | ||
def plot(shape: Drawable, axes: etree.Element, defs: etree.Element): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import logging | ||
from typing import Callable, Dict, Tuple, Type, Union | ||
|
||
from lxml import etree | ||
import pysketcher as ps | ||
|
||
from pysketcher.backend.backend import Backend | ||
from ._svg_adapter import SvgAdapter | ||
from ._svg_circle import SvgCircle | ||
from ._svg_line import SvgLine | ||
|
||
|
||
class SvgBackend(Backend): | ||
"""Simple interface for plotting. Makes use of Matplotlib for plotting.""" | ||
|
||
_doc: etree.ElementTree | ||
_root: etree.Element | ||
_axes: etree.SubElement | ||
_x_min: float | ||
_y_min: float | ||
_x_max: float | ||
_y_max: float | ||
|
||
def __init__(self, x_min, x_max, y_min, y_max): | ||
self._x_min = x_min | ||
self._x_max = x_max | ||
self._y_min = y_min | ||
self._y_max = y_max | ||
self._camera = None | ||
self._defs = None | ||
self._root = etree.Element( | ||
"svg", | ||
attrib={ | ||
"xmlns": "http://www.w3.org/2000/svg", | ||
"version": "1.1", | ||
"width": f"{x_max - x_min}cm", | ||
"height": f"{y_max - y_min}cm", | ||
}, | ||
) | ||
self._doc = etree.ElementTree(element=self._root) | ||
self._configure_axes() | ||
self._load_defs() | ||
|
||
def _load_defs(self): | ||
self._defs = etree.SubElement(self._root, "defs") | ||
|
||
def _configure_axes(self): | ||
# self._axes = eT.SubElement(self._root, "g", attrib={ | ||
# "transform": f"translate({self._x_min},{self._y_max}) scale(1,-1)" | ||
# }) | ||
self._axes = etree.SubElement(self._root, "g") | ||
|
||
def add(self, shape: ps.Drawable) -> None: | ||
for typ, adapter in self._adapters.items(): | ||
if issubclass(shape.__class__, typ): | ||
adapter.plot(shape, self._axes, self._defs) | ||
|
||
def erase(self): | ||
raise NotImplementedError("Erase is not yet implemented") | ||
|
||
def show(self): | ||
raise NotImplementedError("Show is not yet implemented") | ||
|
||
def save(self, filename: str) -> None: | ||
logging.info(f"Saving to {filename}.") | ||
etree.indent(self._doc, space=" ", level=0) | ||
self._doc.write(filename, xml_declaration=True, encoding="utf-8") | ||
|
||
@property | ||
def _adapters(self) -> Dict[Type, SvgAdapter]: | ||
return { | ||
ps.Circle: SvgCircle(), | ||
ps.Line: SvgLine(), | ||
} | ||
|
||
def animate( | ||
self, | ||
func: Callable[[float], ps.Drawable], | ||
interval: Union[Tuple[float, float], Tuple[float, float, float]], | ||
): | ||
raise NotImplementedError("Animation is not yet implemented") | ||
|
||
def show_animation(self): | ||
raise NotImplementedError("Animation is not yet implemented") | ||
|
||
def save_animation(self, filename: str): | ||
raise NotImplementedError("Animation is not yet implemented") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from lxml import etree | ||
|
||
from ._svg_adapter import SvgAdapter | ||
from ._svg_style import SvgStyle | ||
|
||
import pysketcher as ps | ||
|
||
|
||
class SvgCircle(SvgAdapter): | ||
@staticmethod | ||
def plot(shape: ps.Circle, axes: etree.Element, defs: etree.Element): | ||
etree.SubElement( | ||
axes, | ||
"circle", | ||
attrib={ | ||
"cx": f"{shape.center.x}cm", | ||
"cy": f"{shape.center.y}cm", | ||
"r": f"{shape.radius}cm", | ||
**SvgStyle(shape.style).attribs(defs), | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from lxml import etree | ||
|
||
from ._svg_adapter import SvgAdapter | ||
from ._svg_style import SvgStyle | ||
|
||
import pysketcher as ps | ||
|
||
|
||
class SvgLine(SvgAdapter): | ||
@staticmethod | ||
def plot(shape: ps.Line, axes: etree.Element, defs: etree.Element): | ||
etree.SubElement( | ||
axes, | ||
"line", | ||
attrib={ | ||
"x1": f"{shape.start.x}cm", | ||
"y1": f"{shape.start.y}cm", | ||
"x2": f"{shape.end.x}cm", | ||
"y2": f"{shape.end.y}cm", | ||
**SvgStyle(shape.style).attribs(defs), | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import pkgutil | ||
from typing import Dict | ||
|
||
from lxml import etree | ||
|
||
import pysketcher as ps | ||
|
||
|
||
class SvgStyle: | ||
_style: ps.Style | ||
LINE_STYLE_MAP = { | ||
ps.Style.LineStyle.SOLID: None, | ||
ps.Style.LineStyle.DOTTED: ".1", | ||
ps.Style.LineStyle.DASHED: ".4 .1", | ||
ps.Style.LineStyle.DASH_DOT: ".4 .1 .1 .1", | ||
} | ||
|
||
FILL_PATTERN_MAP = { | ||
ps.Style.FillPattern.CIRCLE: "O", | ||
ps.Style.FillPattern.CROSS: "x", | ||
ps.Style.FillPattern.DOT: ".", | ||
ps.Style.FillPattern.HORIZONTAL: "-", | ||
ps.Style.FillPattern.SQUARE: "+", | ||
ps.Style.FillPattern.STAR: "*", | ||
ps.Style.FillPattern.SMALL_CIRCLE: "o", | ||
ps.Style.FillPattern.VERTICAL: "|", | ||
ps.Style.FillPattern.UP_LEFT_TO_RIGHT: "//", | ||
ps.Style.FillPattern.UP_RIGHT_TO_LEFT: "upRightToLeft", | ||
} | ||
|
||
COLOR_MAP = { | ||
ps.Style.Color.GREY: "Grey", | ||
ps.Style.Color.BLACK: "Black", | ||
ps.Style.Color.BLUE: "Blue", | ||
ps.Style.Color.BROWN: "Brown", | ||
ps.Style.Color.CYAN: "Cyan", | ||
ps.Style.Color.GREEN: "Green", | ||
ps.Style.Color.MAGENTA: "Magenta", | ||
ps.Style.Color.ORANGE: "Orange", | ||
ps.Style.Color.PURPLE: "Purple", | ||
ps.Style.Color.RED: "Red", | ||
ps.Style.Color.YELLOW: "Yellow", | ||
ps.Style.Color.WHITE: "White", | ||
} | ||
|
||
ARROW_MAP = { | ||
ps.Style.ArrowStyle.START: "startArrowHead", | ||
ps.Style.ArrowStyle.END: "endArrowHead", | ||
} | ||
|
||
def __init__(self, style: ps.Style): | ||
self._style = style | ||
|
||
@property | ||
def line_width(self) -> float: | ||
return self._style.line_width | ||
|
||
@property | ||
def line_style(self) -> str: | ||
return self.LINE_STYLE_MAP.get(self._style.line_style) | ||
|
||
@property | ||
def line_color(self): | ||
return self.COLOR_MAP.get(self._style.line_color) | ||
|
||
@property | ||
def fill_color(self): | ||
return self.COLOR_MAP.get(self._style.fill_color) | ||
|
||
@property | ||
def fill_pattern(self): | ||
return self.FILL_PATTERN_MAP.get(self._style.fill_pattern) | ||
|
||
@property | ||
def arrow(self): | ||
return self.ARROW_MAP.get(self._style.arrow) | ||
|
||
def __str__(self): | ||
return ( | ||
"line_style: %s, line_width: %s, line_color: %s," | ||
" fill_pattern: %s, fill_color: %s, arrow: %s" | ||
% ( | ||
self.line_style, | ||
self.line_width, | ||
self.line_color, | ||
self.fill_pattern, | ||
self.fill_color, | ||
self.arrow, | ||
) | ||
) | ||
|
||
def _load_def(self, defs: etree.Element, df: str): | ||
df = etree.fromstring(pkgutil.get_data(__name__, f"templates/{df}.xml")) | ||
defs.append(df) | ||
|
||
def attribs(self, defs: etree.Element) -> Dict[str, str]: | ||
ret_dict = {} | ||
|
||
self._process_line_settings(ret_dict) | ||
self._process_fill_settings(defs, ret_dict) | ||
self._process_arrows(defs, ret_dict) | ||
|
||
return ret_dict | ||
|
||
def _process_fill_settings(self, defs, ret_dict): | ||
ret_dict["fill"] = self.fill_color if self.fill_color else "none" | ||
if self.fill_pattern: | ||
self._load_def(defs, self.fill_pattern) | ||
ret_dict["fill"] = f"url(#{self.fill_pattern})" | ||
|
||
def _process_line_settings(self, ret_dict): | ||
if self.line_width: | ||
ret_dict["stroke-width"] = f"{self.line_width}px" | ||
if self.line_color: | ||
ret_dict["stroke"] = self.line_color | ||
if self.line_style: | ||
ret_dict["stroke-dasharray"] = self.line_style | ||
|
||
def _process_arrows(self, defs, ret_dict): | ||
if self._style.arrow in [ps.Style.ArrowStyle.END, ps.Style.ArrowStyle.DOUBLE]: | ||
end_arrow = self.ARROW_MAP[ps.Style.ArrowStyle.END] | ||
self._load_def(defs, end_arrow) | ||
ret_dict["marker-end"] = f"url(#{end_arrow})" | ||
if self._style.arrow in [ps.Style.ArrowStyle.START, ps.Style.ArrowStyle.DOUBLE]: | ||
start_arrow = self.ARROW_MAP[ps.Style.ArrowStyle.START] | ||
self._load_def(defs, start_arrow) | ||
ret_dict["marker-start"] = f"url(#{start_arrow})" | ||
|
||
|
||
# | ||
# class MatplotlibTextStyle(MatplotlibStyle): | ||
# FONT_FAMILY_MAP = { | ||
# TextStyle.FontFamily.SERIF: "serif", | ||
# TextStyle.FontFamily.SANS: "sans-serif", | ||
# TextStyle.FontFamily.MONO: "monospace", | ||
# } | ||
# | ||
# ALIGNMENT_MAP = { | ||
# TextStyle.Alignment.LEFT: "left", | ||
# TextStyle.Alignment.RIGHT: "right", | ||
# TextStyle.Alignment.CENTER: "center", | ||
# } | ||
# | ||
# _style: TextStyle | ||
# | ||
# def __init__(self, text_style: TextStyle): | ||
# super().__init__(text_style) | ||
# | ||
# @property | ||
# def font_size(self) -> float: | ||
# return self._style.font_size | ||
# | ||
# @property | ||
# def font_family(self) -> str: | ||
# return self.FONT_FAMILY_MAP.get(self._style.font_family) | ||
# | ||
# @property | ||
# def alignment(self) -> str: | ||
# return self.ALIGNMENT_MAP.get(self._style.alignment) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.svg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from pysketcher.backend.svg import SvgBackend | ||
|
||
import numpy as np | ||
import pysketcher as ps | ||
|
||
|
||
class TestSvgBackend: | ||
def test_svg_backend(self): | ||
circle = ps.Circle(ps.Point(1.5, 1.5), 1) | ||
circle.style.line_color = ps.Style.Color.RED | ||
circle.set_fill_pattern(ps.Style.FillPattern.UP_RIGHT_TO_LEFT) | ||
line = ps.Line(ps.Point(1.5, 1.5), circle(-np.pi / 4)) | ||
line.set_arrow(ps.Style.ArrowStyle.DOUBLE) | ||
fig = ps.Figure(0, 3, 0, 3, backend=SvgBackend) | ||
fig.add(circle) | ||
fig.add(line) | ||
fig.save("circle.svg") | ||
assert False |