{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Walkthrough\n", "\n", "The following is a detailed orientation on the `Template` class and `insert_image` functions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import copy, os\n", "import matplotlib.pyplot as plt\n", "import matplotlib.gridspec as gs\n", "import numpy as np\n", "import pandas as pd\n", "\n", "from mpl_template import Template, insert_image\n", "\n", "%matplotlib inline\n", "# make the inline output identical to the PDF.\n", "%config InlineBackend.print_figure_kwargs = {'bbox_inches':None}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SCRIPT = \"Template_Docs.ipynb\"\n", "PATH = os.path.join(os.path.join(*os.getcwd().split(os.sep)[-2:]), SCRIPT)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basics\n", "The Builtin template layout is revealed with the blank() method of the Template object. This method labels the axes names that make up the default figure axes elements. These elements will contain the title block contents." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "report_fig = Template(figsize=(8.5, 11), scriptname=SCRIPT)\n", "\n", "blank_fig = report_fig.blank()\n", "page = report_fig.add_page() # This is a helper function that is useful when \n", " # working with %matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Titleblock\n", "The titeleblock boxes are each matplotlib.axes.Axes() instances that have been created with gridspec.GridSpecFromSubplotSpec. The titleblock is stored as a list of dicts, and the widths and heights of the Axes objects are controlled using a list of dicts which contain a `'span'` key. For Example:\n", "\n", "```python\n", "user_specified = [\n", " {'span': [0, 5, 0, 10]},\n", " {'span': [5, 10, 0, 10]},\n", "]\n", "```\n", "\n", "would create two boxes stacked on top of each other, both of them .5 inches tall and 1 inch wide.\n", "\n", "### User Defined Titleblock\n", "Populating the figure axes objects above is simple with a loop, and a function to perform the logic for each axes element. there is a helper method called `populate_titleblock()` to assist with this task, or the user may specify their own function for special cases.\n", "\n", "```python\n", "titleblock_ = [\n", " {\n", " 'name': 'string', \n", " # Optional if box is meant to be empty.\n", " # Required if box will be populated.\n", " # The value referenced by the 'name' key is\n", " # used to assign the 'label' attribute of the\n", " # Axes instance.\n", "\n", " 'text': [{}, {}], \n", " # Optional if box will not contain text.\n", " # Dict or list of dicts containing Axes.text()\n", " # kwargs\n", "\n", " 'span': [],\n", " # Optional if default box dimensions should be used.\n", " # User input of this attribute overrides the\n", " # default span widths and heights and may require the\n", " # user to also adjust the\n", " # template.Template.gstitleblock() values to create\n", " # the intended box.\n", "\n", " 'image': {\n", " 'path': 'path\\to\\image.png', \n", " # Required, may be web address\n", " # or local file path\n", " 'scale': int or float, \n", " # Optional, defaults to 1.\n", " # use scale > 1 for zoom-in\n", " # and scale < 1 for zoom-out\n", " 'expand': bool, \n", " # Optional, defaults to False.\n", " # Applies only if scale > 1.\n", " # If True, image is expanded to completely\n", " # fill enclosing axes object.\n", " # If False, image is zoomed and cropped\n", " # to original aspect ratio.\n", " },\n", "\n", " },\n", " {}, # Box 2\n", " {}, # Box 3 etc...\n", "]\n", "```\n", "\n", "
This example will build a title block as follows using the default spans as shown in the `blank()` output above.\n", "\n", "\n", "- \"b\\_0\" will contain a title and subtitle with different formatting\n", "- \"b\\_1\" will contain a logo image\n", "- \"b\\_2\" will contain the project number\n", "- \"b\\_3\" will contain the date\n", "- \"b\\_4\" will contain the figure number" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TBK = [\n", " {\n", " 'name': 'title',\n", " 'text': [\n", " {\n", " 's': 'Example Figure',\n", " 'weight': 'bold',\n", " 'x': 0.5,\n", " 'y': 0.54,\n", " 'va': 'baseline',\n", " 'ha': 'center',\n", " },\n", " {\n", " 's': 'Weyland-Yutani',\n", " 'weight': 'light',\n", " 'x': 0.5,\n", " 'y': 0.46,\n", " 'va': 'top',\n", " 'ha': 'center',\n", " 'color': (.3, .3, .3),\n", " },\n", " ]\n", " },\n", " {'name': 'logo',\n", " 'image': {\n", " 'path': 'img//logo.png',\n", " 'scale': .75,\n", " },\n", " },\n", " {\n", " 'name': 'project',\n", " 'text': {\n", " 's': 'USCSS $Nostromo$',\n", " 'x': 0.5,\n", " 'y': 0.5,\n", " 'va': 'center',\n", " 'ha': 'center',\n", " },\n", " },\n", " {\n", " 'name': 'date',\n", " 'text': {\n", " 's': 'June 2122',\n", " 'x': 0.5,\n", " 'y': 0.5,\n", " 'va': 'center',\n", " 'ha': 'center',\n", " }\n", " },\n", " {\n", " 'name': 'fignum',\n", " 'text': {\n", " 's': 'Figure\\n\\n{:02d}',\n", " 'weight': 'bold',\n", " 'x': 0.5,\n", " 'y': 0.5,\n", " 'va': 'center',\n", " 'ha': 'center',\n", " },\n", " },\n", "]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "report_fig = Template(figsize=(8.5, 11), scriptname=SCRIPT, titleblock_content=TBK)\n", "report_fig.path_text = PATH \n", "# typically the path is populated via the required `scriptname` kwarg\n", "# but it can be overwritten with this `path_text` property\n", "\n", "report_fig.add_frame()\n", "report_fig.add_titleblock()\n", "report_fig.add_path_text()\n", "report_fig.populate_titleblock()\n", "page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below are the Axes objects created by the `Template` class and `TBK` variable. Note that `frame` and `img_b_1` were not labeled by the user, but by the Template." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for ax in report_fig.fig.get_axes():\n", " print(ax.get_label())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Under The Hood\n", "\n", "

The template object works by breaking down the figure into a grid of 0.1\" squares and using matplotlib's GridSpec to define the axes objects that make up the title block boxes and border. This means that the code is extremely flexible for changing things like margins, titleblock width, and inserting multiple axes into compliated layouts. However, this also means that a user must call GridSpec() or GridSpecFromSubplotSpec() in order to populate the figure.\n", "\n", "

Below is a 1\" by 1\" example to illustrate the underlying grid of the template. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "w, h = 1, 1\n", "report_fig = Template(figsize=(w, h), scriptname=SCRIPT, draft=False)\n", "\n", "for i in range(w * 10 * h * 10):\n", " ax = report_fig.fig.add_subplot(report_fig.gsfig[i], facecolor='none',\n", " xticks=[], yticks=[],\n", " )\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inserting a Plot\n", "\n", "A simple plot example is shown below that uses several methods to get locations of useful figure features." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tbk = copy.deepcopy(TBK)\n", "tbk[0]['text'][0]['s'] = \"Plot Example\"\n", "tbk[4]['text']['s'] = tbk[4]['text']['s'].format(2)\n", "\n", "report_fig = Template(figsize=(8.5, 11), scriptname=SCRIPT, titleblock_content=tbk)\n", "report_fig.path_text=PATH\n", "fig = report_fig.setup_figure() # `setup_figure` performs add_frame(), add_titleblock(), \n", " # add_path_text() and populate_titleblock(). It is the\n", " # recommended way to create figures with the Template class\n", "\n", "# create a sub-gridspec that will be used for the main image\n", "left, right, top, bottom = report_fig.margins\n", "main = report_fig.gsfig[4 + top: -(report_fig.t_h + bottom + 8), 8 + left: -(right + 8)]\n", "\n", "ax = fig.add_subplot(main)\n", "plot = ax.plot([np.random.uniform(-1, 1)*5 for i in range(25)])\n", "_ = ax.set_ylim(-8, 8)\n", "page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inserting an Image\n", "\n", "The template has an additional built in method for inserting images that are centered in the axes object and properly antialiased for the final resolution of the figure. The default value for saving/exporting figures using the Template module is 300 dpi. The insert_image() method also ensures that there is no stretching or other modification to the image that is being inserted. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tbk = copy.deepcopy(TBK)\n", "tbk[0]['text'][0]['s'] = \"Image Example\"\n", "tbk[4]['text']['s'] = tbk[4]['text']['s'].format(3)\n", "\n", "report_fig = Template(figsize=(8.5, 11), scriptname=SCRIPT,\n", " titleblock_content=tbk)\n", "\n", "# create a sub-gridspec that will be used for the main image\n", "left, right, top, bottom = report_fig.margins\n", "main = report_fig.gsfig[4 + top: -(report_fig.t_h + bottom + 8),\n", " 3 + left: -(right + 3)]\n", "\n", "report_fig.path_text = PATH\n", "fig = report_fig.setup_figure()\n", "\n", "# \"frameon = True\" shows that the axes object that is being added\n", "# is exactly the size and shape specified by the gridspec, but the image\n", "# fills the largest dimension first.\n", "\n", "ax = fig.add_subplot(main, xticks=[], yticks=[], frameon=True)\n", "\n", "im_ax = insert_image(ax, \"img//95_confidence.png\", scale=1, dpi=fig.get_dpi())\n", "im_ax.axis('on')\n", "page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plots with Insets" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tbk = copy.deepcopy(TBK)\n", "tbk[0]['text'][0]['s'] = \"Inset Example\"\n", "tbk[4]['text']['s'] = tbk[4]['text']['s'].format(4)\n", "\n", "report_fig = Template(figsize=(17, 11),\n", " scriptname=SCRIPT,\n", " titleblock_content=tbk,\n", " )\n", "report_fig.path_text = PATH\n", "\n", "main = report_fig.gsfig[4 + top: -(report_fig.t_h + bottom),\n", " 8 + left: -(right + 50)]\n", "gs_timeseries = gs.GridSpecFromSubplotSpec(4, 3, main, hspace=0.3, wspace=0.3)\n", "\n", "fig = report_fig.setup_figure()\n", "\n", "for n in range(12):\n", " ax2 = fig.add_subplot(gs_timeseries[n])\n", " x = np.arange(25)\n", " y = np.random.uniform(-2, 2) * x + np.random.uniform(-10, 10, x.shape)\n", " ax2.set_ylim((-50, 50))\n", " ax2.plot(x, y)\n", "\n", "inset = report_fig.gsfig[3 + top: -(report_fig.t_h + bottom + 3),\n", " -(report_fig.t_w + right): -(right + 3)]\n", "\n", "inset_ax = fig.add_subplot(\n", " inset, frameon=False, xticks=[], yticks=[], facecolor='none')\n", "\n", "img = insert_image(inset_ax, \"img//Template_Inset.png\", scale=1)\n", "img.axis('on')\n", "page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Advanced Features\n", "\n", "## Compatability with Seaborn" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import seaborn as sns\n", "\n", "for i in range(2):\n", " with sns.axes_style('white'):\n", " tbk = copy.deepcopy(TBK)\n", " tbk[0]['text'][0]['s'] = \"Seaborn Loop Example\"\n", " tbk[4]['text']['s'] = tbk[4]['text']['s'].format(i + 5)\n", "\n", " report_fig = Template(figsize=(17, 11),\n", " scriptname=SCRIPT,\n", " titleblock_content=tbk,\n", " )\n", " report_fig.path_text = PATH\n", "\n", " left, right, top, bottom = report_fig.margins\n", " main = report_fig.gsfig[4 + top: -(report_fig.t_h + bottom),\n", " 8 + left: -(right + 50)]\n", " gs_timeseries = gs.GridSpecFromSubplotSpec(4, 3,\n", " main,\n", " hspace=0.3,\n", " wspace=0.3,\n", " )\n", " fig = report_fig.setup_figure()\n", "\n", " for n in range(12):\n", " ax2 = fig.add_subplot(gs_timeseries[n])\n", " x = np.arange(25)\n", " y = np.random.uniform(-2, 2) * x + \\\n", " np.random.uniform(-10, 10, x.shape)\n", " ax2.set_ylim((-50, 50))\n", " ax2.plot(x, y)\n", "\n", " inset = report_fig.gsfig[3 + top:-(report_fig.t_h + bottom + 3),\n", " -(report_fig.t_w + right):-(right + 3)]\n", "\n", " inset_ax = fig.add_subplot(inset,\n", " frameon=False,\n", " xticks=[],\n", " yticks=[],\n", " facecolor='none',\n", " )\n", "\n", " img = insert_image(inset_ax, \"img//Template_Inset.png\", scale=1)\n", " page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### FacetGrid\n", "Seaborn's facetgrid cannot be made directly compatable with this module (to my knowledge) because of it's implicit call to [plt.subplots()](https://github.com/mwaskom/seaborn/blob/master/seaborn/axisgrid.py#L310) or [plt.figure()](https://github.com/mwaskom/seaborn/blob/master/seaborn/axisgrid.py#L319) to generate the figure necessary to produce multiple axes in a subplot axes grid. For now, creating a temporary png file of the facet grid and loading it as an image is one way around this limitation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with sns.axes_style('white'):\n", " tbk = copy.deepcopy(TBK)\n", " tbk[0]['text'][0]['s'] = \"Seaborn FacetGrid Example\"\n", " tbk[4]['text']['s'] = tbk[4]['text']['s'].format(7)\n", "\n", " report_fig = Template(figsize=(8.5, 11),\n", " scriptname=SCRIPT,\n", " titleblock_content=tbk,\n", " )\n", " report_fig.path_text = PATH\n", "\n", " attend = sns.load_dataset(\"attention\")\n", " g = sns.FacetGrid(attend, col=\"subject\", col_wrap=5, height=1.5, ylim=(0, 10))\n", " g = g.map(plt.plot, \"solutions\", \"score\", marker=\".\", markersize=10)\n", " g.savefig('img//Facet_Grid.png', dpi=300)\n", " plt.close()\n", "\n", " left, right, top, bottom = report_fig.margins\n", " main = report_fig.gsfig[top: -(report_fig.t_h + bottom + 1),\n", " left: -(right)]\n", "\n", " fig = report_fig.setup_figure()\n", "\n", " ax = fig.add_subplot(main, xticks=[], yticks=[], frameon=False)\n", "\n", " g_ax = insert_image(ax, \"img//Facet_Grid.png\", scale=.9)\n", " g_ax.axis('off')\n", "\n", " page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Custom Titleblock Specifications\n", "\n", "The Template Module is highly customizable using the `titleblock_content`, `titleblock_rows` and `titleblock_cols` kwargs when constructing the object. The rows and columns should be tuples specifying the high level grid structure of the titleblock, and the titleblock content should contain a list of dictionaries (one for each box) with a `'span'` key.\n", "\n", "Spans are given as gridspec lists of indices of the grids to span. The pattern for gridspec is [r0:r,c0:c] where r0 is the top of the vertical span of _rows_, and c0 is the left element of the horizontal span of the _columns_. The span list should provide these indices in order, e.g. [r0,r,c0,c].\n", "\n", "Defaults are shown below:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "report_fig = Template(figsize=(8.5, 11), scriptname=SCRIPT)\n", "\n", "print('Default titleblock_cols = (16, 16, 8)')\n", "print('Default titleblock_rows = (8, 5, 3)')\n", "report_fig.default_spans" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Defaults can be adjusted with the `titleblock_rows` and `titleblock_cols`. It's even possible to align the titleblock anywhere on the figure using gridspec.\n", "\n", "The titleblock can be moved using the `gstitleblock` attribute of the `template.Template()` class." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with sns.axes_style('white'):\n", " tbk = copy.deepcopy(TBK)\n", " tbk[0]['text'][0]['s'] = \" Wide Titleblock on Left\"\n", " tbk[4]['text']['s'] = tbk[4]['text']['s'].format(8)\n", "\n", " report_fig = Template(figsize=(8.5, 11), scriptname=SCRIPT,\n", " titleblock_rows=(8, 5, 5),\n", " titleblock_cols=(30, 20, 10),\n", " margins=(1, 2, 3, 4),\n", " titleblock_content=tbk,\n", " )\n", " report_fig.path_text = PATH\n", " report_fig.gstitleblock = report_fig.gsfig[-(report_fig.bottom + report_fig.t_h) or None: -report_fig.bottom or None,\n", " (report_fig.left) or None: (report_fig.left + report_fig.t_w) or None\n", " ]\n", "\n", " fig = report_fig.setup_figure()\n", " page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's also possible to blend the definitions of spans and use an existing titleblock specification to populate the adjusted boxes. Notice that the specifications from the `TBK` occupy different cells than before because their spans have been defined differently by `new_spans`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_spans = [\n", " {\"span\": [0, 8, 0, 32]},\n", " {\"span\": [13, 16, 16, 30]},\n", " {\"span\": [13, 16, 0, 16]},\n", " {\"span\": [8, 13, 0, 32]},\n", " {\"span\": [0, 13, 32, 40]},\n", " {\"span\": [13, 16, 30, 40]}\n", "]\n", "\n", "update = copy.deepcopy(TBK)\n", "for i, dct in enumerate(update):\n", " dct.update(new_spans[i])\n", "\n", "with sns.axes_style('white'):\n", " tbk = copy.deepcopy(update)\n", " tbk[0]['text'][0]['s'] = \"Custom Titleblock Example\"\n", " tbk[4]['text']['s'] = tbk[4]['text']['s'].format(8)\n", "\n", " report_fig = Template(figsize=(8.5, 11),\n", " scriptname=SCRIPT,\n", " titleblock_content=tbk\n", " )\n", " report_fig.path_text = PATH\n", " fig = report_fig.setup_figure()\n", " page = report_fig.add_page()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fully Custom Titleblock\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fully_custom = [\n", " {\n", " 'span': [0, 32, 0, 16],\n", " 'name': 'Title',\n", " 'text': [\n", " {\n", " \"ha\": \"center\",\n", " \"s\": \"Test Custom Block\",\n", " \"va\": \"baseline\",\n", " \"weight\": \"bold\",\n", " \"x\": 0.5,\n", " \"y\": 0.52,\n", " },\n", " {\n", " \"color\": (.3, 0.3, 0.3),\n", " \"ha\": \"center\",\n", " \"s\": \"Blank Example\",\n", " \"va\": \"top\",\n", " \"weight\": \"light\",\n", " \"x\": 0.5,\n", " \"y\": 0.48,\n", " },\n", " ],\n", "\n", " },\n", " {\n", " 'name': 'logo',\n", " 'image': {\n", " 'path': 'img//logo.png',\n", " 'scale': .6,\n", " },\n", " 'span': [0, 32, 16, 32],\n", " },\n", " {\n", " 'name': 'fignum',\n", " 'text': {'s': '5',\n", " 'x': .5,\n", " 'y': .5,\n", " 'ha': 'center',\n", " 'va': 'center',\n", " },\n", "\n", " 'span': [0, 32, 32, 48],\n", " },\n", "]\n", "\n", "with sns.axes_style('white'):\n", " report_fig = Template(figsize=(8.5, 11),\n", " scriptname=SCRIPT,\n", " titleblock_content=fully_custom,\n", " )\n", " report_fig.path_text = PATH\n", " fig = report_fig.setup_figure()" ] } ], "metadata": { "anaconda-cloud": {}, "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": 2 }