From 533462655aa71c80a8b6f1b32fcad8f903354d08 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Wed, 30 Mar 2016 23:50:36 +0200 Subject: [PATCH] Close issue https://github.com/Phyks/replot/issues/2 This commit adds padding around the graph, depending on the used linewidth, to close issue https://github.com/Phyks/replot/issues/2. --- Examples.ipynb | 43 +++++++++++++++--------- replot/figure.py | 40 +++++++++++++++++++++-- replot/helpers/render.py | 70 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 17 deletions(-) diff --git a/Examples.ipynb b/Examples.ipynb index 49bf383..2a72d3e 100644 --- a/Examples.ipynb +++ b/Examples.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -36,7 +36,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -65,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 25, "metadata": { "collapsed": false, "scrolled": false @@ -3221,11 +3221,24 @@ " figure.plot(x, y, \"x\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Plotting broken lines\n", + "# TODO" + ] + }, { "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": false, + "scrolled": false }, "outputs": [ { @@ -6403,7 +6416,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 19, "metadata": { "collapsed": false, "scrolled": false @@ -7176,7 +7189,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -7952,7 +7965,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -8728,7 +8741,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -9504,7 +9517,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -10280,7 +10293,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -10329,7 +10342,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -11101,7 +11114,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -11877,7 +11890,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -11909,7 +11922,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 26, "metadata": { "collapsed": false }, @@ -12681,7 +12694,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" diff --git a/replot/figure.py b/replot/figure.py index c90a374..5b6b83a 100644 --- a/replot/figure.py +++ b/replot/figure.py @@ -536,10 +536,46 @@ class Figure(): self.legend, lambda: self._legend(axis, overload_legend=True) ) - # Set xrange + # Set xrange / yrange render_helpers.set_axis_property(group_, axis.set_xlim, self.xrange) - # Set yrange render_helpers.set_axis_property(group_, axis.set_ylim, self.yrange) + # Note: Extend axes limits to have the full plot, even with large + # linewidths. This is necessary as we do not clip lines. + maximum_linewidth = max( + max([plt[1].get("lw", 0) for plt in self.plots[group_]]), + max([plt[1].get("linewidth", 0) for plt in self.plots[group_]]) + ) + if maximum_linewidth > 0: + # Only extend axes limits if linewidths is larger than the default + # one. + ticks_position = { # Dump ticks position to restore them afterwards + "x": (axis.xaxis.get_majorticklocs(), + axis.xaxis.get_minorticklocs()), + "y": (axis.yaxis.get_majorticklocs(), + axis.yaxis.get_minorticklocs()) + } + # Set xrange + extra_xrange = render_helpers.data_units_from_points( + maximum_linewidth, + axis, + reference="x") + xrange = (axis.get_xlim()[0] - extra_xrange / 2, + axis.get_xlim()[1] + extra_xrange / 2) + render_helpers.set_axis_property(group_, axis.set_xlim, xrange) + # Set yrange + extra_yrange = render_helpers.data_units_from_points( + maximum_linewidth, + axis, + reference="y") + yrange = (axis.get_ylim()[0] - extra_yrange / 2, + axis.get_ylim()[1] + extra_yrange / 2) + render_helpers.set_axis_property(group_, axis.set_ylim, yrange) + # Restore ticks + axis.xaxis.set_ticks(ticks_position["x"][0], minor=False) + axis.xaxis.set_ticks(ticks_position["x"][1], minor=True) + axis.yaxis.set_ticks(ticks_position["y"][0], minor=False) + axis.yaxis.set_ticks(ticks_position["y"][1], minor=True) + def _render_gif_animation(self, figure, axes): """ diff --git a/replot/helpers/render.py b/replot/helpers/render.py index ae8a520..4a03a8f 100644 --- a/replot/helpers/render.py +++ b/replot/helpers/render.py @@ -1,6 +1,7 @@ """ Various helper functions for plotting. """ +import numpy as np def set_axis_property(group_, setter, value, default_setter=None): @@ -26,3 +27,72 @@ def set_axis_property(group_, setter, value, default_setter=None): else: if value is not None: setter(value) + + +def linewidth_from_data_units(linewidth, axis, reference='y'): + """ + Convert a linewidth in data units to linewidth in points. + + Parameters + ---------- + linewidth: float + Linewidth in data units of the respective reference-axis + axis: matplotlib axis + The axis which is used to extract the relevant transformation + data (data limits and size must not change afterwards) + reference: string + The axis that is taken as a reference for the data width. + Possible values: 'x' and 'y'. Defaults to 'y'. + + Returns + ------- + linewidth: float + Linewidth in points + + From https://stackoverflow.com/questions/19394505/matplotlib-expand-the-line-with-specified-width-in-data-unit. + """ + fig = axis.get_figure() + if reference == 'x': + length = fig.bbox_inches.width * axis.get_position().width + value_range = np.diff(axis.get_xlim()) + elif reference == 'y': + length = fig.bbox_inches.height * axis.get_position().height + value_range = np.diff(axis.get_ylim()) + # Convert length to points + length *= 72 # Inches to points is a fixed conversion in matplotlib + # Scale linewidth to value range + return linewidth * (length / value_range) + + +def data_units_from_points(points, axis, reference='y'): + """ + Convert points to data units on the given axis. + + Parameters + ---------- + points: float + Value in points to convert. + axis: matplotlib axis + The axis which is used to extract the relevant transformation + data (data limits and size must not change afterwards) + reference: string + The axis that is taken as a reference for the data width. + Possible values: 'x' and 'y'. Defaults to 'y'. + + Returns + ------- + points: float + Converted value. + """ + fig = axis.get_figure() + + if reference == 'x': + length = fig.bbox_inches.width * axis.get_position().width + value_range = np.diff(axis.get_xlim()) + elif reference == 'y': + length = fig.bbox_inches.height * axis.get_position().height + value_range = np.diff(axis.get_ylim()) + # Convert length to points + length *= 72 # Inches to points is a fixed conversion in matplotlib + # Scale linewidth to value range + return points / (length / value_range)