diff --git a/README.md b/README.md index 99c4153..8dd060a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This package creates clean and beautiful plots that work on light and dark backgrounds. Inspired by the work of [Edward Tufte](https://en.wikipedia.org/wiki/Edward_Tufte). - | + | :----:|:----:| To use, simply select the `dufte` style. Check out `dufte.legend()` and @@ -29,21 +29,21 @@ import numpy as np plt.style.use(dufte.style) -np.random.seed(0) +rng = np.random.default_rng(0) x0 = np.linspace(0.0, 3.0, 100) y0 = x0 / (x0 + 1) -y0 += 0.1 * np.random.rand(len(y0)) +y0 += 0.1 * rng.random(len(y0)) plt.plot(x0, y0, label="no balacing") x1 = np.linspace(0.0, 3.0, 100) y1 = 1.5 * x1 / (x1 + 1) -y1 += 0.1 * np.random.rand(len(y1)) +y1 += 0.1 * rng.random(len(y1)) plt.plot(x1, y1, label="CRV-27") x2 = np.linspace(0.0, 3.0, 100) y2 = 1.6 * x2 / (x2 + 1) -y2 += 0.1 * np.random.rand(len(y2)) +y2 += 0.1 * rng.random(len(y2)) plt.plot(x2, y2, label="CRV-27*") dufte.ylabel("ylabel") @@ -51,8 +51,30 @@ dufte.legend() plt.show() ``` +The bar plot is created with `dufte.style_bar` here and `dufte.show_bar_values()`. +Note the use of `context` instead of `style.use()`; both are appropriate. +```python +import matplotlib.pyplot as plt +import dufte + + +with plt.style.context(dufte.style_bar): + labels = ["Australia", "Brazil", "China", "Germany", "Mexico", "United\nStates"] + vals = [21.65, 24.5, 6.95, 8.40, 21.00, 8.55] + xpos = range(len(vals)) + plt.bar(xpos, vals) + plt.xticks(xpos, labels) + dufte.show_bar_values("{:.2f}") + plt.title("average temperature [°C]") + plt.show() +``` + Further reading: + * [Remove to improve: data-ink ratio](https://www.darkhorseanalytics.com/blog/data-looks-better-naked) + + * [Remove to improve: Line Graph Edition](https://youtu.be/bDbJBWvonVI) + * [Show the Data - Maximize the Data Ink Ratio](https://youtu.be/pCp0a5_YIWE) * [Randal S. Olson's blog entry](http://www.randalolson.com/2014/06/28/how-to-make-beautiful-data-visualizations-in-python-with-matplotlib/) * [prettyplotlib](https://github.com/olgabot/prettyplotlib) * [Wikipedia: Chartjunk](https://en.wikipedia.org/wiki/Chartjunk) diff --git a/setup.cfg b/setup.cfg index 2d6ddfb..d5df9fe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dufte -version = 0.2.23 +version = 0.2.24 author = Nico Schlömer author_email = nico.schloemer@gmail.com description = Clean matplotlib plots diff --git a/src/dufte/__init__.py b/src/dufte/__init__.py index ce47e6a..84cb9ff 100644 --- a/src/dufte/__init__.py +++ b/src/dufte/__init__.py @@ -1,3 +1,3 @@ -from .main import legend, style, ylabel +from .main import legend, show_bar_values, style, style_bar, ylabel -__all__ = ["legend", "style", "ylabel"] +__all__ = ["legend", "style", "style_bar", "ylabel", "show_bar_values"] diff --git a/src/dufte/main.py b/src/dufte/main.py index a039177..90f520c 100644 --- a/src/dufte/main.py +++ b/src/dufte/main.py @@ -30,6 +30,8 @@ # decides to turn them on. "axes.edgecolor": _gray, "axes.linewidth": _stroke_width, + # default is "line", i.e., below lines but above patches (bars) + "axes.axisbelow": True, # "ytick.right": False, "ytick.color": _gray, @@ -74,10 +76,18 @@ "9edae5", ], ), - "axes.titlepad": 30.0, - "axes.titlesize": 14, + "axes.titlepad": 40, + "axes.titlesize": 18, + "axes.titlelocation": "left", } +style_bar = style.copy() +# hide xticks for bars; the label is enough +style_bar["xtick.major.width"] = 0 +# unhide the bar labels +style_bar["xtick.major.pad"] = 13 +style_bar["font.size"] = 16 + def _move_min_distance(targets, min_distance): """Move the targets such that they are close to their original positions, but keep @@ -225,3 +235,29 @@ def ylabel(string): # place the label 10% above the top tick ax.yaxis.set_label_coords(pos_x, pos_y) ylabel.set_rotation(0) + + +def show_bar_values(fmt="{}"): + ax = plt.gca() + + # turn off y-ticks and y-grid + plt.tick_params(axis="y", which="both", left=False, right=False, labelleft=False) + plt.grid(False) + + data_to_axis = ax.transData + ax.transAxes.inverted() + axis_to_data = ax.transAxes + ax.transData.inverted() + + for rect in ax.patches: + height = rect.get_height() + ypos_ax = data_to_axis.transform([1.0, height]) + ypos = axis_to_data.transform(ypos_ax - 0.1)[1] + ax.text( + rect.get_x() + rect.get_width() / 2, + ypos, + fmt.format(height), + size=14, + weight="bold", + ha="center", + va="bottom", + color="white", + ) diff --git a/tests/test_bar.py b/tests/test_bar.py new file mode 100644 index 0000000..455f97c --- /dev/null +++ b/tests/test_bar.py @@ -0,0 +1,32 @@ +import matplotlib.pyplot as plt + +import dufte + + +def test_bar(filename=None, light=True): + with plt.style.context(dufte.style_bar): + labels = ["Australia", "Brazil", "China", "Germany", "Mexico", "United\nStates"] + vals = [21.65, 24.5, 6.95, 8.40, 21.00, 8.55] + xpos = range(len(vals)) + plt.bar(xpos, vals) + plt.xticks(xpos, labels) + dufte.show_bar_values("{:.2f}") + plt.title("average temperature [°C]") + + if not light: + gh_dark_bg = "#0d1117" + plt.gca().set_facecolor(gh_dark_bg) + plt.gcf().patch.set_facecolor(gh_dark_bg) + + if filename: + plt.savefig(filename, transparent=True, bbox_inches="tight") + else: + plt.show() + + +if __name__ == "__main__": + # test_bar("bar-light.svg", True) + # plt.close() + # test_bar("bar-dark.svg", False) + test_bar("bar.svg", False) + plt.close() diff --git a/tests/test_plot.py b/tests/test_plot.py index 91f7ffc..18d7a39 100644 --- a/tests/test_plot.py +++ b/tests/test_plot.py @@ -12,12 +12,13 @@ def test_plot(filename, light: bool, noise, offsets): plt.style.use(dufte.style) - np.random.seed(0) + rng = np.random.default_rng(0) + x0 = np.linspace(0.0, 3.0, 100) labels = ["no balancing", "CRV-27", "CRV-27*"] for label, offset in zip(labels, offsets): y0 = offset * x0 / (x0 + 1) - y0 += noise * np.random.rand(len(y0)) + y0 += noise * rng.random(len(y0)) plt.plot(x0, y0, label=label) plt.xlabel("distance [m]") @@ -31,13 +32,7 @@ def test_plot(filename, light: bool, noise, offsets): plt.gcf().patch.set_facecolor(gh_dark_bg) if filename: - # - plt.savefig( - filename, - transparent=True, - bbox_inches="tight", - facecolor=plt.gcf().get_facecolor(), - ) + plt.savefig(filename, transparent=True, bbox_inches="tight") else: plt.show() @@ -87,7 +82,8 @@ def test_all_nan(): if __name__ == "__main__": # test_plot(None, True, 0.1, (1.0, 1.5, 1.6)) - test_plot("ex1-light.svg", True, 0.1, (1.0, 1.5, 1.6)) + # test_plot("ex1-light.svg", True, 0.1, (1.0, 1.5, 1.6)) + # plt.close() + # test_plot("ex1-dark.svg", False, 0.1, (1.0, 1.5, 1.6)) + test_plot("ex1.svg", False, 0.1, (1.0, 1.5, 1.6)) plt.close() - test_plot("ex1-dark.svg", False, 0.1, (1.0, 1.5, 1.6)) - # test_nan()