# Copyright 2014, Sandia Corporation. Under the terms of Contract
# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains certain
# rights in this software.
"""Functionality for managing markers (shapes used to highlight datums in plots and text).
"""
import copy
import xml.sax.saxutils
import numpy
import toyplot.style
from toyplot.require import as_float
[docs]
class Marker(object):
"""Represents the complete specification of a marker's appearance."""
def __init__(self, shape, mstyle, size, angle, label, lstyle):
self._shape = shape
self._mstyle = mstyle
self._size = size
self._angle = angle
self._label = label
self._lstyle = lstyle
@property
def shape(self):
return self._shape
@property
def mstyle(self):
return self._mstyle
@property
def size(self):
return self._size
@property
def angle(self):
return self._angle
@property
def label(self):
return self._label
@property
def lstyle(self):
return self._lstyle
def __add__(self, other):
if isinstance(other, str):
return self.to_html() + other
elif isinstance(other, toyplot.marker.Marker):
result = copy.deepcopy(self)
if other._shape is not None:
result._shape = other._shape
result._mstyle = toyplot.style.combine(result.mstyle, other._mstyle)
if other._size is not None:
result._size = other._size
if other._angle is not None:
result._angle = other._angle
if other._label is not None:
result._label = other._label
result._lstyle = toyplot.style.combine(result.lstyle, other._lstyle)
return result
else:
raise ValueError("Can't add toyplot.marker.Marker and %r" % other) # pragma: no cover
def __eq__(self, other):
return self._shape == other._shape and self._mstyle == other._mstyle and self._shape == other._shape and self._angle == other._angle and self._label == other._label and self._lstyle == other._lstyle
def __hash__(self):
return hash((self._shape, self._mstyle, self._size, self._angle, self._label, self._lstyle))
def __radd__(self, other):
return other + self.to_html()
def __repr__(self):
return self.to_html()
def __format__(self, format_spec):
return self.to_html()
[docs]
def to_html(self):
"""Convert a marker specification to HTML markup that can be embedded in rich text."""
return """<marker%s%s%s%s%s%s/>""" % (
" shape='%s'"% xml.sax.saxutils.escape(self._shape) if self._shape else "",
" mstyle='%s'" % toyplot.style.to_css(self._mstyle) if self._mstyle else "",
" size='%s'"% self._size if self._size else "",
" angle='%s'" % self._angle if self._angle else "",
" label='%s'" % xml.sax.saxutils.escape(self._label) if self._label else "",
" lstyle='%s'" % toyplot.style.to_css(self._lstyle) if self._lstyle else "",
)
[docs]
def intersect(self, p):
"""Compute the intersection between this marker's border and a line segment.
Parameters
----------
p: :class:`numpy.ndarray` with shape (2), required
Relative coordinates of a line segment originating at the center of this marker.
Returns
-------
dp: :class:`numpy.ndarray` with shape (2)
Relative coordinates of the intersection with this marker's border.
"""
if self._size:
if self._shape in ["o", "oo", "o|", "o/", "o-", "o\\", "o+", "ox", "o*"]:
p /= numpy.linalg.norm(p)
p *= self._size / 2
return p
if self._shape in ["s"]:
u = numpy.max(numpy.abs(p))
p /= u
p *= self._size / 2
return p
if self._shape and self._shape[0] == "r":
width, height = self._shape[1:].split("x")
width = as_float(width)
height = as_float(height)
ap = numpy.abs(p)
if ap[1]:
if ap[0] / ap[1] > width / height:
p = p / ap[0] * self._size * width / 2
else:
p = p / ap[1] * self._size * height / 2
else:
p = p / ap[0] * self._size * width / 2
return p
return numpy.zeros((2,))
[docs]
def create(shape=None, mstyle=None, size=None, angle=None, label=None, lstyle=None):
"""Factory function for creating instances of :class:`toyplot.marker.Marker`."""
return Marker(shape=shape, mstyle=mstyle, size=size, angle=angle, label=label, lstyle=lstyle)
[docs]
def convert(value):
"""Construct an instance of :class:`toyplot.marker.Marker` from alternative representations."""
if value is None:
return value
if isinstance(value, Marker):
return value
if isinstance(value, str):
return Marker(shape=value, mstyle=None, size=None, angle=None, label=None, lstyle=None)
raise ValueError("Can't convert %r to toyplot.marker.Marker." % value) # pragma: no cover
[docs]
def from_html(html):
"""Convert a parsed xml.etree.ElementTree representation of a marker to a :class:`toyplot.marker.Marker` object."""
size = html.get("size", None)
if size is not None:
size = as_float(size)
angle = html.get("angle", None)
if angle is not None:
angle = as_float(angle)
return Marker(
shape=html.get("shape", None),
mstyle=toyplot.style.parse(html.get("mstyle", "")),
size=size,
angle=angle,
label=html.get("label", None),
lstyle=toyplot.style.parse(html.get("lstyle", "")),
)