From 3a0c513ef42b10a977e2cafdb1c16bd704ab6d03 Mon Sep 17 00:00:00 2001 From: Michael Harms Date: Wed, 3 Jul 2024 14:24:30 -0700 Subject: [PATCH 01/15] add as_float, change repr(x) -> str(x) --- features/steps/testing.py | 10 +- features/steps/tick-locator.py | 3 +- toyplot/color.py | 12 +- toyplot/html.py | 215 +++++++++++++++++---------------- toyplot/layout.py | 5 +- toyplot/locator.py | 2 +- toyplot/marker.py | 9 +- toyplot/reportlab/__init__.py | 95 ++++++++------- toyplot/require.py | 38 ++++++ toyplot/style.py | 5 +- toyplot/units.py | 3 +- 11 files changed, 222 insertions(+), 175 deletions(-) diff --git a/features/steps/testing.py b/features/steps/testing.py index d592fe3b..d04c78bc 100644 --- a/features/steps/testing.py +++ b/features/steps/testing.py @@ -20,6 +20,8 @@ import toyplot.require import toyplot.svg +from toyplot.require import as_float + try: import toyplot.pdf except: @@ -146,7 +148,7 @@ def attribute_mismatch(tag, key, avalue, bvalue): def optional_float(value): try: - return float(value) + return as_float(value) except: return value @@ -173,8 +175,8 @@ def assert_path_equal(tag, key, avalue, bvalue): def assert_points_equal(tag, key, avalue, bvalue): - alist = [float(value) for pair in avalue.split() for value in pair.split(",")] - blist = [float(value) for pair in bvalue.split() for value in pair.split(",")] + alist = [as_float(value) for pair in avalue.split() for value in pair.split(",")] + blist = [as_float(value) for pair in bvalue.split() for value in pair.split(",")] assert_mixed_list_equal(alist, blist, tag, key, avalue, bvalue) @@ -205,7 +207,7 @@ def assert_dom_equal(a, b, exceptions): continue if exception.get("type", None) == "float": - if not numpy.allclose(float(avalue), float(bvalue)): + if not numpy.allclose(as_float(avalue), as_float(bvalue)): attribute_mismatch(a.tag, akey, avalue, bvalue) elif exception.get("type", None) == "path": assert_path_equal(a.tag, akey, avalue, bvalue) diff --git a/features/steps/tick-locator.py b/features/steps/tick-locator.py index ea5af381..76c96bbc 100644 --- a/features/steps/tick-locator.py +++ b/features/steps/tick-locator.py @@ -7,6 +7,7 @@ import numpy import sys import toyplot.locator +from toyplot.require import as_float import testing @@ -223,7 +224,7 @@ def step_impl(context): @given(u'a {count} {units} interval') def step_impl(context, count, units): - context.timestamp_interval = (float(count), units) + context.timestamp_interval = (as_float(count), units) @given(u'an interval of days') def step_impl(context): diff --git a/toyplot/color.py b/toyplot/color.py index 8a9395a3..eace1220 100644 --- a/toyplot/color.py +++ b/toyplot/color.py @@ -14,7 +14,7 @@ import numpy import toyplot.projection - +from toyplot.require import as_float black = "#292724" """Default color used throughout Toyplot figures.""" @@ -3217,30 +3217,30 @@ def css(value): match = css.rgb_percent(value) if match: - r, g, b = [float(group) / 100.0 for group in match.groups()] + r, g, b = [as_float(group) / 100.0 for group in match.groups()] return rgba(r, g, b, 1) match = css.rgba(value) if match: r, g, b, a = [ - int(group) / 255.0 for group in match.groups()[:3]] + [float(match.groups()[3])] + int(group) / 255.0 for group in match.groups()[:3]] + [as_float(match.groups()[3])] return rgba(r, g, b, a) match = css.rgba_percent(value) if match: r, g, b, a = [ - float(group) / 100.0 for group in match.groups()[:3]] + [float(match.groups()[3])] + as_float(group) / 100.0 for group in match.groups()[:3]] + [as_float(match.groups()[3])] return rgba(r, g, b, a) match = css.hsl(value) if match: - h, s, l = [float(group) for group in match.groups()] + h, s, l = [as_float(group) for group in match.groups()] r, g, b = colorsys.hls_to_rgb((h / 360.0) % 1, l / 100.0, s / 100.0) return rgba(r, g, b, 1) match = css.hsla(value) if match: - h, s, l, a = [float(group) for group in match.groups()] + h, s, l, a = [as_float(group) for group in match.groups()] r, g, b = colorsys.hls_to_rgb((h / 360.0) % 1, l / 100.0, s / 100.0) return rgba(r, g, b, a) diff --git a/toyplot/html.py b/toyplot/html.py index e9b37da6..9e0b6071 100644 --- a/toyplot/html.py +++ b/toyplot/html.py @@ -29,6 +29,7 @@ import toyplot.mark import toyplot.marker import toyplot.text +from toyplot.require import as_float log = logging.getLogger(__name__) @@ -381,14 +382,14 @@ def _color_fixup(styles): if "fill" in styles: color = toyplot.color.css(styles["fill"]) if color is not None: - opacity = float(styles.get("fill-opacity", 1.0)) + opacity = as_float(styles.get("fill-opacity", 1.0)) styles["fill"] = "rgb(%.3g%%,%.3g%%,%.3g%%)" % ( color["r"] * 100, color["g"] * 100, color["b"] * 100) styles["fill-opacity"] = str(color["a"] * opacity) if "stroke" in styles: color = toyplot.color.css(styles["stroke"]) if color is not None: - opacity = float(styles.get("stroke-opacity", 1.0)) + opacity = as_float(styles.get("stroke-opacity", 1.0)) styles["stroke"] = "rgb(%.3g%%,%.3g%%,%.3g%%)" % ( color["r"] * 100, color["g"] * 100, color["b"] * 100) styles["stroke-opacity"] = str(color["a"] * opacity) @@ -613,8 +614,8 @@ def _draw_bar(parent_xml, size, angle=0): markup = xml.SubElement( parent_xml, "line", - y1=repr(-size / 2), - y2=repr(size / 2), + y1=str(-size / 2), + y2=str(size / 2), ) if angle: markup.set("transform", "rotate(%r)" % (-angle,)) @@ -624,10 +625,10 @@ def _draw_rect(parent_xml, size, width=1, height=1, angle=0): markup = xml.SubElement( parent_xml, "rect", - x=repr(-size / 2 * width), - y=repr(-size / 2 * height), - width=repr(size * width), - height=repr(size * height), + x=str(-size / 2 * width), + y=str(-size / 2 * height), + width=str(size * width), + height=str(size * height), ) if angle: markup.set("transform", "rotate(%r)" % (-angle,)) @@ -651,7 +652,7 @@ def _draw_circle(parent_xml, size): xml.SubElement( parent_xml, "circle", - r=repr(size / 2), + r=str(size / 2), ) def _draw_marker( @@ -709,7 +710,7 @@ def _draw_marker( _draw_rect(marker_xml, marker.size, angle=45) elif marker.shape and marker.shape[0] == "r": width, height = marker.shape[1:].split("x") - _draw_rect(marker_xml, marker.size, width=float(width), height=float(height)) + _draw_rect(marker_xml, marker.size, width=as_float(width), height=as_float(height)) elif marker.shape == "o": _draw_circle(marker_xml, marker.size) elif marker.shape == "oo": @@ -1401,10 +1402,10 @@ def _render(axis, context): xml.SubElement( axis_xml, "line", - x1=repr(x1), - y1=repr(0), - x2=repr(x2), - y2=repr(0), + x1=str(x1), + y1=str(0), + x2=str(x2), + y2=str(0), style=_css_style( axis.spine._style)) @@ -1421,10 +1422,10 @@ def _render(axis, context): xml.SubElement( ticks_group, "line", - x1=repr(x), - y1=repr(y1), - x2=repr(x), - y2=repr(y2), + x1=str(x), + y1=str(y1), + x2=str(x), + y2=str(y2), style=_css_style( axis.ticks._style, tick_style)) @@ -1509,8 +1510,8 @@ def _render(axis, context): coordinates_xml, "line", x1="0", x2="0", - y1=repr(y1), - y2=repr(y2), + y1=str(y1), + y2=str(y2), style=_css_style(axis.interactive.coordinates.tick.style), ) @@ -1520,7 +1521,7 @@ def _render(axis, context): xml.SubElement( coordinates_xml, "text", x="0", - y=repr(y), + y=str(y), style=_css_style(toyplot.style.combine( {"alignment-baseline": alignment_baseline}, axis.interactive.coordinates.label.style, @@ -1685,10 +1686,10 @@ def _render(numberline, context): xml.SubElement( clip_xml, "rect", - x=repr(0), - y=repr(-height), - width=repr(length), - height=repr(height + numberline.axis._offset), + x=str(0), + y=str(-height), + width=str(length), + height=str(height + numberline.axis._offset), ) children_xml = xml.SubElement( @@ -1727,10 +1728,10 @@ def _render(numberline, colormap, context): xml.SubElement( mark_xml, "rect", - x=repr(x1), - y=repr(-width * 0.5), - width=repr(x2 - x1), - height=repr(width), + x=str(x1), + y=str(-width * 0.5), + width=str(x2 - x1), + height=str(width), style=_css_style({"stroke": "none", "fill": toyplot.color.to_css(color)}), ) @@ -1742,10 +1743,10 @@ def _render(numberline, colormap, context): xml.SubElement( mark_xml, "rect", - x=repr(colormap_range_min), - y=repr(-width * 0.5), - width=repr(colormap_range_max - colormap_range_min), - height=repr(width), + x=str(colormap_range_min), + y=str(-width * 0.5), + width=str(colormap_range_max - colormap_range_min), + height=str(width), style=_css_style(style), ) @@ -1774,10 +1775,10 @@ def _render(numberline, colormap, context): defs_xml, "linearGradient", id="t" + uuid.uuid4().hex, - x1=repr(colormap_range_min), - x2=repr(colormap_range_max), - y1=repr(0), - y2=repr(0), + x1=str(colormap_range_min), + x2=str(colormap_range_max), + y1=str(0), + y2=str(0), gradientUnits="userSpaceOnUse", ) @@ -1804,10 +1805,10 @@ def _render(numberline, colormap, context): xml.SubElement( mark_xml, "rect", - x=repr(colormap_range_min), - y=repr(-width * 0.5), - width=repr(colormap_range_max - colormap_range_min), - height=repr(width), + x=str(colormap_range_min), + y=str(-width * 0.5), + width=str(colormap_range_max - colormap_range_min), + height=str(width), style=_css_style(style), ) @@ -1903,10 +1904,10 @@ def _render(numberline, mark, context): series_xml, "rect", attrib={"class": "toyplot-Datum"}, - x=repr(min(dx1, dx2)), - y=repr(-width * 0.5), - width=repr(numpy.abs(dx1 - dx2)), - height=repr(width), + x=str(min(dx1, dx2)), + y=str(-width * 0.5), + width=str(numpy.abs(dx1 - dx2)), + height=str(width), style=_css_style(dstyle), ) if dtitle is not None: @@ -1922,10 +1923,10 @@ def _render(axes, context): xml.SubElement( clip_xml, "rect", - x=repr(axes._xmin_range - axes.padding), - y=repr(axes._ymin_range - axes.padding), - width=repr(axes._xmax_range - axes._xmin_range + axes.padding * 2), - height=repr(axes._ymax_range - axes._ymin_range + axes.padding * 2), + x=str(axes._xmin_range - axes.padding), + y=str(axes._ymin_range - axes.padding), + width=str(axes._xmax_range - axes._xmin_range + axes.padding * 2), + height=str(axes._ymax_range - axes._ymin_range + axes.padding * 2), ) if axes._hyperlink: @@ -1933,10 +1934,10 @@ def _render(axes, context): xml.SubElement( hyperlink_xml, "rect", - x=repr(axes._xmin_range), - y=repr(axes._ymin_range), - width=repr(axes._xmax_range - axes._xmin_range), - height=repr(axes._ymax_range - axes._ymin_range), + x=str(axes._xmin_range), + y=str(axes._ymin_range), + width=str(axes._xmax_range - axes._xmin_range), + height=str(axes._ymax_range - axes._ymin_range), attrib={"fill": "none", "stroke": "none", "pointer-events": "fill"}, ) @@ -2015,10 +2016,10 @@ def _render(axes, context): cell_xml = xml.SubElement( cell_parent_xml, "rect", - x=repr(cell_left), - y=repr(cell_top), - width=repr(cell_right - cell_left), - height=repr(cell_bottom - cell_top), + x=str(cell_left), + y=str(cell_top), + width=str(cell_right - cell_left), + height=str(cell_bottom - cell_top), style=_css_style({"fill":"transparent", "stroke":"none"}, cell_style), ) @@ -2133,36 +2134,36 @@ def contiguous(a): xml.SubElement( axes_xml, "line", - x1=repr(column_boundaries[start]), - y1=repr(y), - x2=repr(column_boundaries[end]), - y2=repr(y), + x1=str(column_boundaries[start]), + y1=str(y), + x2=str(column_boundaries[end]), + y2=str(y), style=_css_style(axes._gstyle), ) elif line_type == "double": xml.SubElement( axes_xml, "line", - x1=repr( + x1=str( column_boundaries[start]), - y1=repr( + y1=str( y - separation), - x2=repr( + x2=str( column_boundaries[end]), - y2=repr( + y2=str( y - separation), style=_css_style( axes._gstyle)) xml.SubElement( axes_xml, "line", - x1=repr( + x1=str( column_boundaries[start]), - y1=repr( + y1=str( y + separation), - x2=repr( + x2=str( column_boundaries[end]), - y2=repr( + y2=str( y + separation), style=_css_style( axes._gstyle)) @@ -2176,29 +2177,29 @@ def contiguous(a): xml.SubElement( axes_xml, "line", - x1=repr(x), - y1=repr(row_boundaries[start]), - x2=repr(x), - y2=repr(row_boundaries[end]), + x1=str(x), + y1=str(row_boundaries[start]), + x2=str(x), + y2=str(row_boundaries[end]), style=_css_style(axes._gstyle), ) elif line_type == "double": xml.SubElement( axes_xml, "line", - x1=repr(x - separation), - y1=repr(row_boundaries[start]), - x2=repr(x - separation), - y2=repr(row_boundaries[end]), + x1=str(x - separation), + y1=str(row_boundaries[start]), + x2=str(x - separation), + y2=str(row_boundaries[end]), style=_css_style(axes._gstyle), ) xml.SubElement( axes_xml, "line", - x1=repr(x + separation), - y1=repr(row_boundaries[start]), - x2=repr(x + separation), - y2=repr(row_boundaries[end]), + x1=str(x + separation), + y1=str(row_boundaries[start]), + x2=str(x + separation), + y2=str(row_boundaries[end]), style=_css_style(axes._gstyle), ) @@ -2283,10 +2284,10 @@ def _render(axes, mark, context): "rect", attrib={ "class": "toyplot-Datum", - axis1: repr(min(dleft, dright)), - axis2: repr(min(dboundary1, dboundary2)), - distance1: repr(numpy.abs(dleft - dright)), - distance2: repr(numpy.abs(dboundary1 - dboundary2)), + axis1: str(min(dleft, dright)), + axis2: str(min(dboundary1, dboundary2)), + distance1: str(numpy.abs(dleft - dright)), + distance2: str(numpy.abs(dboundary1 - dboundary2)), }, style=_css_style(dstyle), ) @@ -2364,10 +2365,10 @@ def _render(axes, mark, context): "rect", attrib={ "class": "toyplot-Datum", - axis1: repr(min(dleft, dright)), - axis2: repr(min(dboundary1, dboundary2)), - distance1: repr(numpy.abs(dleft - dright)), - distance2: repr(numpy.abs(dboundary1 - dboundary2)), + axis1: str(min(dleft, dright)), + axis2: str(min(dboundary1, dboundary2)), + distance1: str(numpy.abs(dleft - dright)), + distance2: str(numpy.abs(dboundary1 - dboundary2)), }, style=_css_style(dstyle), ) @@ -2511,10 +2512,10 @@ def _render(axes, mark, context): "line", attrib={ "class": "toyplot-Datum", - p1: repr(dposition), - p2: repr(dposition), - b1: repr(boundary1), - b2: repr(boundary2), + p1: str(dposition), + p2: str(dposition), + b1: str(boundary1), + b2: str(boundary2), }, style=_css_style(dstyle), ) @@ -2749,9 +2750,9 @@ def _render(axes, mark, context): # pragma: no cover transform += " translate(%r, 0)" % (marker.size / 2,) if marker.angle is not None: if isinstance(marker.angle, str) and marker.angle[0:1] == "r": - angle = float(marker.angle[1:]) + angle = as_float(marker.angle[1:]) else: - angle = -edge_angle + float(marker.angle) + angle = -edge_angle + as_float(marker.angle) transform += " rotate(%r)" % (-angle,) @@ -2784,9 +2785,9 @@ def _render(axes, mark, context): # pragma: no cover )) if marker.angle is not None: if isinstance(marker.angle, str) and marker.angle[0:1] == "r": - angle += float(marker.angle[1:]) + angle += as_float(marker.angle[1:]) else: - angle = float(marker.angle) + angle = as_float(marker.angle) marker = marker + toyplot.marker.create(angle=angle) @@ -2821,9 +2822,9 @@ def _render(axes, mark, context): # pragma: no cover transform += " translate(%r, 0)" % (-marker.size / 2,) if marker.angle is not None: if isinstance(marker.angle, str) and marker.angle[0:1] == "r": - angle = float(marker.angle[1:]) + angle = as_float(marker.angle[1:]) else: - angle = -edge_angle + float(marker.angle) + angle = -edge_angle + as_float(marker.angle) transform += " rotate(%r)" % (-angle,) @@ -2992,10 +2993,10 @@ def _render(axes, mark, context): series_xml, "rect", attrib={"class": "toyplot-Datum"}, - x=repr(min(dx1, dx2)), - y=repr(min(dy1, dy2)), - width=repr(numpy.abs(dx1 - dx2)), - height=repr(numpy.abs(dy1 - dy2)), + x=str(min(dx1, dx2)), + y=str(min(dy1, dy2)), + width=str(numpy.abs(dx1 - dx2)), + height=str(numpy.abs(dy1 - dy2)), style=_css_style(dstyle), ) if dtitle is not None: @@ -3169,9 +3170,9 @@ def _render(mark, context): xml.SubElement( mark_xml, "image", - x=repr(mark._xmin_range), - y=repr(mark._ymin_range), - width=repr(mark._xmax_range - mark._xmin_range), - height=repr(mark._ymax_range - mark._ymin_range), + x=str(mark._xmin_range), + y=str(mark._ymin_range), + width=str(mark._xmax_range - mark._xmin_range), + height=str(mark._ymax_range - mark._ymin_range), attrib={"xlink:href": toyplot.bitmap.to_png_data_uri(mark._data)}, ) diff --git a/toyplot/layout.py b/toyplot/layout.py index db271965..7eaf7f53 100644 --- a/toyplot/layout.py +++ b/toyplot/layout.py @@ -12,6 +12,7 @@ import numpy import toyplot.units +from toyplot.require import as_float def region( xmin, @@ -70,8 +71,8 @@ def convert(vmin, vmax, value): value = toyplot.units.convert( value, "px", default="px", reference=vmax - vmin) if value < 0: - return float(vmax + value) - return float(vmin + value) + return as_float(vmax + value) + return as_float(vmin + value) # Specify explicit bounds for the region if bounds is not None: diff --git a/toyplot/locator.py b/toyplot/locator.py index f42d9610..e8efd227 100644 --- a/toyplot/locator.py +++ b/toyplot/locator.py @@ -347,7 +347,7 @@ def __init__(self, step=1): def ticks(self, domain_min, domain_max): locations = numpy.arange( domain_min, domain_max + 1, self._step, dtype="int64") - labels = [repr(location) for location in locations] + labels = [str(location) for location in locations] titles = numpy.repeat(None, len(locations)) return locations, labels, titles diff --git a/toyplot/marker.py b/toyplot/marker.py index c07b2366..b60476cf 100644 --- a/toyplot/marker.py +++ b/toyplot/marker.py @@ -12,6 +12,7 @@ import numpy import toyplot.style +from toyplot.require import as_float class Marker(object): @@ -118,8 +119,8 @@ def intersect(self, p): return p if self._shape and self._shape[0] == "r": width, height = self._shape[1:].split("x") - width = float(width) - height = float(height) + width = as_float(width) + height = as_float(height) ap = numpy.abs(p) if ap[1]: @@ -154,11 +155,11 @@ 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 = float(size) + size = as_float(size) angle = html.get("angle", None) if angle is not None: - angle = float(angle) + angle = as_float(angle) return Marker( shape=html.get("shape", None), diff --git a/toyplot/reportlab/__init__.py b/toyplot/reportlab/__init__.py index 9005589a..ffab7558 100644 --- a/toyplot/reportlab/__init__.py +++ b/toyplot/reportlab/__init__.py @@ -15,6 +15,7 @@ import toyplot.color import toyplot.units +from toyplot.require import as_float def render(svg, canvas): @@ -47,8 +48,8 @@ def get_fill(root, style): if color is None: return None, None - fill_opacity = float(style.get("fill-opacity", 1.0)) - opacity = float(style.get("opacity", 1.0)) + fill_opacity = as_float(style.get("fill-opacity", 1.0)) + opacity = as_float(style.get("opacity", 1.0)) fill = toyplot.color.rgba( color["r"], color["g"], @@ -65,8 +66,8 @@ def get_stroke(style): if color is None: return None - stroke_opacity = float(style.get("stroke-opacity", 1.0)) - opacity = float(style.get("opacity", 1.0)) + stroke_opacity = as_float(style.get("stroke-opacity", 1.0)) + opacity = as_float(style.get("opacity", 1.0)) return toyplot.color.rgba( color["r"], color["g"], @@ -144,10 +145,10 @@ def render_element(root, element, canvas, styles): styles.append(current_style) if "stroke-width" in current_style: - canvas.setLineWidth(float(current_style["stroke-width"])) + canvas.setLineWidth(as_float(current_style["stroke-width"])) if "stroke-dasharray" in current_style: - canvas.setDash([float(length) for length in current_style["stroke-dasharray"].split(",")]) + canvas.setDash([as_float(length) for length in current_style["stroke-dasharray"].split(",")]) if current_style.get("visibility") != "hidden": @@ -158,14 +159,14 @@ def render_element(root, element, canvas, styles): arguments = arguments.split(",") if transform.strip() == "translate": if len(arguments) == 2: - canvas.translate(float(arguments[0]), float(arguments[1])) + canvas.translate(as_float(arguments[0]), as_float(arguments[1])) elif transform.strip() == "rotate": if len(arguments) == 1: - canvas.rotate(float(arguments[0])) + canvas.rotate(as_float(arguments[0])) if len(arguments) == 3: - canvas.translate(float(arguments[1]), float(arguments[2])) - canvas.rotate(float(arguments[0])) - canvas.translate(-float(arguments[1]), -float(arguments[2])) + canvas.translate(as_float(arguments[1]), as_float(arguments[2])) + canvas.rotate(as_float(arguments[0])) + canvas.translate(-as_float(arguments[1]), -as_float(arguments[2])) if element.tag == "svg": if "background-color" in current_style: @@ -173,20 +174,20 @@ def render_element(root, element, canvas, styles): canvas.rect( 0, 0, - float(element.get("width")[:-2]), - float(element.get("height")[:-2]), + as_float(element.get("width")[:-2]), + as_float(element.get("height")[:-2]), stroke=0, fill=1, ) if current_style["border-style"] != "none": set_stroke_color(canvas, toyplot.color.css(current_style["border-color"])) - canvas.setLineWidth(float(current_style["border-width"])) + canvas.setLineWidth(as_float(current_style["border-width"])) canvas.rect( 0, 0, - float(element.get("width")[:-2]), - float(element.get("height")[:-2]), + as_float(element.get("width")[:-2]), + as_float(element.get("height")[:-2]), stroke=1, fill=0, ) @@ -205,10 +206,10 @@ def render_element(root, element, canvas, styles): clip_path = root.find(".//*[@id='%s']" % clip_id) for child in clip_path: if child.tag == "rect": - x = float(child.get("x")) - y = float(child.get("y")) - width = float(child.get("width")) - height = float(child.get("height")) + x = as_float(child.get("x")) + y = as_float(child.get("y")) + width = as_float(child.get("width")) + height = as_float(child.get("height")) path = canvas.beginPath() path.moveTo(x, y) path.lineTo(x + width, y) @@ -231,10 +232,10 @@ def render_element(root, element, canvas, styles): set_stroke_color(canvas, stroke) canvas.setLineCap(get_line_cap(current_style)) canvas.line( - float(element.get("x1", 0)), - float(element.get("y1", 0)), - float(element.get("x2", 0)), - float(element.get("y2", 0)), + as_float(element.get("x1", 0)), + as_float(element.get("y1", 0)), + as_float(element.get("x2", 0)), + as_float(element.get("y2", 0)), ) elif element.tag == "path": stroke = get_stroke(current_style) @@ -247,10 +248,10 @@ def render_element(root, element, canvas, styles): command = commands.pop(0) if command == "L": path.lineTo( - float(commands.pop(0)), float(commands.pop(0))) + as_float(commands.pop(0)), as_float(commands.pop(0))) elif command == "M": path.moveTo( - float(commands.pop(0)), float(commands.pop(0))) + as_float(commands.pop(0)), as_float(commands.pop(0))) canvas.drawPath(path) elif element.tag == "polygon": fill, fill_gradient = get_fill(root, current_style) @@ -265,9 +266,9 @@ def render_element(root, element, canvas, styles): points = [point.split(",") for point in element.get("points").split()] path = canvas.beginPath() for point in points[:1]: - path.moveTo(float(point[0]), float(point[1])) + path.moveTo(as_float(point[0]), as_float(point[1])) for point in points[1:]: - path.lineTo(float(point[0]), float(point[1])) + path.lineTo(as_float(point[0]), as_float(point[1])) path.close() canvas.drawPath(path, stroke=stroke is not None, fill=fill is not None) elif element.tag == "rect": @@ -278,10 +279,10 @@ def render_element(root, element, canvas, styles): if stroke is not None: set_stroke_color(canvas, stroke) - x = float(element.get("x", 0)) - y = float(element.get("y", 0)) - width = float(element.get("width")) - height = float(element.get("height")) + x = as_float(element.get("x", 0)) + y = as_float(element.get("y", 0)) + width = as_float(element.get("width")) + height = as_float(element.get("height")) path = canvas.beginPath() path.moveTo(x, y) @@ -294,19 +295,19 @@ def render_element(root, element, canvas, styles): pdf_colors = [] pdf_offsets = [] for stop in fill_gradient: - offset = float(stop.get("offset")) + offset = as_float(stop.get("offset")) color = toyplot.color.css(stop.get("stop-color")) - opacity = float(stop.get("stop-opacity")) + opacity = as_float(stop.get("stop-opacity")) pdf_colors.append(reportlab.lib.colors.Color(color["r"], color["g"], color["b"], color["a"] * opacity)) pdf_offsets.append(offset) canvas.saveState() canvas.clipPath(path, stroke=0, fill=1) canvas.setFillAlpha(1) canvas.linearGradient( - float(fill_gradient.get("x1")), - float(fill_gradient.get("y1")), - float(fill_gradient.get("x2")), - float(fill_gradient.get("y2")), + as_float(fill_gradient.get("x1")), + as_float(fill_gradient.get("y1")), + as_float(fill_gradient.get("x2")), + as_float(fill_gradient.get("y2")), pdf_colors, pdf_offsets, ) @@ -323,13 +324,13 @@ def render_element(root, element, canvas, styles): if stroke is not None: set_stroke_color(canvas, stroke) - cx = float(element.get("cx", 0)) - cy = float(element.get("cy", 0)) - r = float(element.get("r")) + cx = as_float(element.get("cx", 0)) + cy = as_float(element.get("cy", 0)) + r = as_float(element.get("r")) canvas.circle(cx, cy, r, stroke=stroke is not None, fill=fill is not None) elif element.tag == "text": - x = float(element.get("x", 0)) - y = float(element.get("y", 0)) + x = as_float(element.get("x", 0)) + y = as_float(element.get("y", 0)) fill, fill_gradient = get_fill(element, current_style) stroke = get_stroke(current_style) font_family = get_font_family(current_style) @@ -357,10 +358,10 @@ def render_element(root, element, canvas, styles): image = PIL.Image.open(image) image = reportlab.lib.utils.ImageReader(image) - x = float(element.get("x", 0)) - y = float(element.get("y", 0)) - width = float(element.get("width")) - height = float(element.get("height")) + x = as_float(element.get("x", 0)) + y = as_float(element.get("y", 0)) + width = as_float(element.get("width")) + height = as_float(element.get("height")) canvas.saveState() path = canvas.beginPath() diff --git a/toyplot/require.py b/toyplot/require.py index 2d632014..fe32ff21 100644 --- a/toyplot/require.py +++ b/toyplot/require.py @@ -110,3 +110,41 @@ def filename(value): def hyperlink(value): """Raise an exception if a value isn't a valid string hyperlink, or None.""" return optional_string(value) + +def as_int(value,precision=None): + """Raise an exception of a value cannot be converted to an int, or value + coerced to a python int. precision is optional but can be 8, 16, etc.""" + + # Try simple conversion; if this fails, move on + try: + return int(value,precision) + except ValueError: + pass + + # If simple conversion failed, try to parse as a np.int64(value) string + try: + return int(value.split("(")[-1].split(")")[0],precision) + except Exception as e: + err = f"'{value}' could not be converted to a float." + raise ValueError(err) from e + + +def as_float(value): + """Raise an exception of a value cannot be converted to a float, or value + coerced to a python float.""" + + # Try simple conversion; if this fails, move on + try: + return float(value) + except ValueError: + pass + + # If simple conversion failed, try to parse as a np.float64(value) string + try: + return float(value.split("(")[-1].split(")")[0]) + except Exception as e: + err = f"'{value}' could not be converted to a float." + raise ValueError(err) from e + + + \ No newline at end of file diff --git a/toyplot/style.py b/toyplot/style.py index 9a2e5e13..1a9427ee 100644 --- a/toyplot/style.py +++ b/toyplot/style.py @@ -10,6 +10,7 @@ import numpy import toyplot.color +from toyplot.require import as_float def require(css, allowed): """Validate that an object is usable as CSS style information. @@ -136,14 +137,14 @@ def _color_fixup(styles): if "fill" in styles: color = toyplot.color.css(styles["fill"]) if color is not None: - opacity = float(styles.get("fill-opacity", 1.0)) + opacity = as_float(styles.get("fill-opacity", 1.0)) styles["fill"] = "rgb(%.3g%%,%.3g%%,%.3g%%)" % ( color["r"] * 100, color["g"] * 100, color["b"] * 100) styles["fill-opacity"] = str(color["a"] * opacity) if "stroke" in styles: color = toyplot.color.css(styles["stroke"]) if color is not None: - opacity = float(styles.get("stroke-opacity", 1.0)) + opacity = as_float(styles.get("stroke-opacity", 1.0)) styles["stroke"] = "rgb(%.3g%%,%.3g%%,%.3g%%)" % ( color["r"] * 100, color["g"] * 100, color["b"] * 100) styles["stroke-opacity"] = str(color["a"] * opacity) diff --git a/toyplot/units.py b/toyplot/units.py index 682141d1..a93f5496 100644 --- a/toyplot/units.py +++ b/toyplot/units.py @@ -8,6 +8,7 @@ import numbers import re +from toyplot.require import as_float def convert(value, target, default=None, reference=None): """Convert quantities using real-world units. @@ -49,7 +50,7 @@ def convert(value, target, default=None, reference=None): if value == "0": return 0 value, units = re.match(r"([^a-zA-Z%]+)([a-zA-Z%]+)\Z", value).groups() - value = (float(value), units) + value = (as_float(value), units) if not isinstance(value, tuple): raise ValueError("Value must be a number, string or (number, string) tuple.") From 51e9b08c0669ae377bb7bab5e28432b9405040cf Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 13:14:31 -0600 Subject: [PATCH 02/15] Remove all animation-related functionality. --- docs/installation.rst | 9 - docs/reference.rst | 1 - docs/toyplot.mp4.rst | 7 - docs/tutorial.ipynb | 1405 +++------------ features/steps/administrivia.py | 1 - features/steps/testing.py | 2 +- features/steps/video.py | 111 -- features/video.feature | 17 - notebooks/animation.ipynb | 2941 ------------------------------- toyplot/canvas.py | 217 --- toyplot/html.py | 244 --- toyplot/mp4.py | 104 -- 12 files changed, 234 insertions(+), 4825 deletions(-) delete mode 100644 docs/toyplot.mp4.rst delete mode 100644 features/steps/video.py delete mode 100644 features/video.feature delete mode 100644 notebooks/animation.ipynb delete mode 100644 toyplot/mp4.py diff --git a/docs/installation.rst b/docs/installation.rst index 160a0357..95b73e7c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,15 +26,6 @@ Ghostscript, which can't be installed via pip. If you use `Conda `_ -(again, strongly recommended), you can install it as follows:: - - $ conda install ffmpeg - .. _documentation: Documentation diff --git a/docs/reference.rst b/docs/reference.rst index a1d859f6..ff9fc663 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -24,7 +24,6 @@ Python API toyplot.locator.rst toyplot.mark.rst toyplot.marker.rst - toyplot.mp4.rst toyplot.pdf.rst toyplot.png.rst toyplot.projection.rst diff --git a/docs/toyplot.mp4.rst b/docs/toyplot.mp4.rst deleted file mode 100644 index 454b1939..00000000 --- a/docs/toyplot.mp4.rst +++ /dev/null @@ -1,7 +0,0 @@ -toyplot.mp4 module -================== - -.. automodule:: toyplot.mp4 - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/tutorial.ipynb b/docs/tutorial.ipynb index cbae5205..d9e49cd2 100644 --- a/docs/tutorial.ipynb +++ b/docs/tutorial.ipynb @@ -3,7 +3,12 @@ { "cell_type": "raw", "metadata": { - "raw_mimetype": "text/restructuredtext" + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "source": [ "Tutorial\n", @@ -14,7 +19,7 @@ "\n", "Welcome! This tutorial will introduce you to the basics of working with Toyplot.\n", "\n", - "Note: this tutorial was created in a [Jupyter](http://www.ipython.org) notebook and assumes that you're following-along in a notebook of your own. If you aren't using a notebook, you should read the user guide section on :ref:`rendering` for some important information on how to display your figures." + "Note: this tutorial was created in a `Jupyter `_ notebook and assumes that you're following-along in a notebook of your own. If you aren't using a notebook, you should read the user guide section on :ref:`rendering` for some important information on how to display your figures." ] }, { @@ -66,7 +71,7 @@ { "data": { "text/html": [ - "
0510050100
" ] }, @@ -440,7 +445,7 @@ { "data": { "text/html": [ - "
0510050100
" ] }, @@ -809,7 +814,7 @@ { "data": { "text/html": [ - "
01020304050050100
" ] }, @@ -1176,7 +1181,7 @@ { "data": { "text/html": [ - "
0510-101
" ] }, @@ -1567,7 +1572,7 @@ { "data": { "text/html": [ - "
0510-101
" ] }, @@ -1966,7 +1971,7 @@ "
" ], "text/plain": [ - "" + "" ] }, "execution_count": 9, @@ -2026,7 +2031,7 @@ { "data": { "text/html": [ - "
0.00.51.0-4-2024
" ] }, @@ -2381,7 +2386,7 @@ { "data": { "text/html": [ - "
0.00.51.0-4-2024
" ] }, @@ -2737,7 +2742,7 @@ { "data": { "text/html": [ - "
0.00.51.00123
" ] }, @@ -3092,7 +3097,7 @@ { "data": { "text/html": [ - "
010203040500123
" ] }, @@ -3461,7 +3466,7 @@ { "data": { "text/html": [ - "
01020304050-4-2024
" ] }, @@ -3818,7 +3823,7 @@ { "data": { "text/html": [ - "
01020304050-4-2024
" ] }, @@ -4173,7 +4178,7 @@ { "data": { "text/html": [ - "
01020304050-4-2024
" ] }, @@ -4539,7 +4544,7 @@ { "data": { "text/html": [ - "
01020304050-4-2024
" ] }, @@ -4905,7 +4910,7 @@ { "data": { "text/html": [ - "
1st Quartile2nd Quartile3rd Quartile4th Quartile01020304050-4-2024
" ] }, @@ -5278,7 +5283,7 @@ { "data": { "text/html": [ - "
05010004812
" ] }, @@ -5642,7 +5647,7 @@ { "data": { "text/html": [ - "
05010004812
" ] }, @@ -6006,7 +6011,7 @@ { "data": { "text/html": [ - "
05010004812
" ] }, @@ -6361,7 +6366,7 @@ { "data": { "text/html": [ - "
050100-505
" ] }, @@ -6709,7 +6714,7 @@ { "data": { "text/html": [ - "
050100-4048
" ] }, @@ -7082,7 +7087,7 @@ { "data": { "text/html": [ - "
0510050100
" ] }, @@ -7437,7 +7442,7 @@ { "data": { "text/html": [ - "
-2-1012012345
" ] }, @@ -7807,7 +7812,7 @@ { "data": { "text/html": [ - "
-4-2024050010001500
" ] }, @@ -8175,7 +8180,7 @@ { "data": { "text/html": [ - "
05100306090
" ] }, @@ -8541,7 +8546,7 @@ { "data": { "text/html": [ - "
Series 1Series 1Series 1Series 1Series 1Series 1Series 1Series 1Series 1Series 1Series 2Series 2Series 2Series 2Series 2Series 2Series 2Series 2Series 2Series 2Series 3Series 3Series 3Series 3Series 3Series 3Series 3Series 3Series 3Series 3Series 4Series 4Series 4Series 4Series 4Series 4Series 4Series 4Series 4Series 405100306090
" ] }, @@ -8906,7 +8911,7 @@ { "data": { "text/html": [ - "
0.671180366490.1335326337740.5541205739780.2517039739570.9205897480110.7759288665580.1347204372830.4825836850710.08188993451240.3926659033060.8883088878670.9901663250710.9124180775430.1981534929710.9520176342640.8283304454050.3773021026340.1720444573530.002482950217810.4628787394180.5424529654880.9359602075270.6052180652360.163638000510.5212729044720.02115913445260.7154891795510.5053269667880.9288410165950.06828732321960.9450110629430.3526752535930.4021094467720.8678650687380.5422079096990.7801711435650.9609649388790.1872860863710.6640783903610.35655134502805100306090
" ] }, @@ -9277,7 +9282,7 @@ { "data": { "text/html": [ - "
0246-1.0-0.50.00.51.0
" ] }, @@ -9632,7 +9637,7 @@ { "data": { "text/html": [ - "
01020304050-1.0-0.50.00.51.0
" ] }, @@ -9987,7 +9992,7 @@ { "data": { "text/html": [ - "
01020304050-1.0-0.50.00.51.0
" ] }, @@ -10344,7 +10349,7 @@ { "data": { "text/html": [ - "
01020304050-1.0-0.50.00.51.0
" ] }, @@ -10710,7 +10715,7 @@ { "data": { "text/html": [ - "
01020304050-1.0-0.50.00.51.0
" ] }, @@ -11079,7 +11084,7 @@ { "data": { "text/html": [ - "
111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222201020304050-1.0-0.50.00.51.0
" ] }, @@ -11458,7 +11463,7 @@ { "data": { "text/html": [ - "
0510-101
" ] }, @@ -11820,7 +11825,7 @@ { "data": { "text/html": [ - "
0510-1.0-0.50.00.51.00510-1.0-0.50.00.51.0
" ] }, @@ -12202,7 +12207,7 @@ { "data": { "text/html": [ - "
0510Days50100150UsersToyplot User Growth
" ] }, @@ -12557,7 +12562,7 @@ { "data": { "text/html": [ - "
0510Days050100150UsersToyplot User Growth
" ] }, @@ -12921,7 +12926,7 @@ { "data": { "text/html": [ - "
-1000-50005001000-10 3-10 2-10 1-10 0010 010 110 210 3
" ] }, @@ -13258,950 +13263,6 @@ "source": [ "toyplot.plot(x, x, marker=\"o\", xscale=\"linear\", yscale=\"log\", width=500);" ] - }, - { - "cell_type": "raw", - "metadata": { - "raw_mimetype": "text/restructuredtext" - }, - "source": [ - "There are many more properties that control axes positioning and behavior - for more details, see :ref:`canvas-layout` and :ref:`cartesian-coordinates` in the user guide." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Animation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Toyplot can also create animated figures, by recording changes to a figure over time. Assume you've setup the following scatterplot:" - ] - }, - { - "cell_type": "code", - "execution_count": 57, - "metadata": {}, - "outputs": [], - "source": [ - "x = numpy.random.normal(size=100)\n", - "y = numpy.random.normal(size=len(x))" - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
-202-2-1012
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "canvas = toyplot.Canvas(300, 300)\n", - "axes = canvas.cartesian()\n", - "mark = axes.scatterplot(x, y, size=10)" - ] - }, - { - "cell_type": "raw", - "metadata": { - "raw_mimetype": "text/restructuredtext" - }, - "source": [ - "Suppose we want to show the order in which the samples were drawn from some distribution. We could use the `fill` parameter to map each sample's index to a color, but an animation can be more intuitive. We can use :meth:`toyplot.canvas.Canvas.frames` to add a sequence of animation frames to the canvas. We pass the desired number of frames as an argument, iterate over the results which are instances of :class:`frame `. We can use the frame objects to retrieve information about each frame and specify changes to be made to the canvas at that frame. In the example below, we set the opacity of each scatterplot datum to 10% in the first frame, then change them back to 100% over the course of the animation:" - ] - }, - { - "cell_type": "code", - "execution_count": 59, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
-202-2-1012
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "canvas = toyplot.Canvas(300, 300)\n", - "axes = canvas.cartesian()\n", - "mark = axes.scatterplot(x, y, size=10)\n", - "\n", - "for frame in canvas.frames(len(x) + 1):\n", - " if frame.number == 0:\n", - " for i in range(len(x)):\n", - " frame.set_datum_style(mark, 0, i, style={\"opacity\":0.1})\n", - " else:\n", - " frame.set_datum_style(mark, 0, frame.number - 1, style={\"opacity\":1.0})" - ] } ], "metadata": { @@ -14221,9 +13282,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.11.8" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/features/steps/administrivia.py b/features/steps/administrivia.py index 0de9de35..01cd96d3 100644 --- a/features/steps/administrivia.py +++ b/features/steps/administrivia.py @@ -3,7 +3,6 @@ # rights in this software. from behave import * -import nose.tools import os import pkgutil diff --git a/features/steps/testing.py b/features/steps/testing.py index d04c78bc..ae338cca 100644 --- a/features/steps/testing.py +++ b/features/steps/testing.py @@ -338,7 +338,7 @@ def read_png(fobj): if meta["bitdepth"] == 1: image = numpy.resize(numpy.vstack(pixels), (height, width, planes)) elif meta["bitdepth"] == 8: - image = numpy.resize(numpy.vstack(map(numpy.uint8, pixels)), (height, width, planes)) + image = numpy.resize(numpy.vstack(list(map(numpy.uint8, pixels))), (height, width, planes)) return image diff --git a/features/steps/video.py b/features/steps/video.py deleted file mode 100644 index b048beac..00000000 --- a/features/steps/video.py +++ /dev/null @@ -1,111 +0,0 @@ -# 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. - -from behave import * -import nose.tools - -import collections -import io -import json -import logging -import numpy.testing -import os -import subprocess -import sys -import tempfile - -import testing - -try: - import toyplot.mp4 -except: - pass - -try: - import toyplot.png -except: - pass - - -@given(u'an animated canvas') -def step_impl(context): - context.canvas = toyplot.Canvas( - style={"background-color": "white"}, width=600, height=600) - axes = context.canvas.cartesian() - scatterplot = axes.scatterplot(numpy.arange(10)) - - for frame in context.canvas.frames(11): - if frame.number == 0: - for i in numpy.arange(10): - frame.set_datum_style(scatterplot, 0, i, {"opacity": 0}) - else: - frame.set_datum_style( - scatterplot, 0, frame.number - 1, {"opacity": 1}) - - -@then(u'the canvas can be rendered as {type} video') -def step_impl(context, type): - nose.tools.assert_in(type, ["mp4"]) - - def progress(frame): - pass - context.path = os.path.join(tempfile.mkdtemp(), "test.%s" % type) - context.backend.render(context.canvas, context.path, progress=progress) - sys.stderr.write("**** %s ****\n" % context.path) - sys.stderr.flush() - - command = ["ffprobe", "-print_format", "json", "-show_format", - "-show_streams", "-count_frames", context.path] - ffprobe = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = ffprobe.communicate() - video_metadata = json.loads(stdout.decode()) - logging.info("video metadata: %s", video_metadata) - video_format = video_metadata["format"] - nose.tools.assert_equal(video_format["nb_streams"], 1) - nose.tools.assert_in(type, video_format["format_name"]) - video_stream = video_metadata["streams"][0] - nose.tools.assert_equal( - video_stream["codec_name"], "h264" if type == "mp4" else "vp8") - nose.tools.assert_equal(video_stream["codec_type"], "video") - nose.tools.assert_equal(video_stream["width"], 600) - nose.tools.assert_equal(video_stream["height"], 600) - nose.tools.assert_equal(video_stream["nb_read_frames"], "11") - - -@then(u'the canvas can be rendered as png frames') -def step_impl(context): - for frame in toyplot.png.render_frames(context.canvas): - image = testing.read_png(io.BytesIO(frame)) - nose.tools.assert_equal(image.shape, (600, 600, 4)) - nose.tools.assert_equal(image.dtype, "uint8") - - -@when(u'an animation frame is created, its fields are populated correctly.') -def step_impl(context): - frame = toyplot.canvas.AnimationFrame(number=1, begin=2.3, end=2.4, count=1, changes=collections.defaultdict(lambda: collections.defaultdict(list))) - nose.tools.assert_equal(frame.number, 1) - nose.tools.assert_equal(frame.begin, 2.3) - numpy.testing.assert_almost_equal(frame.length, 0.1) - with nose.tools.assert_raises(ValueError): - frame.set_mark_style(None, {}) - with nose.tools.assert_raises(ValueError): - frame.set_datum_style(None, 0, 0, {}) - - -@when(u'a canvas is used to create an animation frame, its fields are populated correctly.') -def step_impl(context): - canvas = toyplot.Canvas() - frame = canvas.frame(0.3, 0.4) - nose.tools.assert_equal(frame.begin, 0.3) - numpy.testing.assert_almost_equal(frame.length, 0.1) - nose.tools.assert_equal(frame.number, 0) - - frame = canvas.frame(0.3, 0.4, 5) - nose.tools.assert_equal(frame.begin, 0.3) - numpy.testing.assert_almost_equal(frame.length, 0.1) - nose.tools.assert_equal(frame.number, 5) - - - diff --git a/features/video.feature b/features/video.feature deleted file mode 100644 index feaabb3b..00000000 --- a/features/video.feature +++ /dev/null @@ -1,17 +0,0 @@ -Feature: Video - Scenario: Animation frame - When an animation frame is created, its fields are populated correctly. - - Scenario: Canvas frame - When a canvas is used to create an animation frame, its fields are populated correctly. - - Scenario Outline: Render animated canvas - Given that the backend is available - And an animated canvas - Then the canvas can be rendered as - - Examples: - | backend | format | - | toyplot.png | png frames | - | toyplot.mp4 | mp4 video | - diff --git a/notebooks/animation.ipynb b/notebooks/animation.ipynb deleted file mode 100644 index e1cbf4e5..00000000 --- a/notebooks/animation.ipynb +++ /dev/null @@ -1,2941 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Animation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Toyplot can also create animated figures, by recording changes to a figure over time. Assume you've setup the following scatterplot:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy\n", - "x = numpy.random.normal(size=100)\n", - "y = numpy.random.normal(size=len(x))" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
-2-1012-2-1012
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import toyplot\n", - "canvas = toyplot.Canvas(300, 300)\n", - "axes = canvas.cartesian()\n", - "mark = axes.scatterplot(x, y, size=10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Suppose we want to show the order in which the samples were drawn from some distribution. We could use the `fill` parameter to map each sample's index to a color, but an animation can be more intuitive. We can use :meth:`toyplot.canvas.Canvas.animate` to add a sequence of animation frames to the canvas. We pass the number of frames and a callback function as arguments, and the callback function will be called once per frame with a single :class:`frame ` argument. The callback uses the frame object to retrieve information about the frame and record any changes that should be made to the canvas at that frame. In the example below, we set the opacity of each scatterplot datum to 5% in the first frame, then change them back to 100% over the course of the animation:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "
-2-1012-2-1012
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "canvas = toyplot.Canvas(300, 300)\n", - "axes = canvas.cartesian()\n", - "mark = axes.scatterplot(x, y, size=10)\n", - "\n", - "for frame in canvas.frames(len(x) + 1):\n", - " if frame.number == 0:\n", - " for i in range(len(x)):\n", - " frame.set_datum_style(mark, 0, i, style={\"opacity\":0.1})\n", - " else:\n", - " frame.set_datum_style(mark, 0, frame.number - 1, style={\"opacity\":1.0})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try animating something other than a datum style - in the following example, we add a text mark to the canvas, and use it to display information about the frame:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
-2-1012-2-1012
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "canvas = toyplot.Canvas(300, 300)\n", - "axes = canvas.cartesian()\n", - "mark = axes.scatterplot(x, y, size=10)\n", - "text = canvas.text(150, 20, \" \")\n", - "\n", - "for frame in canvas.frames(len(x) + 1):\n", - " label = \"%s/%s (%.2f s)\" % (frame.number + 1, frame.count, frame.begin)\n", - " frame.set_datum_text(text, 0, 0, label)\n", - " \n", - " if frame.number == 0:\n", - " for i in range(len(x)):\n", - " frame.set_datum_style(mark, 0, i, style={\"opacity\":0.05})\n", - " else:\n", - " frame.set_datum_style(mark, 0, frame.number - 1, style={\"opacity\":1.0})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note from this example that each frame has a zero-based frame index, along with begin and end times, which are measured in seconds. If you look closely, you'll see that the difference in begin and end times is 0.03 seconds for each frame, which corresponds to a default 30 frames per second. If we want to control the framerate, we can pass a (frames, framerate) tuple when we call :meth:`toyplot.canvas.Canvas.animate` (note that the playback is slower, and the times for the frames are changed):" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
-2-1012-2-1012
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "canvas = toyplot.Canvas(300, 300)\n", - "axes = canvas.cartesian()\n", - "mark = axes.scatterplot(x, y, size=10)\n", - "text = canvas.text(150, 20, \" \")\n", - "\n", - "for frame in canvas.frames((len(x) + 1, 5)):\n", - " label = \"%s/%s (%.2f s)\" % (frame.number + 1, frame.count, frame.begin)\n", - " frame.set_datum_text(text, 0, 0, label)\n", - "\n", - " if frame.number == 0:\n", - " for i in range(len(x)):\n", - " frame.set_datum_style(mark, 0, i, style={\"opacity\":0.05})\n", - " else:\n", - " frame.set_datum_style(mark, 0, frame.number - 1, style={\"opacity\":1.0})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sometimes the callback approach to animation is awkward, particularly if you simply have a one-time \"event\" that needs to happen in the middle of the animation. In this case, you can use :meth:`toyplot.canvas.Canvas.time` to record changes for individual frames:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
-2-1012-2-1012
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "canvas = toyplot.Canvas(300, 300)\n", - "axes = canvas.cartesian()\n", - "mark = axes.scatterplot(x, y, size=10)\n", - "text = canvas.text(150, 20, \" \")\n", - "text2 = canvas.text(150, 35, \" \")\n", - "\n", - "for frame in canvas.frames(len(x) + 1):\n", - " label = \"%s/%s (%.2f s)\" % (frame.number + 1, frame.count, frame.begin)\n", - " frame.set_datum_text(text, 0, 0, label)\n", - "\n", - " if frame.number == 0:\n", - " for i in range(len(x)):\n", - " frame.set_datum_style(mark, 0, i, style={\"opacity\":0.05})\n", - " else:\n", - " frame.set_datum_style(mark, 0, frame.number - 1, style={\"opacity\":1.0})\n", - "\n", - "canvas.frame(0.0).set_datum_text(text2, 0, 0, \"Halfway There!\", style={\"font-weight\":\"bold\", \"opacity\":0.2})\n", - "canvas.frame(50.0 * (1.0 / 30.0)).set_datum_text(text2, 0, 0, \"Halfway There!\", style={\"font-weight\":\"bold\", \"fill\":\"blue\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that when you combine :meth:`toyplot.canvas.Canvas.animate` and :meth:`toyplot.canvas.Canvas.time`, you don't have to force the \"frames\" to line-up ... you can record events in any order and at any point in time, whether there are existing frames at those times or not. In fact, you could call :meth:`toyplot.canvas.Canvas.animate` multiple times, if you wanted to animate events happening at different rates:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "canvas = toyplot.Canvas(100, 100, style={\"background-color\":\"ivory\"})\n", - "t1 = canvas.text(50, 33, \" \")\n", - "t2 = canvas.text(50, 66, \" \")\n", - "\n", - "for frame in canvas.frames((10, 2)):\n", - " frame.set_datum_text(t1, 0, 0, \"1 hz\", style={\"opacity\":frame.number % 2.0})\n", - "\n", - "for frame in canvas.frames((20, 4)):\n", - " frame.set_datum_text(t2, 0, 0, \"2 hz\", style={\"opacity\":frame.number % 2.0})" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n", - "1\n", - "2\n", - "3\n", - "4\n", - "5\n", - "6\n", - "7\n", - "8\n", - "9\n", - "10\n", - "11\n", - "12\n", - "13\n", - "14\n", - "15\n", - "16\n", - "17\n", - "18\n", - "19\n" - ] - } - ], - "source": [ - "import toyplot.mp4\n", - "toyplot.mp4.render(canvas, \"test.mp4\", progress=print)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.3" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/toyplot/canvas.py b/toyplot/canvas.py index 194c3567..646b3fb5 100644 --- a/toyplot/canvas.py +++ b/toyplot/canvas.py @@ -20,129 +20,6 @@ import toyplot.units -class AnimationFrame(object): - """Used to specify modifications to a `toyplot.canvas.Canvas` for animation. - - Do not create AnimationFrame instances yourself, use - :meth:`toyplot.canvas.Canvas.frame` and - :meth:`toyplot.canvas.Canvas.frames` instead. - """ - def __init__(self, changes, count, number, begin, end): - self._changes = changes - self._count = count - self._number = number - self._begin = begin - self._end = end - - @property - def count(self): - """Total number of frames in the current animation. - """ - return self._count - - @property - def number(self): - """Current animation frame number (zero-based). - """ - return self._number - - @property - def begin(self): - """Beginning of the current frame in seconds. - """ - return self._begin - - @property - def end(self): - """End of the current frame in seconds. - """ - return self._end - - @property - def length(self): - """Duration of the current frame in seconds. - """ - return self._end - self._begin - - def set_mark_style(self, mark, style): - """Change the style of a mark. - - Parameters - ---------- - mark: :class:`toyplot.mark.Mark` instance - style: dict containing CSS style information - """ - if not isinstance(mark, toyplot.mark.Mark): - raise ValueError( - "Mark style can only be set on toyplot.mark.Mark instances.") - - self._changes.add(self._begin, self._end, "set-mark-style", dict(mark=mark, style=style)) - - def set_datum_style(self, mark, series, datum, style): - """Change the style of one datum in a :class:`toyplot.mark.Mark` at the current frame. - - Parameters - ---------- - mark: :class:`toyplot.mark.Mark`, required - Mark containing the datum to modify. - series: int, required - Zero-based index of the series containing the datum to modify. - datum: int, required - Zero-based index of the datum to modify. - style: dict, required - Python dict containing CSS style information to be assigned to the datum. - """ - if not isinstance(mark, ( - toyplot.mark.BarBoundaries, - toyplot.mark.BarMagnitudes, - toyplot.mark.Plot, - toyplot.mark.Point, - )): - raise ValueError("Cannot set datum style for %s." % type(mark)) - - self._changes.add(self._begin, self._end, "set-datum-style", dict(mark=mark, series=series, datum=datum, style=style)) - - def set_text(self, mark, text, style=None): - self.set_datum_text(mark, 0, 0, text, style) - - def set_datum_text(self, mark, series, datum, text, style=None): - """Change the text in a :class:`toyplot.mark.Text` at the current frame. - - Note - ---- - Currently, animated text completely overwrites the original - thus, it - cannot inherit style (including color) information, which you will have - to re-specify explicitly. - - Parameters - ---------- - mark: :class:`toyplot.mark.Text`, required - Mark containing the text to modify. - series: int, required - Zero-based index of the series containing the datum to modify. - datum: int, required - Zero-based index of the datum to modify. - text: str, required - String containing the new text to be assigned to the datum. - style: dict, optional - Python dict containing CSS style information to be assigned to the datum. - angle: float, optional - Angle for the new text. - """ - if not isinstance(mark, toyplot.mark.Text): - raise ValueError("mark must be an instance of toyplot.mark.Text.") - if not isinstance(series, int): - raise ValueError("series must be an integer index.") - if not isinstance(datum, int): - raise ValueError("datum must be an integer index.") - if not isinstance(text, str): - raise ValueError("text must be a string.") - if not isinstance(style, (dict, type(None))): - raise ValueError("style must be a dict or None.") - - self._changes.add(self._begin, self._end, "set-datum-text", dict(mark=mark, series=series, datum=datum, text=text, style=style)) - - ########################################################################## # Canvas @@ -180,19 +57,6 @@ class Canvas(object): >>> canvas = toyplot.Canvas("8in", "6in", style={"background-color":"yellow"}) """ - class _AnimationChanges(object): - def __init__(self): - self._begin = [] - self._end = [] - self._change = [] - self._state = [] - - def add(self, begin, end, change, state): - self._begin.append(begin) - self._end.append(end) - self._change.append(change) - self._state.append(state) - def __init__(self, width=None, height=None, style=None, hyperlink=None, autorender=None, autoformat=None): if width is None: width = toyplot.config.width @@ -219,7 +83,6 @@ def __init__(self, width=None, height=None, style=None, hyperlink=None, autorend self.hyperlink = hyperlink self.style = style - self._changes = toyplot.Canvas._AnimationChanges() self._scenegraph = toyplot.scenegraph.SceneGraph() self.autorender(autorender, autoformat) @@ -271,86 +134,6 @@ def width(self, value): self._width = toyplot.units.convert(value, "px", default="px") - def frame(self, begin, end=None, number=None, count=None): - """Return a single animation frame that can be used to animate the canvas contents. - - Parameters - ---------- - begin: float - Specify the frame start time (in seconds). - end: float, optional - Specify the frame end time (in seconds). - number: integer, optional - Specify a number for this frame. - count: integer, optional - Specify the total number of frames of which this frame is one. - - Returns - ------- - frame: :class:`toyplot.canvas.AnimationFrame` instance. - """ - if end is None: - end = begin + (1.0 / 30.0) - if number is None: - number = 0 - if count is None: - count = 1 - return AnimationFrame(changes=self._changes, count=count, number=number, begin=begin, end=end) - - - def frames(self, frames): - """Return a sequence of animation frames that can be used to animate the canvas contents. - - Parameters - ---------- - frames: integer, tuple, or sequence - Pass a sequence of values that specify the time (in seconds) of the - beginning / end of each frame. Note that the number of frames will be the - length of the sequence minus one. Alternatively, you can pass a 2-tuple - containing the number of frames and the frame rate in frames-per-second. - Finally, you may simply specify the number of frames, in which case the - rate will default to 30 frames-per-second. - - Yields - ------ - frames: :class:`toyplot.canvas.AnimationFrame` - Use the methods and properties of each frame object to modify the - state of the canvas over time. - """ - if isinstance(frames, numbers.Integral): - frames = (frames, 30.0) - - if isinstance(frames, tuple): - frame_count, frame_rate = frames - times = numpy.linspace(0, frame_count / frame_rate, frame_count + 1, endpoint=True) - - for index, (begin, end) in enumerate(zip(times[:-1], times[1:])): - yield AnimationFrame(changes=self._changes, count=frame_count, number=index, begin=begin, end=end) - - - def animation(self): - """Return a summary of the canvas animation state. - - Returns - ------- - begin: float - Start-time of the animation in seconds. - end: float - End-time of the animation in seconds. - changes: object - Opaque collection of data structures that describe changes to the - canvas state during animation (if any). - """ - - begin = 0.0 - end = numpy.max(self._changes._end) if self._changes._end else 0.0 - changes = collections.defaultdict(lambda: collections.defaultdict(list)) - for begin, change, state in zip(self._changes._begin, self._changes._change, self._changes._state): - changes[begin][change].append(state) - - return begin, end, changes - - def autorender(self, enable=None, autoformat=None): """Enable / disable canvas autorendering. diff --git a/toyplot/html.py b/toyplot/html.py index 9e0b6071..38311f83 100644 --- a/toyplot/html.py +++ b/toyplot/html.py @@ -1017,7 +1017,6 @@ def _render(canvas, context): # Embed Javascript code and dependencies in the container. _render_javascript(context.copy(parent=javascript_xml)) - _render_animation(canvas, context.copy(parent=javascript_xml)) # pylint: disable=no-value-for-parameter def _render_javascript(context): @@ -1130,249 +1129,6 @@ def _render_table(owner, key, label, table, filename, context): ) -@dispatch(toyplot.canvas.Canvas, RenderContext) -def _render_animation(canvas, context): - # Collect animation changes, alter them so they can be used with the DOM, - # and store them in the render context for later use by callers. - begin, end, changes = canvas.animation() - - for time, change in changes.items(): - context.animation[time] = {} - for key, states in change.items(): - context.animation[time][key] = [dict(state) for state in states] - - for state in context.animation[time][key]: - if "mark" in state: - state["mark"] = context.get_id(state["mark"]) - - if key == "set-datum-text": - layout_xml = xml.Element("temp") - _draw_text(layout_xml, text=state.pop("text"), style=state.pop("style")) - state["layout"] = layout_xml.find("g") - - # If we don't have any animation, we're done. - if len(context.animation) < 1: - return - - # We do have animation, so reconfigure our animation changes for use with - # JavaScript. - - times = numpy.concatenate((sorted(context.animation.keys()), [end])) - durations = times[1:] - times[:-1] - changes = [change for time, change in sorted(context.animation.items())] - - context.parent.append(xml.XML( - """
- - - - - - - - -
""".format( - frames=len(context.animation), - black=toyplot.color.black, - ))) - - xml.SubElement(context.parent, "script").text = string.Template(""" - (function() - { - var root_id = "$root_id"; - var frame_durations = $frame_durations; - var state_changes = $state_changes; - - var current_frame = null; - var timeout = null; - - function set_timeout(value) - { - if(timeout !== null) - window.clearTimeout(timeout); - timeout = value; - } - - function set_current_frame(frame) - { - current_frame = frame; - document.querySelector("#" + root_id + " .toyplot-current-frame").value = frame; - } - - function play_reverse() - { - set_current_frame((current_frame - 1 + frame_durations.length) % frame_durations.length); - render_changes(0, current_frame+1) - set_timeout(window.setTimeout(play_reverse, frame_durations[(current_frame - 1 + frame_durations.length) % frame_durations.length] * 1000)); - } - - function play_forward() - { - set_current_frame((current_frame + 1) % frame_durations.length); - render_changes(current_frame, current_frame+1); - set_timeout(window.setTimeout(play_forward, frame_durations[current_frame] * 1000)); - } - - var item_cache = {}; - function get_item(id) - { - if(!(id in item_cache)) - item_cache[id] = document.getElementById(id); - return item_cache[id]; - } - - function render_changes(begin, end) - { - for(var frame = begin; frame != end; ++frame) - { - var changes = state_changes[frame]; - for(var type in changes) - { - var states = changes[type] - if(type == "set-mark-style") - { - for(var i = 0; i != states.length; ++i) - { - var state = states[i]; - var mark = get_item(state.mark); - for(var key in state.style) - { - mark.style.setProperty(key, state[1][key]); - } - } - } - else if(type == "set-datum-style") - { - for(var i = 0; i != states.length; ++i) - { - var state = states[i]; - var datum = get_item(state.mark).querySelectorAll(".toyplot-Series")[state.series].querySelectorAll(".toyplot-Datum")[state.datum]; - for(var key in state.style) - { - datum.style.setProperty(key, state.style[key]); - } - } - } - else if(type == "set-datum-text") - { - for(var i = 0; i != states.length; ++i) - { - var state = states[i]; - var datum = get_item(state.mark).querySelectorAll(".toyplot-Series")[state.series].querySelectorAll(".toyplot-Datum")[state.datum]; - var layout = document.createElementNS("http://www.w3.org/2000/svg", "g"); - layout.innerHTML = state.layout; - layout = layout.firstChild; - - while(datum.firstElementChild) - datum.removeChild(datum.firstElementChild); - while(layout.firstElementChild) - datum.appendChild(layout.removeChild(layout.firstElementChild)); - } - } - } - } - } - - function on_set_frame() - { - set_timeout(null); - set_current_frame(document.querySelector("#" + root_id + " .toyplot-current-frame").valueAsNumber); - render_changes(0, current_frame+1); - } - - function on_frame_rewind() - { - set_timeout(null); - set_current_frame((current_frame - 1 + frame_durations.length) % frame_durations.length); - render_changes(0, current_frame+1); - } - - function on_rewind() - { - set_current_frame(0); - render_changes(0, current_frame+1); - } - - function on_play_reverse() - { - set_timeout(window.setTimeout(play_reverse, frame_durations[(current_frame - 1 + frame_durations.length) % frame_durations.length] * 1000)); - } - - function on_stop() - { - set_timeout(null); - } - - function on_play_forward() - { - set_timeout(window.setTimeout(play_forward, frame_durations[current_frame] * 1000)); - } - - function on_fast_forward() - { - set_timeout(null); - set_current_frame(frame_durations.length - 1); - render_changes(0, current_frame + 1) - } - - function on_frame_advance() - { - set_timeout(null); - set_current_frame((current_frame + 1) % frame_durations.length); - render_changes(current_frame, current_frame+1); - } - - set_current_frame(0); - render_changes(0, current_frame+1); - - document.querySelector("#" + root_id + " .toyplot-current-frame").oninput = on_set_frame; - document.querySelector("#" + root_id + " .toyplot-rewind").onclick = on_rewind; - document.querySelector("#" + root_id + " .toyplot-reverse-play").onclick = on_play_reverse; - document.querySelector("#" + root_id + " .toyplot-frame-rewind").onclick = on_frame_rewind; - document.querySelector("#" + root_id + " .toyplot-stop").onclick = on_stop; - document.querySelector("#" + root_id + " .toyplot-frame-advance").onclick = on_frame_advance; - document.querySelector("#" + root_id + " .toyplot-forward-play").onclick = on_play_forward; - document.querySelector("#" + root_id + " .toyplot-fast-forward").onclick = on_fast_forward; - })(); - """).substitute( - root_id=context.root.get("id"), - frame_durations=json.dumps(durations.tolist()), - state_changes=json.dumps(changes, cls=_CustomJSONEncoder), - ) - - @dispatch(toyplot.coordinates.Axis, RenderContext) def _render(axis, context): if context.already_rendered(axis): diff --git a/toyplot/mp4.py b/toyplot/mp4.py deleted file mode 100644 index fb917458..00000000 --- a/toyplot/mp4.py +++ /dev/null @@ -1,104 +0,0 @@ -# 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. - -"""Generate MPEG-4 videos.""" - - -import logging -import os -import subprocess - -import toyplot.png - -log = logging.getLogger(__name__) - -# Verify that ffmpeg is installed, and check the version -_ffmpeg_command = None -_ffmpeg_version = None -for command in ["ffmpeg"]: - try: - _ffmpeg_version = subprocess.check_output([command, "-version"]).decode(encoding="utf-8").strip() - _ffmpeg_command = command - log.info("Using %s.", _ffmpeg_version) - except: # pragma: no cover - pass - -def render( - canvas, - filename, - width=None, - height=None, - scale=None, - progress=None): - """Render a canvas as an MPEG-4 video. - - By default, the canvas dimensions in CSS pixels are mapped directly to - pixels in the output MPEG-4 video. Use one of `width`, `height`, or - `scale` to override this behavior. - - Parameters - ---------- - canvas: :class:`toyplot.canvas.Canvas` - Canvas to be rendered. - filename: string - Output video filename. - width: number, optional - Specify the width of the output video in pixels. - height: number, optional - Specify the height of the output video in pixels. - scale: number, optional - Ratio of output video pixels to `canvas` drawing units. - progress: callback function taking a single `frame` argument, optional - Callback function that will receive the number of each frame as it's - written; useful to provide an indication of progress to end-users. - - Notes - ----- - The individual video frames are rendered using PNG representations - of the canvas generated with :func:`toyplot.png.render_frames()`. - - Examples - -------- - >>> def callback(frame): - ... print "Writing frame %s" % frame - ... toyplot.mp4.render(canvas, "test.mp4", progress=callback) - """ - - if _ffmpeg_command is None: - raise RuntimeError("An ffmpeg executable is required.") # pragma: no cover - - - command = [ - _ffmpeg_command, - "-f", "image2pipe", - "-c", "png", - "-i", "-", - "-pix_fmt", "yuv420p", - "-vcodec", "h264", - "-preset", "slow", - "-tune", "animation", - "-crf", "17", - "-y", - filename, - ] - - try: - log.info("Running command: %s", " ".join(command)) - ffmpeg = subprocess.Popen( - command, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - for frame, png in enumerate( - toyplot.png.render_frames( - canvas=canvas, width=width, height=height, scale=scale)): - if progress is not None: - progress(frame) - ffmpeg.stdin.write(png) - ffmpeg.stdin.close() - ffmpeg.wait() - except Exception as e: # pragma: no cover - log.error(ffmpeg.stdout.read()) - log.error(ffmpeg.stderr.read()) - raise e From 463214c569f5d424f244c9993d1ca505ecaf07a2 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 13:15:51 -0600 Subject: [PATCH 03/15] Run regression tests on the remove-animation branch. --- .github/workflows/regression-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index a7124a55..b54190a1 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -2,7 +2,7 @@ name: Regression tests on: push: - branches: [ main ] + branches: [ main, remove-animation ] pull_request: branches: [ main ] From a75d8251762399c05fb9b70e8ac5e382bef46122 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 13:20:56 -0600 Subject: [PATCH 04/15] Temporarily limit the build to numpy < 2.0.0. --- pyproject.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6a237c6e..4a70f29d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,13 +19,13 @@ classifiers = [ "Topic :: Scientific/Engineering :: Visualization", ] dependencies = [ - "arrow>=1.0", - "custom_inherit", - "multipledispatch", - "numpy>=1.8.0", - "packaging", - "pypng", - "reportlab", + "arrow>=1.0", + "custom_inherit", + "multipledispatch", + "numpy>=1.8.0,<2.0.0", + "packaging", + "pypng", + "reportlab", ] description = "A modern plotting toolkit supporting electronic publishing and reproducibility." dynamic = ["version"] From dc3e2ef9f75783e22109ead185f7129c3a61cfc2 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 13:40:10 -0600 Subject: [PATCH 05/15] Update the major version. --- toyplot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toyplot/__init__.py b/toyplot/__init__.py index 13c5dc42..95389bc8 100644 --- a/toyplot/__init__.py +++ b/toyplot/__init__.py @@ -14,7 +14,7 @@ from toyplot.canvas import Canvas -__version__ = "1.0.4-dev" +__version__ = "2.0.0-dev" log = logging.getLogger(__name__) log.setLevel(logging.WARNING) From d59b4f8e160f11db139f30c997dc463b5f50d1e4 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 13:57:58 -0600 Subject: [PATCH 06/15] Fix broken readthedocs build. --- .readthedocs.yaml | 35 +++++++++++++++++++++++++++++++++++ docs/requirements.txt | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..f6a12ce2 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,35 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + # You can also specify other tool versions: + # nodejs: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt index d6364949..51ecf0ac 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,7 +2,7 @@ custom_inherit ipython multipledispatch nbsphinx -numpy +numpy < 2.0.0 pypng reportlab sphinx >= 3.5 From a41362ea0d14ce66ce9f57ef9d676b97f43ea12a Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 14:14:54 -0600 Subject: [PATCH 07/15] Remove nose.tools dependency. --- features/steps/backend.py | 42 +++--- features/steps/bar-visualization.py | 1 - features/steps/browser.py | 10 +- features/steps/canvas.py | 8 +- features/steps/cartesian-coordinates.py | 99 +++++++-------- features/steps/color.py | 58 ++++----- features/steps/data-table.py | 134 ++++++++++---------- features/steps/data.py | 2 +- features/steps/documentation.py | 2 +- features/steps/fill-visualization.py | 1 - features/steps/format.py | 8 +- features/steps/image-visualization.py | 50 ++++---- features/steps/legends.py | 2 +- features/steps/log-scale-axes.py | 1 - features/steps/plot-visualization.py | 2 +- features/steps/projection.py | 34 ++--- features/steps/rectangle-visualization.py | 2 +- features/steps/scatterplot-visualization.py | 2 +- features/steps/table-visualization.py | 1 - features/steps/test.py | 41 ++++++ features/steps/text.py | 8 +- features/steps/units.py | 40 +++--- pyproject.toml | 1 - 23 files changed, 292 insertions(+), 257 deletions(-) create mode 100644 features/steps/test.py diff --git a/features/steps/backend.py b/features/steps/backend.py index 32e1df52..da4ac633 100644 --- a/features/steps/backend.py +++ b/features/steps/backend.py @@ -6,11 +6,11 @@ import importlib import io -import nose.tools import numpy import os import xml.etree.ElementTree as xml +import test import testing if not os.path.exists(testing.backend_dir): @@ -30,7 +30,7 @@ def step_impl(context): target = os.path.join(testing.backend_dir, "%s.html" % context.name) context.backend.render(context.canvas, target) #html = xml.parse(target) - #nose.tools.assert_equal(html.getroot().tag, "{http://www.w3.org/2000/svg}svg") + #test.assert_equal(html.getroot().tag, "{http://www.w3.org/2000/svg}svg") @then(u'the canvas can be rendered to an html buffer') @@ -38,14 +38,14 @@ def step_impl(context): buffer = io.BytesIO() context.backend.render(context.canvas, buffer) #html = xml.parse(buffer.getvalue()) - #nose.tools.assert_equal(html.getroot().tag, "html") + #test.assert_equal(html.getroot().tag, "html") @then(u'the canvas can be rendered to a returned html dom') def step_impl(context): html = context.backend.render(context.canvas) - nose.tools.assert_is_instance(html, xml.Element) - nose.tools.assert_equal(html.tag, "div") + test.assert_is_instance(html, xml.Element) + test.assert_equal(html.tag, "div") @then(u'the canvas can be rendered to a pdf file') @@ -70,8 +70,8 @@ def step_impl(context): target = os.path.join(testing.backend_dir, "%s.png" % context.name) context.backend.render(context.canvas, target) image = testing.read_png(target) - nose.tools.assert_equal(image.shape, (600, 600, 4)) - nose.tools.assert_equal(image.dtype, "uint8") + test.assert_equal(image.shape, (600, 600, 4)) + test.assert_equal(image.dtype, "uint8") @then(u'the canvas can be rendered to a png buffer') @@ -81,38 +81,38 @@ def step_impl(context): buffer.seek(0) image = testing.read_png(buffer) - nose.tools.assert_equal(image.shape, (600, 600, 4)) - nose.tools.assert_equal(image.dtype, "uint8") + test.assert_equal(image.shape, (600, 600, 4)) + test.assert_equal(image.dtype, "uint8") @then(u'the canvas can be rendered to a returned png document') def step_impl(context): png = context.backend.render(context.canvas) image = testing.read_png(io.BytesIO(png)) - nose.tools.assert_equal(image.shape, (600, 600, 4)) - nose.tools.assert_equal(image.dtype, "uint8") + test.assert_equal(image.shape, (600, 600, 4)) + test.assert_equal(image.dtype, "uint8") @then(u'the canvas can be rendered to a 200 pixel wide png document') def step_impl(context): png = context.backend.render(context.canvas, width="200px") image = testing.read_png(io.BytesIO(png)) - nose.tools.assert_equal(image.shape, (200, 200, 4)) - nose.tools.assert_equal(image.dtype, "uint8") + test.assert_equal(image.shape, (200, 200, 4)) + test.assert_equal(image.dtype, "uint8") @then(u'the canvas can be rendered to a 150 pixel high png document') def step_impl(context): png = context.backend.render(context.canvas, height="150px") image = testing.read_png(io.BytesIO(png)) - nose.tools.assert_equal(image.shape, (150, 150, 4)) - nose.tools.assert_equal(image.dtype, "uint8") + test.assert_equal(image.shape, (150, 150, 4)) + test.assert_equal(image.dtype, "uint8") @then(u'the canvas can be rendered to a half scale png document') def step_impl(context): png = context.backend.render(context.canvas, scale=0.5) image = testing.read_png(io.BytesIO(png)) - nose.tools.assert_equal(image.shape, (300, 300, 4)) - nose.tools.assert_equal(image.dtype, "uint8") + test.assert_equal(image.shape, (300, 300, 4)) + test.assert_equal(image.dtype, "uint8") @then(u'the canvas can be rendered to an svg file') @@ -120,7 +120,7 @@ def step_impl(context): target = os.path.join(testing.backend_dir, "%s.svg" % context.name) context.backend.render(context.canvas, target) svg = xml.parse(target) - nose.tools.assert_equal(svg.getroot().tag, "{http://www.w3.org/2000/svg}svg") + test.assert_equal(svg.getroot().tag, "{http://www.w3.org/2000/svg}svg") @then(u'the canvas can be rendered to an svg buffer') @@ -128,11 +128,11 @@ def step_impl(context): buffer = io.BytesIO() context.backend.render(context.canvas, buffer) #svg = xml.parse(buffer.getvalue()) - #nose.tools.assert_equal(svg.getroot().tag, "svg") + #test.assert_equal(svg.getroot().tag, "svg") @then(u'the canvas can be rendered to a returned svg dom') def step_impl(context): svg = context.backend.render(context.canvas) - nose.tools.assert_is_instance(svg, xml.Element) - nose.tools.assert_equal(svg.tag, "svg") + test.assert_is_instance(svg, xml.Element) + test.assert_equal(svg.tag, "svg") diff --git a/features/steps/bar-visualization.py b/features/steps/bar-visualization.py index 78d93eaf..f183f078 100644 --- a/features/steps/bar-visualization.py +++ b/features/steps/bar-visualization.py @@ -6,7 +6,6 @@ import collections -import nose.tools import numpy.testing import toyplot diff --git a/features/steps/browser.py b/features/steps/browser.py index 54a580ca..239cf789 100644 --- a/features/steps/browser.py +++ b/features/steps/browser.py @@ -4,7 +4,7 @@ from behave import * -import nose.tools +import test import numpy import sys import toyplot.browser @@ -17,10 +17,10 @@ def step_impl(context): canvas, axes, mark = toyplot.plot(numpy.sin(numpy.linspace(0, 10))) with unittest.mock.patch("webbrowser.open") as webbrowser_open: toyplot.browser.show(canvas) - nose.tools.assert_equal(webbrowser_open.call_count, 1) - nose.tools.assert_true( + test.assert_equal(webbrowser_open.call_count, 1) + test.assert_true( webbrowser_open.call_args[0][0].startswith("file://")) - nose.tools.assert_equal(webbrowser_open.call_args[1]["new"], 1) - nose.tools.assert_equal(webbrowser_open.call_args[1]["autoraise"], True) + test.assert_equal(webbrowser_open.call_args[1]["new"], 1) + test.assert_equal(webbrowser_open.call_args[1]["autoraise"], True) diff --git a/features/steps/canvas.py b/features/steps/canvas.py index 3eff0016..e2215cf3 100644 --- a/features/steps/canvas.py +++ b/features/steps/canvas.py @@ -6,7 +6,7 @@ import io -import nose.tools +import test import numpy.testing import toyplot @@ -65,14 +65,14 @@ def step_impl(context): @then(u'the canvas can be rendered in Jupyter as HTML') def step_impl(context): html = context.canvas._repr_html_() - nose.tools.assert_is_instance(html, str) + test.assert_is_instance(html, str) @then(u'the canvas can be rendered in Jupyter as a PNG image') def step_impl(context): png = context.canvas._repr_png_() image = testing.read_png(io.BytesIO(png)) - nose.tools.assert_equal(image.shape, (600, 600, 4)) - nose.tools.assert_equal(image.dtype, "uint8") + test.assert_equal(image.shape, (600, 600, 4)) + test.assert_equal(image.dtype, "uint8") @then(u'numberlines can be added to the canvas using relative coordinates') def step_impl(context): diff --git a/features/steps/cartesian-coordinates.py b/features/steps/cartesian-coordinates.py index 635b0939..8c084069 100644 --- a/features/steps/cartesian-coordinates.py +++ b/features/steps/cartesian-coordinates.py @@ -4,7 +4,6 @@ from behave import * -import nose import numpy import toyplot.data @@ -19,58 +18,58 @@ def step_impl(context): @then(u'the cartesian axes can be rendered with hidden axes') def step_impl(context): context.axes.show = False - nose.tools.assert_equal(context.axes.show, False) + test.assert_equal(context.axes.show, False) @then(u'the cartesian axes can be rendered with axes label') def step_impl(context): context.axes.label.text = "Howdy!" - nose.tools.assert_equal(context.axes.label.text, "Howdy!") + test.assert_equal(context.axes.label.text, "Howdy!") context.axes.label.style = {"fill": "red"} - nose.tools.assert_equal(context.axes.label.style["fill"], "red") + test.assert_equal(context.axes.label.style["fill"], "red") @then(u'the cartesian axes can be rendered with hidden x axis') def step_impl(context): context.axes.x.show = False - nose.tools.assert_equal(context.axes.x.show, False) + test.assert_equal(context.axes.x.show, False) @then(u'the cartesian axes can be rendered with log-10 x scale') def step_impl(context): context.axes.x.scale = "log" - nose.tools.assert_equal(context.axes.x.scale, ("log", 10)) + test.assert_equal(context.axes.x.scale, ("log", 10)) @then(u'the cartesian axes can be rendered with hidden x spine') def step_impl(context): context.axes.x.spine.show = False - nose.tools.assert_equal(context.axes.x.spine.show, False) + test.assert_equal(context.axes.x.spine.show, False) @then( u'the cartesian axes can be rendered with x spine at an explicit position') def step_impl(context): context.axes.x.spine.position = 0 - nose.tools.assert_equal(context.axes.x.spine.position, 0) + test.assert_equal(context.axes.x.spine.position, 0) @then(u'the cartesian axes can be rendered with x spine at the high end of y') def step_impl(context): context.axes.x.spine.position = "high" - nose.tools.assert_equal(context.axes.x.spine.position, "high") + test.assert_equal(context.axes.x.spine.position, "high") @then(u'the cartesian axes can be rendered with styled x spine') def step_impl(context): context.axes.x.spine.style = {"stroke": "red"} - nose.tools.assert_equal(context.axes.x.spine.style["stroke"], "red") + test.assert_equal(context.axes.x.spine.style["stroke"], "red") @then(u'the cartesian axes can be rendered with visible x ticks') def step_impl(context): context.axes.x.ticks.show = True - nose.tools.assert_equal(context.axes.x.ticks.show, True) + test.assert_equal(context.axes.x.ticks.show, True) @then(u'the cartesian axes can be rendered with sized x ticks') @@ -78,15 +77,15 @@ def step_impl(context): context.axes.x.ticks.show = True context.axes.x.ticks.far = 10 context.axes.x.ticks.near = 3 - nose.tools.assert_equal(context.axes.x.ticks.far, 10) - nose.tools.assert_equal(context.axes.x.ticks.near, 3) + test.assert_equal(context.axes.x.ticks.far, 10) + test.assert_equal(context.axes.x.ticks.near, 3) @then(u'the cartesian axes can be rendered with styled x ticks') def step_impl(context): context.axes.x.ticks.show = True context.axes.x.ticks.style = {"stroke": "red"} - nose.tools.assert_equal(context.axes.x.ticks.style["stroke"], "red") + test.assert_equal(context.axes.x.ticks.style["stroke"], "red") @then( @@ -95,7 +94,7 @@ def step_impl(context): context.axes.x.ticks.show = True locator = toyplot.locator.Uniform(count=11) context.axes.x.ticks.locator = locator - nose.tools.assert_is(context.axes.x.ticks.locator, locator) + test.assert_is(context.axes.x.ticks.locator, locator) @then( @@ -103,7 +102,7 @@ def step_impl(context): def step_impl(context): context.axes.x.ticks.show = True context.axes.x.ticks.tick(index=0).style = {"stroke": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.x.ticks.tick(index=0).style["stroke"], "red") @@ -112,40 +111,40 @@ def step_impl(context): def step_impl(context): context.axes.x.ticks.show = True context.axes.x.ticks.tick(value=0).style = {"stroke": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.x.ticks.tick(value=0).style["stroke"], "red") @then(u'the cartesian axes can be rendered with hidden x tick labels') def step_impl(context): context.axes.x.ticks.labels.show = False - nose.tools.assert_equal(context.axes.x.ticks.labels.show, False) + test.assert_equal(context.axes.x.ticks.labels.show, False) @then(u'the cartesian axes can be rendered with angled x tick labels') def step_impl(context): context.axes.x.ticks.labels.angle = 45 - nose.tools.assert_equal(context.axes.x.ticks.labels.angle, 45) + test.assert_equal(context.axes.x.ticks.labels.angle, 45) context.axes.x.ticks.show = True @then(u'the cartesian axes can be rendered with offset x tick labels') def step_impl(context): context.axes.x.ticks.labels.offset = "0.125in" - nose.tools.assert_equal(context.axes.x.ticks.labels.offset, 12) + test.assert_equal(context.axes.x.ticks.labels.offset, 12) @then(u'the cartesian axes can be rendered with styled x tick labels') def step_impl(context): context.axes.x.ticks.labels.style = {"fill": "red"} - nose.tools.assert_equal(context.axes.x.ticks.labels.style["fill"], "red") + test.assert_equal(context.axes.x.ticks.labels.style["fill"], "red") @then( u'the cartesian axes can be rendered with x axis per-tick-label styles identified by index') def step_impl(context): context.axes.x.ticks.labels.label(index=0).style = {"fill": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.x.ticks.labels.label(index=0).style["fill"], "red") @@ -153,67 +152,67 @@ def step_impl(context): u'the cartesian axes can be rendered with x axis per-tick-label styles identified by value') def step_impl(context): context.axes.x.ticks.labels.label(value=0).style = {"fill": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.x.ticks.labels.label(value=0).style["fill"], "red") @then(u'the cartesian axes can be rendered with x axis label') def step_impl(context): context.axes.x.label.text = "Howdy!" - nose.tools.assert_equal(context.axes.x.label.text, "Howdy!") + test.assert_equal(context.axes.x.label.text, "Howdy!") context.axes.x.label.style = {"fill": "red"} - nose.tools.assert_equal(context.axes.x.label.style["fill"], "red") + test.assert_equal(context.axes.x.label.style["fill"], "red") @then(u'the cartesian axes can be rendered with explicit x axis domain') def step_impl(context): context.axes.x.domain.min = 0 - nose.tools.assert_equal(context.axes.x.domain.min, 0) + test.assert_equal(context.axes.x.domain.min, 0) context.axes.x.domain.max = 1 - nose.tools.assert_equal(context.axes.x.domain.max, 1) + test.assert_equal(context.axes.x.domain.max, 1) @then(u'the cartesian axes can be rendered with hidden y axis') def step_impl(context): context.axes.y.show = False - nose.tools.assert_equal(context.axes.y.show, False) + test.assert_equal(context.axes.y.show, False) @then(u'the cartesian axes can be rendered with log-10 y scale') def step_impl(context): context.axes.y.scale = "log" - nose.tools.assert_equal(context.axes.y.scale, ("log", 10)) + test.assert_equal(context.axes.y.scale, ("log", 10)) @then(u'the cartesian axes can be rendered with hidden y spine') def step_impl(context): context.axes.y.spine.show = False - nose.tools.assert_equal(context.axes.y.spine.show, False) + test.assert_equal(context.axes.y.spine.show, False) @then( u'the cartesian axes can be rendered with y spine at an explicit position') def step_impl(context): context.axes.y.spine.position = 10 - nose.tools.assert_equal(context.axes.y.spine.position, 10) + test.assert_equal(context.axes.y.spine.position, 10) @then(u'the cartesian axes can be rendered with y spine at the high end of x') def step_impl(context): context.axes.y.spine.position = "high" - nose.tools.assert_equal(context.axes.y.spine.position, "high") + test.assert_equal(context.axes.y.spine.position, "high") @then(u'the cartesian axes can be rendered with styled y spine') def step_impl(context): context.axes.y.spine.style = {"stroke": "red"} - nose.tools.assert_equal(context.axes.y.spine.style["stroke"], "red") + test.assert_equal(context.axes.y.spine.style["stroke"], "red") @then(u'the cartesian axes can be rendered with visible y ticks') def step_impl(context): context.axes.y.ticks.show = True - nose.tools.assert_equal(context.axes.y.ticks.show, True) + test.assert_equal(context.axes.y.ticks.show, True) @then(u'the cartesian axes can be rendered with sized y ticks') @@ -221,15 +220,15 @@ def step_impl(context): context.axes.y.ticks.show = True context.axes.y.ticks.near = 3 context.axes.y.ticks.far = 10 - nose.tools.assert_equal(context.axes.y.ticks.near, 3) - nose.tools.assert_equal(context.axes.y.ticks.far, 10) + test.assert_equal(context.axes.y.ticks.near, 3) + test.assert_equal(context.axes.y.ticks.far, 10) @then(u'the cartesian axes can be rendered with styled y ticks') def step_impl(context): context.axes.y.ticks.show = True context.axes.y.ticks.style = {"stroke": "red"} - nose.tools.assert_equal(context.axes.y.ticks.style["stroke"], "red") + test.assert_equal(context.axes.y.ticks.style["stroke"], "red") @then( @@ -238,7 +237,7 @@ def step_impl(context): context.axes.y.ticks.show = True locator = toyplot.locator.Uniform(count=5) context.axes.y.ticks.locator = locator - nose.tools.assert_is(context.axes.y.ticks.locator, locator) + test.assert_is(context.axes.y.ticks.locator, locator) @then( @@ -246,7 +245,7 @@ def step_impl(context): def step_impl(context): context.axes.y.ticks.show = True context.axes.y.ticks.tick(index=0).style = {"stroke": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.y.ticks.tick(index=0).style["stroke"], "red") @@ -255,40 +254,40 @@ def step_impl(context): def step_impl(context): context.axes.y.ticks.show = True context.axes.y.ticks.tick(value=0).style = {"stroke": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.y.ticks.tick(value=0).style["stroke"], "red") @then(u'the cartesian axes can be rendered with hidden y tick labels') def step_impl(context): context.axes.y.ticks.labels.show = False - nose.tools.assert_equal(context.axes.y.ticks.labels.show, False) + test.assert_equal(context.axes.y.ticks.labels.show, False) @then(u'the cartesian axes can be rendered with angled y tick labels') def step_impl(context): context.axes.y.ticks.labels.angle = -45 - nose.tools.assert_equal(context.axes.y.ticks.labels.angle, -45) + test.assert_equal(context.axes.y.ticks.labels.angle, -45) context.axes.y.ticks.show = True @then(u'the cartesian axes can be rendered with offset y tick labels') def step_impl(context): context.axes.y.ticks.labels.offset = "16px" - nose.tools.assert_equal(context.axes.y.ticks.labels.offset, 16) + test.assert_equal(context.axes.y.ticks.labels.offset, 16) @then(u'the cartesian axes can be rendered with styled y tick labels') def step_impl(context): context.axes.y.ticks.labels.style = {"fill": "red"} - nose.tools.assert_equal(context.axes.y.ticks.labels.style["fill"], "red") + test.assert_equal(context.axes.y.ticks.labels.style["fill"], "red") @then( u'the cartesian axes can be rendered with y axis per-tick-label styles identified by index') def step_impl(context): context.axes.y.ticks.labels.label(index=0).style = {"fill": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.y.ticks.labels.label(index=0).style["fill"], "red") @@ -296,24 +295,24 @@ def step_impl(context): u'the cartesian axes can be rendered with y axis per-tick-label styles identified by value') def step_impl(context): context.axes.y.ticks.labels.label(value=0).style = {"fill": "red"} - nose.tools.assert_equal( + test.assert_equal( context.axes.y.ticks.labels.label(value=0).style["fill"], "red") @then(u'the cartesian axes can be rendered with y axis label') def step_impl(context): context.axes.y.label.text = "Howdy!" - nose.tools.assert_equal(context.axes.y.label.text, "Howdy!") + test.assert_equal(context.axes.y.label.text, "Howdy!") context.axes.y.label.style = {"fill": "red"} - nose.tools.assert_equal(context.axes.y.label.style["fill"], "red") + test.assert_equal(context.axes.y.label.style["fill"], "red") @then(u'the cartesian axes can be rendered with explicit y axis domain') def step_impl(context): context.axes.y.domain.min = 0 - nose.tools.assert_equal(context.axes.y.domain.min, 0) + test.assert_equal(context.axes.y.domain.min, 0) context.axes.y.domain.max = 1 - nose.tools.assert_equal(context.axes.y.domain.max, 1) + test.assert_equal(context.axes.y.domain.max, 1) @given(u'a shared axis') diff --git a/features/steps/color.py b/features/steps/color.py index 1c07a1e8..dd2f31be 100644 --- a/features/steps/color.py +++ b/features/steps/color.py @@ -6,7 +6,7 @@ from behave import * import json -import nose.tools +import test import numpy import toyplot.color @@ -31,7 +31,7 @@ def step_impl(context, value): @then(u'toyplot.color.to_css should return {value}') def step_impl(context, value): - nose.tools.assert_equal(toyplot.color.to_css(context.value), value) + test.assert_equal(toyplot.color.to_css(context.value), value) @given(u'a color value') @@ -59,7 +59,7 @@ def step_impl(context): @given(u'a color brewer category, the palette names for that category can be retrieved.') def step_impl(context): - nose.tools.assert_equal(toyplot.color.brewer.names("sequential"), [ + test.assert_equal(toyplot.color.brewer.names("sequential"), [ 'BlueGreen', 'BlueGreenYellow', 'BluePurple', @@ -79,7 +79,7 @@ def step_impl(context): 'RedPurple', 'Reds', ]) - nose.tools.assert_equal(toyplot.color.brewer.names("diverging"), [ + test.assert_equal(toyplot.color.brewer.names("diverging"), [ 'BlueGreenBrown', 'BlueRed', 'BlueYellowRed', @@ -90,7 +90,7 @@ def step_impl(context): 'PurpleOrange', 'Spectral', ]) - nose.tools.assert_equal(toyplot.color.brewer.names("qualitative"), [ + test.assert_equal(toyplot.color.brewer.names("qualitative"), [ 'Accent', 'Dark2', 'Paired', @@ -104,12 +104,12 @@ def step_impl(context): @given(u'a color brewer palette name, the color counts for that palette can be retrieved.') def step_impl(context): - nose.tools.assert_equal(toyplot.color.brewer.counts("BlueRed"), [3, 4, 5, 6, 7, 8, 9, 10, 11]) + test.assert_equal(toyplot.color.brewer.counts("BlueRed"), [3, 4, 5, 6, 7, 8, 9, 10, 11]) @given(u'a color brewer palette name, the category for that palette can be retrieved.') def step_impl(context): - nose.tools.assert_equal(toyplot.color.brewer.category("BlueRed"), "diverging") + test.assert_equal(toyplot.color.brewer.category("BlueRed"), "diverging") @when(u'the user creates a Color Brewer palette') @@ -202,15 +202,15 @@ def step_impl(context): @then( u'individual values can be mapped to css colors by the diverging color map') def step_impl(context): - nose.tools.assert_equal( + test.assert_equal( context.color_map.css(-1), "rgba(23.0%,29.9%,75.4%,1.000)") - nose.tools.assert_equal( + test.assert_equal( context.color_map.css(0), "rgba(23.0%,29.9%,75.4%,1.000)") - nose.tools.assert_equal( + test.assert_equal( context.color_map.css(0.5), "rgba(86.5%,86.5%,86.5%,1.000)") - nose.tools.assert_equal( + test.assert_equal( context.color_map.css(1), "rgba(70.6%,1.6%,15.0%,1.000)") - nose.tools.assert_equal( + test.assert_equal( context.color_map.css(2), "rgba(70.6%,1.6%,15.0%,1.000)") @@ -242,21 +242,21 @@ def step_impl(context): @then(u'the linear color map can map scalar values to css colors') def step_impl(context): - nose.tools.assert_equal(context.color_map.css(-1), "rgba(100.0%,0.0%,0.0%,1.000)") - nose.tools.assert_equal(context.color_map.css(0), "rgba(100.0%,0.0%,0.0%,1.000)") - nose.tools.assert_equal(context.color_map.css(0.5), "rgba(50.0%,0.0%,50.0%,1.000)") - nose.tools.assert_equal(context.color_map.css(1), "rgba(0.0%,0.0%,100.0%,1.000)") - nose.tools.assert_equal(context.color_map.css(2), "rgba(0.0%,0.0%,100.0%,1.000)") + test.assert_equal(context.color_map.css(-1), "rgba(100.0%,0.0%,0.0%,1.000)") + test.assert_equal(context.color_map.css(0), "rgba(100.0%,0.0%,0.0%,1.000)") + test.assert_equal(context.color_map.css(0.5), "rgba(50.0%,0.0%,50.0%,1.000)") + test.assert_equal(context.color_map.css(1), "rgba(0.0%,0.0%,100.0%,1.000)") + test.assert_equal(context.color_map.css(2), "rgba(0.0%,0.0%,100.0%,1.000)") @then(u'the color map domain can be changed') def step_impl(context): - nose.tools.assert_equal(context.color_map.domain.min, 0) - nose.tools.assert_equal(context.color_map.domain.max, 1) + test.assert_equal(context.color_map.domain.min, 0) + test.assert_equal(context.color_map.domain.max, 1) context.color_map.domain.min = -1 context.color_map.domain.max = 2 - nose.tools.assert_equal(context.color_map.domain.min, -1) - nose.tools.assert_equal(context.color_map.domain.max, 2) + test.assert_equal(context.color_map.domain.min, -1) + test.assert_equal(context.color_map.domain.max, 2) @given(u'a starting color') @@ -302,7 +302,7 @@ def step_impl(context): @then(u'the palette should contain 8 colors') def step_impl(context): - nose.tools.assert_equal(len(context.palette), 8) + test.assert_equal(len(context.palette), 8) @then(u'the default palette can be rendered as ipython html') @@ -351,9 +351,9 @@ def step_impl(context): testing.assert_color_equal(palette[0], (1, 0, 0, 1)) testing.assert_color_equal(palette[1], (0, 1, 0, 1)) testing.assert_color_equal(palette[-1], (0, 0, 1, 1)) - with nose.tools.assert_raises(IndexError): + with test.assert_raises(IndexError): palette[3] - with nose.tools.assert_raises(TypeError): + with test.assert_raises(TypeError): palette[0:3] @given(u'a color palette, callers can iterate over the colors') @@ -363,7 +363,7 @@ def step_impl(context): testing.assert_color_equal(next(color), (1, 0, 0, 1)) testing.assert_color_equal(next(color), (0, 1, 0, 1)) testing.assert_color_equal(next(color), (0, 0, 1, 1)) - with nose.tools.assert_raises(StopIteration): + with test.assert_raises(StopIteration): next(color) @given(u'a color palette, callers can retrieve colors by index') @@ -375,8 +375,8 @@ def step_impl(context): @given(u'a color palette, colors can retrieve css colors by index') def step_impl(context): palette = toyplot.color.Palette([(1, 0, 0), (0, 1, 0), (0, 0, 1)]) - nose.tools.assert_equal(palette.css(0), "rgba(100.0%,0.0%,0.0%,1.000)") - nose.tools.assert_equal(palette.css(-1), "rgba(0.0%,0.0%,100.0%,1.000)") + test.assert_equal(palette.css(0), "rgba(100.0%,0.0%,0.0%,1.000)") + test.assert_equal(palette.css(-1), "rgba(0.0%,0.0%,100.0%,1.000)") @given(u'a categorical color map, the map can be rendered as ipython html') def step_impl(context): @@ -402,6 +402,6 @@ def step_impl(context): def step_impl(context): colormap = toyplot.color.CategoricalMap( toyplot.color.Palette(["red", "lime", "blue", (1, 1, 1)])) - nose.tools.assert_equal(colormap.css(0), "rgba(100.0%,0.0%,0.0%,1.000)") - nose.tools.assert_equal(colormap.css(-1), "rgba(100.0%,100.0%,100.0%,1.000)") + test.assert_equal(colormap.css(0), "rgba(100.0%,0.0%,0.0%,1.000)") + test.assert_equal(colormap.css(-1), "rgba(100.0%,100.0%,100.0%,1.000)") diff --git a/features/steps/data-table.py b/features/steps/data-table.py index daa32e28..96f665ca 100644 --- a/features/steps/data-table.py +++ b/features/steps/data-table.py @@ -3,7 +3,7 @@ # rights in this software. from behave import * -import nose.tools +import test import numpy.testing import collections @@ -41,26 +41,26 @@ def step_impl(context): @then(u'the table should be empty') def step_impl(context): - nose.tools.assert_equal(len(context.data), 0) - nose.tools.assert_equal(context.data.shape, (0, 0)) - nose.tools.assert_equal(list(context.data.items()), []) - nose.tools.assert_equal(list(context.data.keys()), []) - nose.tools.assert_equal(list(context.data.values()), []) + test.assert_equal(len(context.data), 0) + test.assert_equal(context.data.shape, (0, 0)) + test.assert_equal(list(context.data.items()), []) + test.assert_equal(list(context.data.keys()), []) + test.assert_equal(list(context.data.values()), []) @then(u'adding columns should change the table') def step_impl(context): context.data["a"] = numpy.arange(10) - nose.tools.assert_equal(list(context.data.keys()), ["a"]) - nose.tools.assert_equal(context.data.shape, (10, 1)) + test.assert_equal(list(context.data.keys()), ["a"]) + test.assert_equal(context.data.shape, (10, 1)) context.data["b"] = context.data["a"] ** 2 - nose.tools.assert_equal(list(context.data.keys()), ["a", "b"]) - nose.tools.assert_equal(context.data.shape, (10, 2)) + test.assert_equal(list(context.data.keys()), ["a", "b"]) + test.assert_equal(context.data.shape, (10, 2)) context.data["c"] = numpy.zeros(10) - nose.tools.assert_equal(list(context.data.keys()), ["a", "b", "c"]) - nose.tools.assert_equal(context.data.shape, (10, 3)) + test.assert_equal(list(context.data.keys()), ["a", "b", "c"]) + test.assert_equal(context.data.shape, (10, 3)) @then(u'columns can be retrieved by name') @@ -70,7 +70,7 @@ def step_impl(context): @then(u'partial columns can be retrieved by name and index') def step_impl(context): - nose.tools.assert_equal(context.data["a", 5], 5) + test.assert_equal(context.data["a", 5], 5) @then(u'partial columns can be retrieved by name and slice') @@ -81,69 +81,69 @@ def step_impl(context): @then(u'partial tables can be retrieved by row index') def step_impl(context): table = context.data[5] - nose.tools.assert_equal(list(table.keys()), ["a", "b", "c"]) - nose.tools.assert_equal(table.shape, (1, 3)) + test.assert_equal(list(table.keys()), ["a", "b", "c"]) + test.assert_equal(table.shape, (1, 3)) numpy.testing.assert_array_equal(table["a"], [5]) @then(u'partial tables can be retrieved by row slice') def step_impl(context): table = context.data[5:7] - nose.tools.assert_equal(list(table.keys()), ["a", "b", "c"]) - nose.tools.assert_equal(table.shape, (2, 3)) + test.assert_equal(list(table.keys()), ["a", "b", "c"]) + test.assert_equal(table.shape, (2, 3)) numpy.testing.assert_array_equal(table["a"], [5,6]) @then(u'partial tables can be retrieved by row index and column name') def step_impl(context): table = context.data[5, "b"] - nose.tools.assert_equal(list(table.keys()), ["b"]) - nose.tools.assert_equal(table.shape, (1, 1)) + test.assert_equal(list(table.keys()), ["b"]) + test.assert_equal(table.shape, (1, 1)) numpy.testing.assert_array_equal(table["b"], [25]) @then(u'partial tables can be retrieved by row slice and column name') def step_impl(context): table = context.data[5:7, "b"] - nose.tools.assert_equal(list(table.keys()), ["b"]) - nose.tools.assert_equal(table.shape, (2, 1)) + test.assert_equal(list(table.keys()), ["b"]) + test.assert_equal(table.shape, (2, 1)) numpy.testing.assert_array_equal(table["b"], [25,36]) @then(u'partial tables can be retrieved by row index and column names') def step_impl(context): table = context.data[5, ["b", "a"]] - nose.tools.assert_equal(list(table.keys()), ["b", "a"]) - nose.tools.assert_equal(table.shape, (1, 2)) + test.assert_equal(list(table.keys()), ["b", "a"]) + test.assert_equal(table.shape, (1, 2)) numpy.testing.assert_array_equal(table["a"], [5]) @then(u'partial tables can be retrieved by row slice and column names') def step_impl(context): table = context.data[5:7, ["b", "a"]] - nose.tools.assert_equal(list(table.keys()), ["b", "a"]) - nose.tools.assert_equal(table.shape, (2, 2)) + test.assert_equal(list(table.keys()), ["b", "a"]) + test.assert_equal(table.shape, (2, 2)) numpy.testing.assert_array_equal(table["a"], [5,6]) @then(u'partial tables can be retrieved by column names') def step_impl(context): table = context.data[["b", "a"]] - nose.tools.assert_equal(list(table.keys()), ["b", "a"]) - nose.tools.assert_equal(table.shape, (10, 2)) + test.assert_equal(list(table.keys()), ["b", "a"]) + test.assert_equal(table.shape, (10, 2)) @then(u'partial tables can be retrieved by row indices') def step_impl(context): table = context.data[[5, 7]] - nose.tools.assert_equal(list(table.keys()), ["a", "b", "c"]) - nose.tools.assert_equal(table.shape, (2, 3)) + test.assert_equal(list(table.keys()), ["a", "b", "c"]) + test.assert_equal(table.shape, (2, 3)) numpy.testing.assert_array_equal(table["a"], [5, 7]) @then(u'columns can be replaced by name') def step_impl(context): context.data["c"] = numpy.ones(10) - nose.tools.assert_equal(list(context.data.keys()), ["a", "b", "c"]) - nose.tools.assert_equal(context.data.shape, (10, 3)) + test.assert_equal(list(context.data.keys()), ["a", "b", "c"]) + test.assert_equal(context.data.shape, (10, 3)) numpy.testing.assert_array_equal(context.data["c"], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) @@ -174,48 +174,48 @@ def step_impl(context): @then(u'partial columns can be masked by name and index') def step_impl(context): context.data["c", 3] = numpy.ma.masked - nose.tools.assert_is(context.data["c"][3], numpy.ma.masked) + test.assert_is(context.data["c"][3], numpy.ma.masked) @then(u'partial columns can be masked by name and slice') def step_impl(context): context.data["c", 8:10] = numpy.ma.masked - nose.tools.assert_is(context.data["c"][8], numpy.ma.masked) - nose.tools.assert_is(context.data["c"][9], numpy.ma.masked) + test.assert_is(context.data["c"][8], numpy.ma.masked) + test.assert_is(context.data["c"][9], numpy.ma.masked) @then(u'deleting columns should change the table') def step_impl(context): del context.data["c"] - nose.tools.assert_equal(list(context.data.keys()), ["a", "b"]) - nose.tools.assert_equal(context.data.shape, (10, 2)) + test.assert_equal(list(context.data.keys()), ["a", "b"]) + test.assert_equal(context.data.shape, (10, 2)) @then(u'new columns must have a string name') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): context.data[3] = numpy.arange(10) @then(u'new columns must have the same number of rows as existing columns') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): context.data["c"] = numpy.random.random(4) @then(u'new columns must be one-dimensional') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): context.data["c"] = numpy.random.random((10, 4)) @then(u'per-column metadata can be specified') def step_impl(context): - nose.tools.assert_equal(context.data.metadata("b"), {}) + test.assert_equal(context.data.metadata("b"), {}) context.data.metadata("b")["foo"] = True - nose.tools.assert_equal(context.data.metadata("b"), {"foo": True}) + test.assert_equal(context.data.metadata("b"), {"foo": True}) - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): context.data.metadata("c") @@ -232,11 +232,11 @@ def step_impl(context): @then(u'the toyplot.data.Table is empty') def step_impl(context): - nose.tools.assert_equal(len(context.data), 0) - nose.tools.assert_equal(context.data.shape, (0, 0)) - nose.tools.assert_equal(list(context.data.items()), []) - nose.tools.assert_equal(list(context.data.keys()), []) - nose.tools.assert_equal(list(context.data.values()), []) + test.assert_equal(len(context.data), 0) + test.assert_equal(context.data.shape, (0, 0)) + test.assert_equal(list(context.data.items()), []) + test.assert_equal(list(context.data.keys()), []) + test.assert_equal(list(context.data.values()), []) @when(u'toyplot.data.Table is initialized with a toyplot.data.Table') @@ -257,7 +257,7 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the columns') def step_impl(context): table = toyplot.data.Table(context.data) - nose.tools.assert_equal(list(table.keys()), ["a", "b"]) + test.assert_equal(list(table.keys()), ["a", "b"]) numpy.testing.assert_array_equal( table["a"], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) numpy.testing.assert_array_equal( @@ -272,7 +272,7 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the columns, sorted by key') def step_impl(context): table = toyplot.data.Table(context.data) - nose.tools.assert_equal(list(table.keys()), ["a", "b"]) + test.assert_equal(list(table.keys()), ["a", "b"]) numpy.testing.assert_array_equal( table["a"], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) numpy.testing.assert_array_equal( @@ -289,7 +289,7 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the matrix columns with generated keys') def step_impl(context): table = toyplot.data.Table(context.data) - nose.tools.assert_equal(list(table.keys()), ["0", "1", "2", "3"]) + test.assert_equal(list(table.keys()), ["0", "1", "2", "3"]) numpy.testing.assert_array_equal( table["0"], [0, 4, 8, 12]) numpy.testing.assert_array_equal( @@ -312,7 +312,7 @@ def step_impl(context): @then(u'the toyplot.data.Table raises ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.data.Table(context.data) @@ -333,10 +333,10 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the csv file columns') def step_impl(context): - nose.tools.assert_equal(context.data.shape, (362, 6)) - nose.tools.assert_equal(list(context.data.keys()), ['STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) + test.assert_equal(context.data.shape, (362, 6)) + test.assert_equal(list(context.data.keys()), ['STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) for column in context.data.values(): - nose.tools.assert_true(issubclass(column.dtype.type, numpy.character)) + test.assert_true(issubclass(column.dtype.type, numpy.character)) @when(u'toyplot.data.Table is initialized with a csv file and conversion') @@ -346,10 +346,10 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the csv file columns with numeric type') def step_impl(context): - nose.tools.assert_equal(context.data.shape, (362, 6)) - nose.tools.assert_equal(list(context.data.keys()), ['STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) + test.assert_equal(context.data.shape, (362, 6)) + test.assert_equal(list(context.data.keys()), ['STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) for column, column_type in zip(context.data.values(), [numpy.character, numpy.character, numpy.integer, numpy.integer, numpy.integer, numpy.integer]): - nose.tools.assert_true(issubclass(column.dtype.type, column_type)) + test.assert_true(issubclass(column.dtype.type, column_type)) @when(u'toyplot.data.Table is initialized with a pandas dataframe') @@ -360,8 +360,8 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the data frame columns') def step_impl(context): - nose.tools.assert_equal(context.data.shape, (362, 6)) - nose.tools.assert_equal(list(context.data.keys()), ['STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) + test.assert_equal(context.data.shape, (362, 6)) + test.assert_equal(list(context.data.keys()), ['STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) @when(u'toyplot.data.Table is initialized with a pandas dataframe with index') @@ -372,8 +372,8 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the data frame columns plus an index column') def step_impl(context): - nose.tools.assert_equal(context.data.shape, (362, 7)) - nose.tools.assert_equal(list(context.data.keys()), ["index0", 'STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) + test.assert_equal(context.data.shape, (362, 7)) + test.assert_equal(list(context.data.keys()), ["index0", 'STATION', 'STATION_NAME', 'DATE', 'TMAX', 'TMIN', 'TOBS']) @when(u'toyplot.data.Table is initialized with a pandas dataframe with hierarchical index') @@ -386,8 +386,8 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the data frame columns plus multiple index columns') def step_impl(context): - nose.tools.assert_equal(context.data.shape, (4, 6)) - nose.tools.assert_equal(list(context.data.keys()), ["index0", 'index1', '0', '1', '2', '3']) + test.assert_equal(context.data.shape, (4, 6)) + test.assert_equal(list(context.data.keys()), ["index0", 'index1', '0', '1', '2', '3']) @when(u'toyplot.data.Table is initialized with a pandas dataframe with hierarchical index and custom index format') @@ -400,8 +400,8 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the data frame columns plus multiple custom format index columns') def step_impl(context): - nose.tools.assert_equal(context.data.shape, (4, 6)) - nose.tools.assert_equal(list(context.data.keys()), ["Index 0", 'Index 1', '0', '1', '2', '3']) + test.assert_equal(context.data.shape, (4, 6)) + test.assert_equal(list(context.data.keys()), ["Index 0", 'Index 1', '0', '1', '2', '3']) @when(u'toyplot.data.Table is initialized with a pandas dataframe with duplicate column names') @@ -412,13 +412,13 @@ def step_impl(context): @then(u'the toyplot.data.Table contains the data frame columns with uniqified column names') def step_impl(context): - nose.tools.assert_equal(list(context.data.keys()), ['STATION', 'DATE', 'STATION-1', 'DATE-1', 'DATE-2']) + test.assert_equal(list(context.data.keys()), ['STATION', 'DATE', 'STATION-1', 'DATE-1', 'DATE-2']) @then(u'the table can be rendered as format ipython html string') def step_impl(context): html = context.data._repr_html_() - nose.tools.assert_is_instance(html, str) + test.assert_is_instance(html, str) testing.assert_html_equal(html, "data-table") diff --git a/features/steps/data.py b/features/steps/data.py index 0622753f..4d9900c9 100644 --- a/features/steps/data.py +++ b/features/steps/data.py @@ -3,7 +3,7 @@ # rights in this software. from behave import * -import nose.tools +import test import numpy.testing import toyplot.data diff --git a/features/steps/documentation.py b/features/steps/documentation.py index abb2007f..b14294fd 100644 --- a/features/steps/documentation.py +++ b/features/steps/documentation.py @@ -3,7 +3,7 @@ # rights in this software. from behave import * -import nose.tools +import test import glob import logging diff --git a/features/steps/fill-visualization.py b/features/steps/fill-visualization.py index 530c4604..a9ccfa3d 100644 --- a/features/steps/fill-visualization.py +++ b/features/steps/fill-visualization.py @@ -4,7 +4,6 @@ from behave import * -import nose import numpy import toyplot.data diff --git a/features/steps/format.py b/features/steps/format.py index 3e5b6f30..86a7de0c 100644 --- a/features/steps/format.py +++ b/features/steps/format.py @@ -4,7 +4,7 @@ from behave import * -import nose.tools +import test import numpy.testing import toyplot.format @@ -39,7 +39,7 @@ def step_impl(context, value, output): value = eval(value) output = eval(output) prefix, separator, suffix = context.formatter.format(value) - nose.tools.assert_equal(prefix, output[0]) - nose.tools.assert_equal(separator, output[1]) - nose.tools.assert_equal(suffix, output[2]) + test.assert_equal(prefix, output[0]) + test.assert_equal(separator, output[1]) + test.assert_equal(suffix, output[2]) diff --git a/features/steps/image-visualization.py b/features/steps/image-visualization.py index 1fedeb82..4270a9f0 100644 --- a/features/steps/image-visualization.py +++ b/features/steps/image-visualization.py @@ -6,7 +6,7 @@ import os -import nose.tools +import test import numpy import PIL.Image import toyplot.color @@ -21,89 +21,89 @@ def step_impl(context): context.image = testing.read_png(os.path.join(art_dir, "toyplot-8-L.png")) context.image = context.image > 128 - nose.tools.assert_equal(context.image.shape, (256, 256, 1)) - nose.tools.assert_equal(context.image.dtype, "bool") + test.assert_equal(context.image.shape, (256, 256, 1)) + test.assert_equal(context.image.dtype, "bool") @given(u'a numpy 8 bit L image') def step_impl(context): context.image = testing.read_png(os.path.join(art_dir, "toyplot-8-L.png")) - nose.tools.assert_equal(context.image.shape, (256, 256, 1)) - nose.tools.assert_equal(context.image.dtype, "uint8") + test.assert_equal(context.image.shape, (256, 256, 1)) + test.assert_equal(context.image.dtype, "uint8") @given(u'a numpy 8 bit L image with colormap') def step_impl(context): context.image = testing.read_png(os.path.join(art_dir, "toyplot-8-L.png")) - nose.tools.assert_equal(context.image.shape, (256, 256, 1)) - nose.tools.assert_equal(context.image.dtype, "uint8") + test.assert_equal(context.image.shape, (256, 256, 1)) + test.assert_equal(context.image.dtype, "uint8") context.image = (context.image, toyplot.color.brewer.map("BlueRed")) @given(u'a numpy 8 bit LA image') def step_impl(context): context.image = testing.read_png(os.path.join(art_dir, "toyplot-8-LA.png")) - nose.tools.assert_equal(context.image.shape, (256, 256, 2)) - nose.tools.assert_equal(context.image.dtype, "uint8") + test.assert_equal(context.image.shape, (256, 256, 2)) + test.assert_equal(context.image.dtype, "uint8") @given(u'a numpy 8 bit RGB image') def step_impl(context): context.image = testing.read_png(os.path.join(art_dir, "toyplot-8-RGB.png")) - nose.tools.assert_equal(context.image.shape, (256, 256, 3)) - nose.tools.assert_equal(context.image.dtype, "uint8") + test.assert_equal(context.image.shape, (256, 256, 3)) + test.assert_equal(context.image.dtype, "uint8") @given(u'a numpy 8 bit RGBA image') def step_impl(context): context.image = testing.read_png(os.path.join(art_dir, "toyplot-8-RGBA.png")) - nose.tools.assert_equal(context.image.shape, (256, 256, 4)) - nose.tools.assert_equal(context.image.dtype, "uint8") + test.assert_equal(context.image.shape, (256, 256, 4)) + test.assert_equal(context.image.dtype, "uint8") @given(u'a pillow 8 bit L image') def step_impl(context): context.image = PIL.Image.open(os.path.join(art_dir, "toyplot-8-L.png")) - nose.tools.assert_equal(context.image.size, (256, 256)) - nose.tools.assert_equal(context.image.mode, "L") + test.assert_equal(context.image.size, (256, 256)) + test.assert_equal(context.image.mode, "L") @given(u'a pillow 8 bit L image with colormap') def step_impl(context): context.image = PIL.Image.open(os.path.join(art_dir, "toyplot-8-L.png")) - nose.tools.assert_equal(context.image.size, (256, 256)) - nose.tools.assert_equal(context.image.mode, "L") + test.assert_equal(context.image.size, (256, 256)) + test.assert_equal(context.image.mode, "L") context.image = (context.image, toyplot.color.brewer.map("BlueRed")) @given(u'a pillow 8 bit RGB image') def step_impl(context): context.image = PIL.Image.open(os.path.join(art_dir, "toyplot-8-RGB.png")) - nose.tools.assert_equal(context.image.size, (256, 256)) - nose.tools.assert_equal(context.image.mode, "RGB") + test.assert_equal(context.image.size, (256, 256)) + test.assert_equal(context.image.mode, "RGB") @given(u'a pillow 8 bit RGBA image') def step_impl(context): context.image = PIL.Image.open(os.path.join(art_dir, "toyplot-8-RGBA.png")) - nose.tools.assert_equal(context.image.size, (256, 256)) - nose.tools.assert_equal(context.image.mode, "RGBA") + test.assert_equal(context.image.size, (256, 256)) + test.assert_equal(context.image.mode, "RGBA") @given(u'a non-square numpy 8 bit L image') def step_impl(context): numpy.random.seed(1234) context.image = numpy.random.uniform(0, 1, size=(10, 5)).repeat(50, axis=0).repeat(50, axis=1) - nose.tools.assert_equal(context.image.shape, (500, 250)) - nose.tools.assert_equal(context.image.dtype, "float64") + test.assert_equal(context.image.shape, (500, 250)) + test.assert_equal(context.image.dtype, "float64") @given(u'a non-square numpy 8 bit L image with colormap') def step_impl(context): numpy.random.seed(1234) context.image = numpy.random.uniform(0, 1, size=(10, 5)).repeat(50, axis=0).repeat(50, axis=1) - nose.tools.assert_equal(context.image.shape, (500, 250)) - nose.tools.assert_equal(context.image.dtype, "float64") + test.assert_equal(context.image.shape, (500, 250)) + test.assert_equal(context.image.dtype, "float64") context.image = (context.image, toyplot.color.linear.map("Blackbody")) diff --git a/features/steps/legends.py b/features/steps/legends.py index 12bba7c4..1583c4d7 100644 --- a/features/steps/legends.py +++ b/features/steps/legends.py @@ -3,7 +3,7 @@ # rights in this software. from behave import * -import nose.tools +import test import numpy def sample_data(): diff --git a/features/steps/log-scale-axes.py b/features/steps/log-scale-axes.py index 00a29c4f..8fb2460d 100644 --- a/features/steps/log-scale-axes.py +++ b/features/steps/log-scale-axes.py @@ -4,7 +4,6 @@ from behave import * -import nose import numpy import toyplot.data diff --git a/features/steps/plot-visualization.py b/features/steps/plot-visualization.py index 96d5f028..af486c19 100644 --- a/features/steps/plot-visualization.py +++ b/features/steps/plot-visualization.py @@ -6,7 +6,7 @@ import collections -import nose.tools +import test import numpy.testing import toyplot diff --git a/features/steps/projection.py b/features/steps/projection.py index 283d98a7..0da8eb4c 100644 --- a/features/steps/projection.py +++ b/features/steps/projection.py @@ -3,7 +3,7 @@ # rights in this software. from behave import * -import nose.tools +import test import numpy.testing import toyplot.projection @@ -16,20 +16,20 @@ def step_impl(context): @then(u'0 should project to 0') def step_impl(context): - nose.tools.assert_equal(context.projection(0), 0) - nose.tools.assert_equal(context.projection.inverse(0), 0) + test.assert_equal(context.projection(0), 0) + test.assert_equal(context.projection.inverse(0), 0) @then(u'0.5 should project to 50') def step_impl(context): - nose.tools.assert_equal(context.projection(0.5), 50) - nose.tools.assert_equal(context.projection.inverse(50), 0.5) + test.assert_equal(context.projection(0.5), 50) + test.assert_equal(context.projection.inverse(50), 0.5) @then(u'1 should project to 100') def step_impl(context): - nose.tools.assert_equal(context.projection(1.0), 100) - nose.tools.assert_equal(context.projection.inverse(100), 1.0) + test.assert_equal(context.projection(1.0), 100) + test.assert_equal(context.projection.inverse(100), 1.0) @given(u'A log10 projection with 1, 100 and 0, 100') @@ -39,20 +39,20 @@ def step_impl(context): @then(u'1 should project to 0') def step_impl(context): - nose.tools.assert_equal(context.projection(1), 0) - nose.tools.assert_equal(context.projection.inverse(0), 1) + test.assert_equal(context.projection(1), 0) + test.assert_equal(context.projection.inverse(0), 1) @then(u'10 should project to 50') def step_impl(context): - nose.tools.assert_equal(context.projection(10), 50) - nose.tools.assert_equal(context.projection.inverse(50), 10) + test.assert_equal(context.projection(10), 50) + test.assert_equal(context.projection.inverse(50), 10) @then(u'100 should project to 100') def step_impl(context): - nose.tools.assert_equal(context.projection(100), 100) - nose.tools.assert_equal(context.projection.inverse(100), 100) + test.assert_equal(context.projection(100), 100) + test.assert_equal(context.projection.inverse(100), 100) @given(u'A log10 projection with -100, 100 and 0, 100') @@ -62,11 +62,11 @@ def step_impl(context): @then(u'-100 should project to 0') def step_impl(context): - nose.tools.assert_equal(context.projection(-100), 0) - nose.tools.assert_equal(context.projection.inverse(0), -100) + test.assert_equal(context.projection(-100), 0) + test.assert_equal(context.projection.inverse(0), -100) @then(u'0 should project to 50') def step_impl(context): - nose.tools.assert_equal(context.projection(0), 50) - nose.tools.assert_equal(context.projection.inverse(50), 0) + test.assert_equal(context.projection(0), 50) + test.assert_equal(context.projection.inverse(50), 0) diff --git a/features/steps/rectangle-visualization.py b/features/steps/rectangle-visualization.py index 976d4bac..d9150f5c 100644 --- a/features/steps/rectangle-visualization.py +++ b/features/steps/rectangle-visualization.py @@ -6,7 +6,7 @@ import collections -import nose.tools +import test import numpy.testing import toyplot diff --git a/features/steps/scatterplot-visualization.py b/features/steps/scatterplot-visualization.py index 70d7664a..6a934fc3 100644 --- a/features/steps/scatterplot-visualization.py +++ b/features/steps/scatterplot-visualization.py @@ -6,7 +6,7 @@ import collections -import nose.tools +import test import numpy.testing import toyplot diff --git a/features/steps/table-visualization.py b/features/steps/table-visualization.py index 423b2b27..34d635c1 100644 --- a/features/steps/table-visualization.py +++ b/features/steps/table-visualization.py @@ -4,7 +4,6 @@ from behave import * -import nose import numpy import toyplot.data diff --git a/features/steps/test.py b/features/steps/test.py new file mode 100644 index 00000000..04a16ba8 --- /dev/null +++ b/features/steps/test.py @@ -0,0 +1,41 @@ +# 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. + + +import unittest + +# Repackaging existing tests for PEP-8. + +def assert_almost_equal(first, second, places=7, delta=None, msg=None): + return unittest.TestCase().assertAlmostEqual(first, second, places=places, msg=msg, delta=delta) + +def assert_dict_equal(first, second, msg=None): + return unittest.TestCase().assertDictEqual(first, second, msg) + +def assert_equal(first, second, msg=None): + return unittest.TestCase().assertEqual(first, second, msg) + +def assert_false(expr, msg=None): + return unittest.TestCase().assertFalse(expr, msg) + +def assert_is(first, second, msg=None): + return unittest.TestCase().assertIs(first, second, msg) + +def assert_is_instance(obj, cls, msg=None): + return unittest.TestCase().assertIsInstance(obj, cls, msg) + +def assert_is_none(expr, msg=None): + return unittest.TestCase().assertIsNone(expr, msg) + +def assert_logs(logger=None, level=None): + return unittest.TestCase().assertLogs(logger, level) + +def assert_no_logs(logger=None, level=None): + return unittest.TestCase().assertNoLogs(logger, level) + +def assert_sequence_equal(first, second, msg=None, seq_type=None): + return unittest.TestCase().assertSequenceEqual(first, second, msg, seq_type) + +def assert_true(expr, msg=None): + return unittest.TestCase().assertTrue(expr, msg) diff --git a/features/steps/text.py b/features/steps/text.py index 16a45797..d81721a1 100644 --- a/features/steps/text.py +++ b/features/steps/text.py @@ -4,7 +4,7 @@ from behave import * -import nose.tools +import test import toyplot.html @@ -184,7 +184,7 @@ def step_impl(context): @when(u'text is aligned with an unknown text-anchor value, an exception is raised.') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): context.axes.text( 0, 0, "Text!", style={"text-anchor": "foo"}) toyplot.html.render(context.canvas) @@ -192,7 +192,7 @@ def step_impl(context): @when(u'text is aligned with an unknown alignment-baseline value, an exception is raised.') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): context.axes.text( 0, 0, "Text!", style={"alignment-baseline": "foo"}) toyplot.html.render(context.canvas) @@ -210,7 +210,7 @@ def step_impl(context, family): @when(u'text is drawn with an unknown font family, an exception is raised.') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): context.axes.text(0, 0, "Font-family: nonexistent", style={"font-family": "nonexistent", "font-size": "32px"}) context.canvas._repr_html_() diff --git a/features/steps/units.py b/features/steps/units.py index 2313b5e4..a2d9abae 100644 --- a/features/steps/units.py +++ b/features/steps/units.py @@ -3,7 +3,7 @@ # rights in this software. from behave import * -import nose.tools +import test import toyplot.units @@ -11,103 +11,103 @@ @when( u'converting "0" without default units to points the response should be 0.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert("0", "pt"), 0.0) + test.assert_equal(toyplot.units.convert("0", "pt"), 0.0) @when( u'converting 0 without default units to points the response should be 0.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert(0, "pt"), 0.0) + test.assert_equal(toyplot.units.convert(0, "pt"), 0.0) @when( u'converting 72 without default units to points the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert(72, "pt") @when(u'converting 72 with default units pt to in the response should be 1.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert(72, "in", default="pt"), 1.0) + test.assert_equal(toyplot.units.convert(72, "in", default="pt"), 1.0) @when(u'converting "72pt" to in the response should be 1.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert("72pt", "in"), 1.0) + test.assert_equal(toyplot.units.convert("72pt", "in"), 1.0) @when(u'converting (72, "pt") to in the response should be 1.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert((72, "pt"), "in"), 1.0) + test.assert_equal(toyplot.units.convert((72, "pt"), "in"), 1.0) @when( u'converting "100%" without reference to in the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert("100%", "in") @when(u'converting "100%" with reference 1.0 to in the response should be 1.0') def step_impl(context): - nose.tools.assert_equal( + test.assert_equal( toyplot.units.convert("100%", "in", reference=1.0), 1.0) @when(u'converting "40%" with reference 1.0 to in the response should be 0.4') def step_impl(context): - nose.tools.assert_equal( + test.assert_equal( toyplot.units.convert("40%", "in", reference=1.0), 0.4) @when(u'converting "96px" to in the response should be 1.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert("96px", "in"), 1.0) + test.assert_equal(toyplot.units.convert("96px", "in"), 1.0) @when(u'converting "96px" to pt the response should be 72.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert("96px", "pt"), 72.0) + test.assert_equal(toyplot.units.convert("96px", "pt"), 72.0) @when(u'converting ".5in" to pt the response should be 36.0') def step_impl(context): - nose.tools.assert_equal(toyplot.units.convert(".5in", "pt"), 36.0) + test.assert_equal(toyplot.units.convert(".5in", "pt"), 36.0) @when(u'converting "1in" to cm the response should be 2.54') def step_impl(context): - nose.tools.assert_almost_equal(toyplot.units.convert("1in", "cm"), 2.54) + test.assert_almost_equal(toyplot.units.convert("1in", "cm"), 2.54) @when(u'converting "1furlong" to in the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert("1furlong", "in") @when(u'converting ("72pt",) to in the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert(("72pt",), "in") @when(u'converting "1in" to furlong the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert("1in", "furlong") @when(u'converting [] to in the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert([], "in") @when(u'converting ("1","cm") to in the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert(("1", "cm"), "in") @when(u'converting (1,2) to in the response should be raise ValueError') def step_impl(context): - with nose.tools.assert_raises(ValueError): + with test.assert_raises(ValueError): toyplot.units.convert((1, 2), "in") diff --git a/pyproject.toml b/pyproject.toml index 4a70f29d..e7a90065 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ all = [ "coverage", "ipykernel", "nbsphinx", - "nose", "pandas", "scikit-image", "sphinx >= 3.5", From 2f640f16bfb100814d073da7c89d7dc1b85216da Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 14:28:43 -0600 Subject: [PATCH 08/15] Add missing test.assert_raises() API. --- features/steps/cartesian-coordinates.py | 1 + features/steps/test.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/features/steps/cartesian-coordinates.py b/features/steps/cartesian-coordinates.py index 8c084069..43dadc1e 100644 --- a/features/steps/cartesian-coordinates.py +++ b/features/steps/cartesian-coordinates.py @@ -7,6 +7,7 @@ import numpy import toyplot.data +import test import testing diff --git a/features/steps/test.py b/features/steps/test.py index 04a16ba8..231f4194 100644 --- a/features/steps/test.py +++ b/features/steps/test.py @@ -34,6 +34,9 @@ def assert_logs(logger=None, level=None): def assert_no_logs(logger=None, level=None): return unittest.TestCase().assertNoLogs(logger, level) +def assert_raises(exception): + return unittest.TestCase().assertRaises(exception) + def assert_sequence_equal(first, second, msg=None, seq_type=None): return unittest.TestCase().assertSequenceEqual(first, second, msg, seq_type) From 8573bba1feeae66e61ed96c3b74edd7db7d87e96 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 14:42:02 -0600 Subject: [PATCH 09/15] Add regression testing for Python 3.12. --- .github/workflows/regression-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index b54190a1..9a9cf167 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 From e6bb3e052a1b3328554be9e902b41f5777c44501 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 21:38:51 -0600 Subject: [PATCH 10/15] Add separate tests for numpy 1 and numpy 2. --- .github/workflows/regression-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 9a9cf167..c6ad6cbf 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -13,6 +13,7 @@ jobs: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + numpy-version: ["<2.0.0", ">=2.0.0"] steps: - uses: actions/checkout@v3 @@ -25,6 +26,7 @@ jobs: sudo apt-get install ffmpeg ghostscript python -m pip install --upgrade pip pip install coveralls + pip install numpy${{ matrix.numpy-version }} pip install .[all] - name: Run tests run: | From 55bff26e0c08bdff1e738c8e1c64c803f4f9ac3c Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 21:40:09 -0600 Subject: [PATCH 11/15] Tweaking the numpy version tests. --- .github/workflows/regression-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index c6ad6cbf..76a9d10d 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - numpy-version: ["<2.0.0", ">=2.0.0"] + numpy-version: ["numpy<2.0.0", "numpy>=2.0.0"] steps: - uses: actions/checkout@v3 @@ -26,7 +26,7 @@ jobs: sudo apt-get install ffmpeg ghostscript python -m pip install --upgrade pip pip install coveralls - pip install numpy${{ matrix.numpy-version }} + pip install ${{ matrix.numpy-version }} pip install .[all] - name: Run tests run: | From 15a7a616a7a2646d1ee990b511c957e14395161e Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 21:44:18 -0600 Subject: [PATCH 12/15] More work on numpy version tests. --- .github/workflows/regression-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 76a9d10d..90e4f07e 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -26,7 +26,7 @@ jobs: sudo apt-get install ffmpeg ghostscript python -m pip install --upgrade pip pip install coveralls - pip install ${{ matrix.numpy-version }} + pip install "${{ matrix.numpy-version }}" pip install .[all] - name: Run tests run: | From 4d310d2fd462659f7409dad98180aaab10ebc104 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 21:49:35 -0600 Subject: [PATCH 13/15] Don't test NumPy 2 with Python 3.8, it requires Python 3.9 or later. --- .github/workflows/regression-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 90e4f07e..3bf7e128 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -14,6 +14,9 @@ jobs: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] numpy-version: ["numpy<2.0.0", "numpy>=2.0.0"] + exclude: + - python-version: "3.8" + numpy-version: "numpy>=2.0.0" steps: - uses: actions/checkout@v3 From 25f1fed884815e303021e3b44f47d773cb5f9024 Mon Sep 17 00:00:00 2001 From: "Timothy M. Shead" Date: Thu, 11 Jul 2024 21:59:05 -0600 Subject: [PATCH 14/15] Allow NumPy>=2.0.0. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e7a90065..2f42ae49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ dependencies = [ "arrow>=1.0", "custom_inherit", "multipledispatch", - "numpy>=1.8.0,<2.0.0", + "numpy>=1.8.0", "packaging", "pypng", "reportlab", From cce091bb5092cf564f936b73324ce593cbf7c58d Mon Sep 17 00:00:00 2001 From: Michael Harms Date: Fri, 12 Jul 2024 11:44:20 -0700 Subject: [PATCH 15/15] %r -> %s to handle new numpy repr behavior --- toyplot/font.py | 2 +- toyplot/html.py | 50 +++++++++++++++++++++++----------------------- toyplot/require.py | 4 ++-- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/toyplot/font.py b/toyplot/font.py index 1b6ad5f1..10affccd 100644 --- a/toyplot/font.py +++ b/toyplot/font.py @@ -89,7 +89,7 @@ def __init__(self, family, size): self._descent = toyplot.units.convert(descent, target="px", default="pt") def __repr__(self): # pragma: no cover - return "" % (self._family, self._size, self.ascent, self.descent) + return "" % (self._family, self._size, self.ascent, self.descent) @property def ascent(self): diff --git a/toyplot/html.py b/toyplot/html.py index 38311f83..da29aabf 100644 --- a/toyplot/html.py +++ b/toyplot/html.py @@ -457,9 +457,9 @@ def _draw_text( transform = "" if x or y: - transform += "translate(%r,%r)" % (x, y) + transform += "translate(%s,%s)" % (x, y) if angle: - transform += "rotate(%r)" % (-angle) # pylint: disable=invalid-unary-operand-type + transform += "rotate(%s)" % (-angle) # pylint: disable=invalid-unary-operand-type group = xml.SubElement( root, @@ -618,7 +618,7 @@ def _draw_bar(parent_xml, size, angle=0): y2=str(size / 2), ) if angle: - markup.set("transform", "rotate(%r)" % (-angle,)) + markup.set("transform", "rotate(%s)" % (-angle,)) def _draw_rect(parent_xml, size, width=1, height=1, angle=0): @@ -631,21 +631,21 @@ def _draw_rect(parent_xml, size, width=1, height=1, angle=0): height=str(size * height), ) if angle: - markup.set("transform", "rotate(%r)" % (-angle,)) + markup.set("transform", "rotate(%s)" % (-angle,)) def _draw_triangle(parent_xml, size, angle=0): markup = xml.SubElement( parent_xml, "polygon", - points=" ".join(["%r,%r" % (xp, yp) for xp, yp in [ + points=" ".join(["%s,%s" % (xp, yp) for xp, yp in [ (-size / 2, size / 2), (0, -size / 2), (size / 2, size / 2), ]]), ) if angle: - markup.set("transform", "rotate(%r)" % (-angle,)) + markup.set("transform", "rotate(%s)" % (-angle,)) def _draw_circle(parent_xml, size): @@ -673,9 +673,9 @@ def _draw_marker( xml.SubElement(marker_xml, "title").text = str(title) if transform is None: - transform = "translate(%r, %r)" % (cx, cy) + transform = "translate(%s, %s)" % (cx, cy) if marker.angle: - transform += " rotate(%r)" % (-marker.angle,) + transform += " rotate(%s)" % (-marker.angle,) marker_xml.set("transform", transform) if marker.shape == "|": @@ -750,7 +750,7 @@ def _draw_marker( { "-toyplot-vertical-align": "middle", "fill": toyplot.color.black, - "font-size": "%rpx" % (marker.size * 0.75), + "font-size": "%spx" % (marker.size * 0.75), "stroke": "none", "text-anchor": "middle", }, @@ -794,9 +794,9 @@ def _render(canvas, context): "xmlns:toyplot": "http://www.sandia.gov/toyplot", "xmlns:xlink": "http://www.w3.org/1999/xlink", }, - width="%rpx" % canvas.width, - height="%rpx" % canvas.height, - viewBox="0 0 %r %r" % (canvas.width, canvas.height), + width="%spx" % canvas.width, + height="%spx" % canvas.height, + viewBox="0 0 %s %s" % (canvas.width, canvas.height), preserveAspectRatio="xMidYMid meet", style=_css_style(canvas._style), id=context.get_id(canvas)) @@ -2176,7 +2176,7 @@ def _render(axes, mark, context): numpy.concatenate((boundary1[segment], boundary2[segment][::-1])), numpy.concatenate((position[segment], position[segment][::-1]))) series_xml = xml.SubElement(mark_xml, "polygon", points=" ".join( - ["%r,%r" % (xi, yi) for xi, yi in coordinates]), style=_css_style(series_style)) + ["%s,%s" % (xi, yi) for xi, yi in coordinates]), style=_css_style(series_style)) if title is not None: xml.SubElement(series_xml, "title").text = str(title) @@ -2222,7 +2222,7 @@ def _render(axes, mark, context): numpy.concatenate((boundary1[segment], boundary2[segment][::-1])), numpy.concatenate((position[segment], position[segment][::-1]))) series_xml = xml.SubElement(mark_xml, "polygon", points=" ".join( - ["%r,%r" % (xi, yi) for xi, yi in coordinates]), style=_css_style(series_style)) + ["%s,%s" % (xi, yi) for xi, yi in coordinates]), style=_css_style(series_style)) if title is not None: xml.SubElement(series_xml, "title").text = str(title) @@ -2319,7 +2319,7 @@ def _render(axes, mark, context): x = axes.project("x", dx + p[:,0]) y = axes.project("y", dy + p[:,1]) - points = ["%r,%r" % point for point in zip(x, y)] + points = ["%s,%s" % point for point in zip(x, y)] datum_xml = xml.SubElement( series_xml, @@ -2500,16 +2500,16 @@ def _render(axes, mark, context): # pragma: no cover edge_coordinates[estart+1][0] - edge_coordinates[estart][0], )) - transform = "translate(%r, %r)" % (edge_coordinates[estart][0], edge_coordinates[estart][1]) + transform = "translate(%s, %s)" % (edge_coordinates[estart][0], edge_coordinates[estart][1]) if edge_angle: - transform += " rotate(%r)" % (-edge_angle,) - transform += " translate(%r, 0)" % (marker.size / 2,) + transform += " rotate(%s)" % (-edge_angle,) + transform += " translate(%s, 0)" % (marker.size / 2,) if marker.angle is not None: if isinstance(marker.angle, str) and marker.angle[0:1] == "r": angle = as_float(marker.angle[1:]) else: angle = -edge_angle + as_float(marker.angle) - transform += " rotate(%r)" % (-angle,) + transform += " rotate(%s)" % (-angle,) _draw_marker( @@ -2572,16 +2572,16 @@ def _render(axes, mark, context): # pragma: no cover edge_coordinates[end-1][0] - edge_coordinates[end-2][0], )) - transform = "translate(%r, %r)" % (edge_coordinates[end-1][0], edge_coordinates[end-1][1]) + transform = "translate(%s, %s)" % (edge_coordinates[end-1][0], edge_coordinates[end-1][1]) if edge_angle: - transform += " rotate(%r)" % (-edge_angle,) - transform += " translate(%r, 0)" % (-marker.size / 2,) + transform += " rotate(%s)" % (-edge_angle,) + transform += " translate(%s, 0)" % (-marker.size / 2,) if marker.angle is not None: if isinstance(marker.angle, str) and marker.angle[0:1] == "r": angle = as_float(marker.angle[1:]) else: angle = -edge_angle + as_float(marker.angle) - transform += " rotate(%r)" % (-angle,) + transform += " rotate(%s)" % (-angle,) _draw_marker( @@ -2682,9 +2682,9 @@ def _render(axes, mark, context): for segment in segments: start, stop, step = segment.indices(len(not_null)) for i in range(start, start + 1): - d.append("M %r %r" % (x[i], y[i])) + d.append("M %s %s" % (x[i], y[i])) for i in range(start + 1, stop): - d.append("L %r %r" % (x[i], y[i])) + d.append("L %s %s" % (x[i], y[i])) xml.SubElement( series_xml, "path", diff --git a/toyplot/require.py b/toyplot/require.py index fe32ff21..49cf4858 100644 --- a/toyplot/require.py +++ b/toyplot/require.py @@ -112,7 +112,7 @@ def hyperlink(value): return optional_string(value) def as_int(value,precision=None): - """Raise an exception of a value cannot be converted to an int, or value + """Raise an exception if a value cannot be converted to an int, or value coerced to a python int. precision is optional but can be 8, 16, etc.""" # Try simple conversion; if this fails, move on @@ -130,7 +130,7 @@ def as_int(value,precision=None): def as_float(value): - """Raise an exception of a value cannot be converted to a float, or value + """Raise an exception if a value cannot be converted to a float, or value coerced to a python float.""" # Try simple conversion; if this fails, move on