{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "# Gallery\n", "\n", "Example `outset` plots, with source code.\n", "\n", "![gallery collage](assets/outset-gallery-collage.png)\n", "\n", "Contributions welcome!\n", "Open [an issue](https://github.com/mmore500/outset/issues) or [a pull request](https://github.com/mmore500/outset/pulls).\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Table of Contents\n", "\n", "- [cartopy](#cartopy)\n", "- [heatmap](#heatmap)\n", "- [imshow](#imshow)\n", "- [kdeplot](#kdeplot)\n", "- [lineplot](#lineplot)\n", "- [patches](#patches)\n", "- [plot](#plot)\n", "- [regplot](#regplot)\n", "- [scatterplot](#scatterplot)\n", "- [streamplot](#streamplot)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import copy\n", "import itertools as it\n", "import typing\n", "\n", "\n", "import cartopy.crs as ccrs\n", "from datetime import datetime, timedelta\n", "from matplotlib import cbook as mpl_cbook\n", "from matplotlib import cm as mpl_cm\n", "from matplotlib import colors as mpl_colors\n", "from matplotlib import patches as mpl_patches\n", "from matplotlib import text as mpl_text\n", "import matplotlib as mpl\n", "from matplotlib import pyplot as plt\n", "import numpy as np\n", "import opytional as opyt\n", "import pandas as pd\n", "import seaborn as sns\n", "\n", "import outset as otst\n", "from outset import mark as otst_mark\n", "from outset import patched as otst_patched\n", "from outset import tweak as otst_tweak\n", "from outset import util as otst_util\n", "from outset import stub as otst_stub" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## cartopy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# WMTS service URL and layer\n", "wmts_url = \"https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi\"\n", "wmts_layer = \"VIIRS_CityLights_2012\"\n", "\n", "# initialize outset grid with named frames and marqueeplot configuration\n", "outset_grid = otst.OutsetGrid(\n", " otst_util.NamedFrames(\n", " pasta=(12, 40, 16, 42), # (x0, y0, x1, y1)\n", " croissant=(1, 48, 3, 50),\n", " ),\n", " col=\"dish\",\n", " marqueeplot_kws={\"leader_face_kws\": {\"alpha\": 0}},\n", " palette=sns.color_palette(\"husl\", 2),\n", " subplot_kws={\"projection\": ccrs.PlateCarree()}, # for underlying FacetGrid\n", ")\n", "\n", "\n", "# function to plot WMTS layer on axis\n", "def draw_map(ax):\n", " ax.add_wmts(wmts_url, wmts_layer)\n", " ax.set_extent([-15, 25, 35, 60], crs=ccrs.PlateCarree())\n", "\n", "\n", "# apply plot function to all subplots\n", "outset_grid.broadcast(draw_map)\n", "\n", "# apply marquee elements\n", "outset_grid.marqueeplot()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## heatmap" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# generate random data and assign to x, y\n", "data = np.random.randn(10000, 2)\n", "x, y = data.T\n", "\n", "# create a 2D histogram from data\n", "heatmap, _xedges, _yedges = np.histogram2d(x, y, bins=(100, 100))\n", "\n", "# initialize outset grid with outset frame coords configuration for marquee plot\n", "outset_grid = otst.OutsetGrid(\n", " [(50.5, 53.5, 56.5, 60.5)], # (x0, y0, x1, y1) region to outset\n", " color=\"fuchsia\",\n", " marqueeplot_outset_kws={\"frame_edge_kws\": {\"lw\": 4, \"ec\": \"pink\"}},\n", " marqueeplot_source_kws={\"leader_stretch\": 1},\n", ")\n", "\n", "# apply heatmap across all subplots\n", "outset_grid.broadcast(\n", " sns.heatmap,\n", " heatmap.T,\n", " cmap=\"jet\",\n", " cbar=False, # add colorbar manually later to prevent layout issues\n", " square=True,\n", " vmin=0,\n", " vmax=np.max(heatmap.flat),\n", " zorder=-2,\n", ")\n", "\n", "for ax in outset_grid.axes.flat:\n", " ax.invert_yaxis() # outset implementation requires ascending y-axis\n", "\n", "# display marquee plot\n", "outset_grid.marqueeplot(preserve_aspect=True) # keep square aspect\n", "\n", "# create colorbar for the last subplot\n", "cbar = outset_grid.figure.colorbar(\n", " mpl_cm.ScalarMappable(\n", " norm=mpl_colors.Normalize(vmin=0, vmax=np.max(heatmap.flat)),\n", " cmap=\"jet\",\n", " ),\n", " ax=outset_grid.axes.flat[-1],\n", " location=\"right\",\n", " fraction=1e-9, # steal negligible space from last subplot\n", ")\n", "cbar.ax.set_position([1, 0.15, 0.2, 0.73]) # expand colorbar and move over\n", "\n", "# refresh axes ticks\n", "for ax in outset_grid.axes.flat:\n", " ax.xaxis.set_major_locator(plt.AutoLocator())\n", " ax.xaxis.set_major_formatter(plt.ScalarFormatter())\n", " ax.yaxis.set_major_locator(plt.AutoLocator())\n", " ax.yaxis.set_major_formatter(plt.ScalarFormatter())\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# generate random Gumbel distributed data and assign to x, y\n", "data = np.random.gumbel(size=(10000, 2))\n", "x, y = data.T\n", "\n", "# create a 2D histogram from data\n", "heatmap, _xedges, _yedges = np.histogram2d(x, y, bins=(100, 100))\n", "\n", "# initialize outset grid with specific configuration\n", "outset_grid = otst.OutsetGrid(\n", " [(9.5, 16.5, 25.5, 23.5)], # # (x0, y0, x1, y1) region to outset\n", " color=\"red\",\n", " marqueeplot_kws={\n", " \"color\": \"fuchsia\",\n", " \"mark_glyph_kws\": {\"color\": \"fuchsia\"},\n", " \"frame_edge_kws\": {\"lw\": 3},\n", " },\n", " marqueeplot_source_kws={\"leader_stretch\": 0.6},\n", ")\n", "\n", "# apply heatmap across all subplots\n", "outset_grid.broadcast(\n", " sns.heatmap,\n", " heatmap,\n", " cmap=\"jet\",\n", " cbar=False, # must add colorbar manually to prevent layout issues\n", " square=True,\n", " zorder=-1,\n", ")\n", "\n", "for ax in outset_grid.axes.flat:\n", " ax.invert_yaxis() # outset implementation requires ascending y-axis\n", "\n", "# create colorbar for the last subplot\n", "# must create cbar prior to insetting then position last\n", "cbar = outset_grid.figure.colorbar(\n", " mpl_cm.ScalarMappable(\n", " norm=mpl_colors.Normalize(vmin=0, vmax=np.max(heatmap.flat)),\n", " cmap=\"jet\",\n", " ),\n", " ax=outset_grid.outset_axes.flat[-1],\n", " location=\"right\",\n", " fraction=1e-9, # steal negligible space from last subplot\n", ")\n", "\n", "# insert insets and adjust outset axes\n", "otst.inset_outsets(\n", " outset_grid,\n", " insets=\"NE\",\n", " strip_ticks=False, # override: keep ticks ofter insetting\n", ")\n", "\n", "# dispatch marqueeplot\n", "outset_grid.marqueeplot(preserve_aspect=True) # keep square aspect\n", "\n", "# style inset axes\n", "for ax in outset_grid.outset_axes:\n", " ax.tick_params(color=\"white\", labelcolor=\"white\")\n", " for spine in ax.spines.values():\n", " spine.set_linewidth(2)\n", " spine.set_edgecolor(\"white\")\n", " sns.despine(ax=ax) # remove upper/right spines\n", "\n", "# inflate and position colorbar\n", "cbar.ax.set_position([1, 0.15, 0.2, 0.73]) # expand colorbar and move over\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## imshow" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load sample image\n", "with mpl_cbook.get_sample_data(\"grace_hopper.jpg\") as image_file:\n", " image = plt.imread(image_file)\n", "\n", "h, w = image.shape[0], image.shape[1]\n", "\n", "# initialize outset grid with named frames\n", "outset_grid = otst.OutsetGrid(\n", " data=otst_util.NamedFrames(\n", " { # (x0, y0, x1, y1) region to outset\n", " \"hat\": (0.42 * w, 0.78 * h, 0.62 * w, 0.98 * h),\n", " \"badge\": (0.10 * w, 0.14 * h, 0.40 * w, 0.21 * h),\n", " }\n", " ),\n", " aspect=w / h, # set subgrid aspect ratios to match image\n", " col=\"swag\", # names columns\n", " hue=True,\n", ")\n", "\n", "# take actions that might affect layout early\n", "outset_grid.source_axes.set_title(\"The Hopster\", loc=\"left\")\n", "outset_grid.broadcast(lambda: plt.axis(\"off\"))\n", "plt.tight_layout() # refresh layout\n", "\n", "# add content to all subplots\n", "outset_grid.broadcast( # display image\n", " plt.imshow,\n", " image,\n", " extent=(0, w, 0, h), # ensures ascending yaxis\n", " origin=\"upper\",\n", " zorder=-1,\n", ")\n", "\n", "# display marquee plot, keeping even aspect\n", "outset_grid.marqueeplot(preserve_aspect=True)\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load sample image\n", "with mpl_cbook.get_sample_data(\"grace_hopper.jpg\") as image_file:\n", " image = plt.imread(image_file)\n", "\n", "h, w = image.shape[0], image.shape[1]\n", "\n", "# initialize outset grid with specific marquee styles and positions\n", "outset_grid = otst.OutsetGrid(\n", " data=otst_util.NamedFrames( # allows for nice legend labels\n", " { # (x0, y0, x1, y1) region to outset\n", " \"hat\": (0.42 * w, 0.78 * h, 0.62 * w, 0.98 * h),\n", " \"badge\": (0.10 * w, 0.14 * h, 0.40 * w, 0.21 * h),\n", " }\n", " ),\n", " aspect=2,\n", " col_wrap=1, # stack subplots vertically\n", " col=\"swag\",\n", " hue=\"swag\",\n", " marqueeplot_kws={\n", " \"mark_glyph\": otst_mark.MarkMagnifyingGlass(),\n", " \"mark_glyph_kws\": {\"markersize\": 25},\n", " \"frame_outer_pad\": 0.0, # make frame flush with subfigures\n", " },\n", " include_sourceplot=False, # exclude original image\n", ")\n", "\n", "# add and position legend\n", "outset_grid.add_legend()\n", "\n", "# turn off axis accoutrements\n", "outset_grid.broadcast(lambda: plt.axis(\"off\"))\n", "outset_grid.set_titles(\"\")\n", "\n", "# add content\n", "outset_grid.broadcast( # display image on all axes\n", " plt.imshow,\n", " image,\n", " extent=(0, w, 0, h), # ensures ascending yaxis\n", " origin=\"upper\",\n", " zorder=-1,\n", ")\n", "\n", "# display marquee plot, keeping even aspect\n", "outset_grid.marqueeplot(preserve_aspect=True)\n", "\n", "# move legend to the upper right of the figure\n", "sns.move_legend(\n", " outset_grid.figure, \"upper left\", bbox_to_anchor=(0.95, 0.95), frameon=False\n", ")\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load Grace Hopper image\n", "with mpl_cbook.get_sample_data(\"grace_hopper.jpg\") as image_file:\n", " image = plt.imread(image_file)\n", "\n", "h, w = image.shape[0], image.shape[1]\n", "\n", "# initialize outset grid with marqueeplot and source configurations\n", "outset_grid = otst.OutsetGrid(\n", " data=[\n", " # (x0, y0, x1, y1) regions to outset\n", " (0.42 * w, 0.78 * h, 0.62 * w, 0.98 * h),\n", " (0.10 * w, 0.14 * h, 0.40 * w, 0.21 * h),\n", " ], # frame coordinates\n", " aspect=w / h,\n", " height=5, # make subplots bigger via sns.FacetGrid\n", " marqueeplot_kws={\n", " \"frame_inner_pad\": 0.0,\n", " \"frame_outer_pad\": 0.05,\n", " \"frame_outer_pad_unit\": \"inches\",\n", " \"frame_edge_kws\": {\"linewidth\": 4, \"linestyle\": \"-\"},\n", " \"leader_edge_kws\": {\"linewidth\": 2, \"linestyle\": \"-\"},\n", " \"leader_face_kws\": {\"alpha\": 1.0},\n", " \"mark_glyph_kws\": {\"markersize\": 20},\n", " },\n", " marqueeplot_source_kws={\n", " \"leader_stretch\": 0.4,\n", " \"leader_stretch_unit\": \"inches\",\n", " \"mark_glyph_kws\": {\"markersize\": 30},\n", " },\n", " palette=sns.color_palette()[8:], # custom palette\n", ")\n", "\n", "# turn off source axis ticks, spinees, etc.\n", "outset_grid.source_axes.set_axis_off()\n", "\n", "# display image in all subplots\n", "outset_grid.broadcast(\n", " plt.imshow,\n", " image,\n", " aspect=\"equal\",\n", " extent=(0, w, 0, h),\n", " origin=\"upper\",\n", " zorder=-1,\n", ")\n", "\n", "# inset outsets with manually chosen positions and sizes\n", "otst.inset_outsets(\n", " outset_grid,\n", " insets=[ # manually choose inset positions relative to source axes\n", " (0.02, 0.53, 0.50, 0.44),\n", " (0.05, 0.28, 0.9, 0.23),\n", " ],\n", ")\n", "\n", "# display marquee plot, keeping even aspect\n", "outset_grid.marqueeplot(preserve_aspect=True)\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## kdeplot\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from https://seaborn.pydata.org/examples/smooth_bivariate_kde.html\n", "\n", "# load penguins dataset\n", "penguins_data = sns.load_dataset(\"penguins\")\n", "\n", "# create a single subplot\n", "fig, ax = plt.subplots(1)\n", "\n", "# plot a pretty kde\n", "sns.kdeplot(\n", " data=penguins_data,\n", " x=\"body_mass_g\",\n", " y=\"bill_depth_mm\",\n", " ax=ax,\n", " fill=True,\n", " clip=((2200, 6800), (10, 25)),\n", " thresh=0,\n", " levels=100,\n", " cmap=\"rocket\", # color map\n", ")\n", "\n", "# manually draw marquee around an area of interest\n", "otst.draw_marquee(\n", " (5200, 6000), # x coordinates\n", " (14, 16), # y coordinates\n", " ax,\n", " color=\"teal\",\n", " mark_glyph=otst_mark.MarkArrow(rotate_angle=-45), # arrow marker\n", " leader_stretch_unit=\"inches\",\n", " leader_stretch=0.2,\n", " zorder=10, # draw on top\n", ")\n", "\n", "# give the arrow something to point at\n", "ax.annotate(\"zoink?\", (6300, 17), color=\"teal\", zorder=5)\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load Iris dataset and initialize outset grid with configurations\n", "outset_grid = otst.OutsetGrid(\n", " data=sns.load_dataset(\"iris\").dropna(),\n", " x=\"petal_width\",\n", " y=\"petal_length\",\n", " col=\"species\",\n", " col_wrap=2,\n", " color=sns.color_palette()[1], # force all marquees to have same color\n", " marqueeplot_kws={\n", " \"mark_glyph\": otst.mark.MarkAlphabeticalBadges,\n", " },\n", " marqueeplot_source_kws={\"leader_tweak\": otst_tweak.TweakReflect()},\n", " marqueeplot_outset_kws={\n", " \"leader_tweak\": otst_tweak.TweakReflect(vertical=True)\n", " },\n", ")\n", "\n", "# map kdeplot for petal width vs length on the main axes\n", "outset_grid.map_dataframe(\n", " sns.kdeplot, x=\"petal_width\", y=\"petal_length\", legend=False, zorder=0\n", ")\n", "\n", "# map scatterplot for petal width vs length on the outset axes\n", "outset_grid.map_dataframe_outset(\n", " sns.scatterplot,\n", " x=\"petal_width\",\n", " y=\"petal_length\",\n", " legend=False,\n", " zorder=0,\n", ")\n", "\n", "# dispatch marquee render\n", "outset_grid.marqueeplot()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load Iris dataset\n", "iris_data = sns.load_dataset(\"iris\").dropna()\n", "\n", "# initialize outset grid with specific configurations\n", "outset_grid = otst.OutsetGrid(\n", " aspect=1.5,\n", " data=iris_data,\n", " x=\"petal_width\",\n", " y=\"petal_length\",\n", " col=\"species\",\n", " # hack to add two extra facets with no data\n", " col_order=sorted(iris_data[\"species\"].unique()) + [\"dummy1\", \"dummy2\"],\n", " hue=\"species\",\n", " marqueeplot_kws={\"mark_glyph\": otst.mark.MarkAlphabeticalBadges},\n", " marqueeplot_source_kws={\"leader_tweak\": otst_tweak.TweakReflect()},\n", " marqueeplot_outset_kws={\n", " \"leader_tweak\": otst_tweak.TweakReflect(vertical=True)\n", " },\n", ")\n", "\n", "# create two-layered KDE plot on source plot\n", "outset_grid.map_dataframe_source(\n", " sns.kdeplot,\n", " x=\"petal_width\",\n", " y=\"petal_length\",\n", " alpha=0.4,\n", " fill=True,\n", " zorder=0,\n", ")\n", "outset_grid.map_dataframe_source(\n", " sns.kdeplot,\n", " x=\"petal_width\",\n", " y=\"petal_length\",\n", " alpha=0.4,\n", " zorder=0,\n", " levels=3,\n", ")\n", "\n", "# map regplot on outset subplots\n", "outset_grid.map_dataframe_outset(\n", " otst.patched.regplot,\n", " x=\"petal_width\",\n", " y=\"petal_length\",\n", " line_kws={\"lw\": 1},\n", " scatter_kws={\"s\": 1},\n", ")\n", "\n", "# move and style legend\n", "sns.move_legend(\n", " outset_grid.source_axes, loc=\"center left\", bbox_to_anchor=(1, 0.5)\n", ")\n", "outset_grid.source_axes.get_legend().get_frame().set_linewidth(0.0)\n", "\n", "# calculate insets positions and add insets to outset grid\n", "# combine three upper left insets and two lower right insets\n", "insets = otst_util.layout_corner_insets(\n", " 3, \"NW\"\n", ") + otst_util.layout_corner_insets(2, \"SE\")\n", "otst.inset_outsets(outset_grid, insets=insets)\n", "\n", "# draw on extra insets\n", "# configure and plot KDE for petal width on specific outset axis\n", "outset_grid.outset_axes[3].set_title(\"petal_width\", fontsize=6)\n", "outset_grid.outset_axes[3].set_aspect(\"auto\")\n", "outset_grid.outset_axes[3].set_autoscale_on(True)\n", "sns.kdeplot(\n", " iris_data,\n", " x=\"petal_width\",\n", " hue=\"species\",\n", " fill=True,\n", " ax=outset_grid.outset_axes[3],\n", " legend=False,\n", " clip_on=False,\n", " bw_adjust=2,\n", ")\n", "\n", "# configure and plot KDE for petal length on specific outset axis\n", "outset_grid.outset_axes[4].set_title(\"petal_length\", fontsize=6)\n", "outset_grid.outset_axes[4].set_aspect(\"auto\")\n", "outset_grid.outset_axes[4].set_autoscale_on(True)\n", "sns.kdeplot(\n", " iris_data,\n", " x=\"petal_length\",\n", " hue=\"species\",\n", " fill=True,\n", " ax=outset_grid.outset_axes[4],\n", " legend=False,\n", " clip_on=False,\n", " bw_adjust=2,\n", ")\n", "\n", "# note: didn't dispatch marquee annotations!\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## lineplot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from https://seaborn.pydata.org/examples/wide_data_lineplot.html\n", "\n", "# generate random data and calculate rolling mean\n", "random_state = np.random.RandomState(365)\n", "values = random_state.randn(365, 4).cumsum(axis=0)\n", "dates = np.array(range(365))\n", "rolling_data = pd.DataFrame(values, dates, columns=[\"A\", \"B\", \"C\", \"D\"])\n", "rolling_data = rolling_data.rolling(7).mean()\n", "\n", "\n", "# initialize axes grid manager\n", "outset_grid = otst.OutsetGrid(\n", " aspect=2, # make subplots wide\n", " data=[(210, 6, 250, 12)], # (x0, y0, x1, y1) region to outset\n", " col_wrap=1,\n", " x=\"days\",\n", " y=\"profit\",\n", ")\n", "\n", "# broadcast lineplot to all subplots\n", "outset_grid.broadcast(\n", " sns.lineplot,\n", " data=rolling_data,\n", " palette=\"tab10\",\n", " linewidth=2.5,\n", " zorder=-1,\n", ")\n", "\n", "# dispatch marquee render\n", "outset_grid.marqueeplot()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ----- prepare data -----\n", "nwls = \"NW Lysimeter\\n(35.18817624°N, -102.09791°W)\"\n", "swls = \"SW Lysimeter\\n(35.18613985°N, -102.0979187°W)\"\n", "df = pd.read_csv(\"https://osf.io/6mx3e/download\")\n", "df[nwls] = df[\"NW precip in mm\"]\n", "df[swls] = df[\"SW precip in mm\"]\n", "df[\"Max precip\"] = np.maximum(df[\"SW precip in mm\"], df[\"NW precip in mm\"])\n", "\n", "march_df = df[ # filter down to just data from March 2019\n", " (df[\"Decimal DOY\"] >= 59) & (df[\"Decimal DOY\"] <= 90)\n", "].reset_index()\n", "\n", "# ----- setup axes grid -----\n", "grid = otst.OutsetGrid( # initialize axes grid manager\n", " data=[\n", " (71.6, 0, 72.2, 2), # (x0, y0, x1, y1) region to outset\n", " (59, 0, 90, 0.2),\n", " (81.3, 0, 82.2, 16),\n", " ],\n", " x=\"Time\",\n", " y=\"Max precip\",\n", " marqueeplot_kws={\"frame_outer_pad\": 0, \"mark_glyph_kws\": {\"zorder\": 11}},\n", " marqueeplot_source_kws={\"zorder\": 10, \"frame_face_kws\": {\"zorder\": 10}},\n", " aspect=2,\n", " col_wrap=2,\n", ")\n", "\n", "# ----- plot content -----\n", "grid.broadcast( # apply white underlay for lineplot to all axes\n", " plt.stackplot,\n", " march_df[\"Decimal DOY\"].to_numpy(),\n", " march_df[\"Max precip\"].to_numpy(),\n", " colors=[\"white\"],\n", " lw=20,\n", " edgecolor=\"white\",\n", " zorder=10,\n", ")\n", "# draw semi-transparent filled lineplot on all axes for each lysimeter\n", "for y, color in zip([nwls, swls], [\"fuchsia\", \"aquamarine\"]):\n", " grid.broadcast(\n", " plt.stackplot,\n", " march_df[\"Decimal DOY\"].to_numpy(),\n", " march_df[y].to_numpy(),\n", " colors=[color],\n", " labels=[y],\n", " lw=2,\n", " edgecolor=color,\n", " alpha=0.4,\n", " zorder=10,\n", " )\n", "\n", "# dispatch marquee render\n", "grid.marqueeplot(equalize_aspect=False) # allow axes aspect ratios to vary\n", "\n", "\n", "# ----- replace numeric axes tick labels with datetimes -----\n", "def to_dt(day_of_year: float, year: int = 2019) -> datetime:\n", " \"\"\"Convert decimal day of the year to a datetime object.\"\"\"\n", " return datetime(year=year, month=1, day=1) + timedelta(days=day_of_year)\n", "\n", "\n", "def format_tick_value(\n", " prev_value: typing.Optional[mpl_text.Text],\n", " value: mpl_text.Text,\n", ") -> str:\n", " decimal_doy = float(value.get_text())\n", " prev_decimal_doy = opyt.apply_if(prev_value, lambda x: float(x.get_text()))\n", " if int(decimal_doy) == opyt.apply_if(prev_decimal_doy, int):\n", " return to_dt(decimal_doy).strftime(\"%H:%M\")\n", " elif decimal_doy % 1.0 < 1 / 24:\n", " return to_dt(decimal_doy).strftime(\"%b %-d\")\n", " else:\n", " return to_dt(decimal_doy).strftime(\"%H:%M\\n%b %-d\")\n", "\n", "\n", "for ax in grid.axes.flat:\n", " ax.set_xticks(\n", " # keep only x ticks that are within axes limits\n", " [val for val in ax.get_xticks() if np.clip(val, *ax.get_xlim()) == val]\n", " )\n", " ax.set_xticklabels(\n", " list(\n", " it.starmap(\n", " format_tick_value,\n", " it.pairwise((None, *ax.get_xticklabels())),\n", " ),\n", " ),\n", " )\n", "\n", "# ----- finalize plot -----\n", "grid.axes.flat[0].legend(\n", " loc=\"upper left\",\n", " bbox_to_anchor=(0.08, 0.95),\n", " bbox_transform=grid.figure.transFigure,\n", " frameon=True,\n", ")\n", "grid.set_ylabels(\"Precipitation (mm)\")\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## patches" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from https://matplotlib.org/stable/gallery/shapes_and_collections/ellipse_demo.html\n", "\n", "# Generate a sequence of random ellipses\n", "NUM_ELLIPSES = 3000\n", "ellipses = [\n", " mpl_patches.Ellipse(\n", " xy=np.random.rand(2) * 20,\n", " width=np.random.rand(),\n", " height=np.random.rand(),\n", " angle=np.random.rand() * 360,\n", " facecolor=np.random.rand(3),\n", " alpha=0.4,\n", " zorder=-2,\n", " )\n", " for _ in range(NUM_ELLIPSES)\n", "]\n", "\n", "\n", "# define plot function to add ellipses to axes\n", "def draw_ellipses(ax):\n", " ax.set(\n", " xlim=(0, 20), ylim=(0, 20), aspect=\"equal\"\n", " ) # set axis limits and aspect ratio\n", "\n", " for ellipse in ellipses:\n", " # note: must copy patches because each can only render one one axes\n", " ellipse_copy = copy.copy(ellipse)\n", " ax.add_artist(ellipse_copy)\n", " ellipse_copy.set_clip_box(ax.bbox)\n", "\n", "\n", "# initialize outset grid with marqueeplot source configurations\n", "outset_grid = otst.OutsetGrid(\n", " [(4, 4, 7, 6), (10, 10, 13, 12)], # coordinates for marquee frames\n", " marqueeplot_source_kws=dict(\n", " frame_face_kws={\"alpha\": 0.5},\n", " leader_face_kws={\"alpha\": 1.0},\n", " leader_edge_kws={\"ls\": \"-\"},\n", " ),\n", ")\n", "\n", "# add content to all subplots\n", "outset_grid.broadcast(draw_ellipses)\n", "\n", "# display marquee plot\n", "outset_grid.marqueeplot()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from https://matplotlib.org/stable/gallery/shapes_and_collections/ellipse_demo.html\n", "\n", "# Generate a sequence of random ellipses\n", "NUM_ELLIPSES = 3000\n", "ellipses = [\n", " mpl_patches.Ellipse(\n", " xy=np.random.rand(2) * 20,\n", " width=np.random.rand(),\n", " height=np.random.rand(),\n", " angle=np.random.rand() * 360,\n", " facecolor=np.random.rand(3),\n", " alpha=0.4,\n", " zorder=-2,\n", " )\n", " for _ in range(NUM_ELLIPSES)\n", "]\n", "\n", "\n", "# define plot function to add ellipses to axes\n", "def draw_ellipses(ax):\n", " ax.set(\n", " xlim=(0, 20), ylim=(0, 20), aspect=\"equal\"\n", " ) # set axis limits and aspect ratio\n", "\n", " for ellipse in ellipses:\n", " # note: must copy patches because each can only render one one axes\n", " ellipse_copy = copy.copy(ellipse)\n", " ax.add_artist(ellipse_copy)\n", " ellipse_copy.set_clip_box(ax.bbox)\n", "\n", "\n", "outset_grid = otst.OutsetGrid(\n", " [(4, 4, 5, 5)], # choose area to outset\n", " marqueeplot_source_kws=dict(\n", " frame_face_kws={\"alpha\": 1.0, \"color\": \"lightblue\"},\n", " frame_inner_pad=0.3,\n", " leader_face_kws={\"alpha\": 1.0},\n", " leader_edge_kws={\"ls\": \"-\"},\n", " ),\n", ")\n", "\n", "\n", "# add content to all subplots\n", "outset_grid.broadcast(draw_ellipses)\n", "\n", "# reposition outset plots over source plot\n", "otst.inset_outsets(outset_grid, strip_ticks=False)\n", "\n", "# add white background to inset axes\n", "for ax in outset_grid.outset_axes:\n", " outset_grid.source_axes.add_patch(\n", " mpl_patches.Rectangle(\n", " (-0.33, -0.25),\n", " 1.5,\n", " 1.5,\n", " transform=ax.transAxes,\n", " facecolor=\"white\",\n", " edgecolor=\"none\",\n", " zorder=-1,\n", " )\n", " )\n", "\n", "\n", "# dispatch marquee render\n", "outset_grid.marqueeplot()\n", "\n", "pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# make some values to plot\n", "x_values = np.linspace(0, 6, 10000)\n", "y_values = np.sin(x_values) + np.cos(x_values * 100) * 0.01\n", "\n", "\n", "# setup outset grid\n", "outset_grid = otst.OutsetGrid(\n", " data=otst_util.NamedFrames(\n", " peak=(2.3 / 2, 0.95, 4 / 2, 1.05), # coordinates for 'peak' frame\n", " mid=(2.9, -0.08, 3.4, 0.08), # coordinates for 'mid' frame\n", " ),\n", " marqueeplot_kws={\n", " \"mark_glyph\": otst_mark.MarkAlphabeticalBadges(start=\"X\")\n", " }, # start badges from 'X'\n", " col=\"region\", # used for autogenerated axis titles\n", " hue=False, # ensure al marquees same color\n", ")\n", "\n", "# Broadcast the plot function to all subplots with orange color\n", "outset_grid.broadcast(\n", " plt.plot,\n", " x_values,\n", " y_values,\n", " color=\"orange\",\n", ")\n", "\n", "# render marquees\n", "outset_grid.marqueeplot()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## regplot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# initialize outset grid with iris dataset and specific configurations\n", "outset_grid = otst.OutsetGrid(\n", " data=sns.load_dataset(\"iris\"),\n", " x=\"petal_length\",\n", " y=\"petal_width\",\n", " col=\"species\",\n", " hue=\"species\",\n", " col_wrap=2,\n", " marqueeplot_source_kws={ # tweak callout geometry, just for source plot\n", " \"leader_stretch\": 0.07,\n", " \"mark_retract\": 0.25,\n", " },\n", " zorder=4,\n", ")\n", "\n", "# map kdeplot for petal length and width on source plots with legend disabled\n", "outset_grid.map_dataframe_source(\n", " sns.kdeplot,\n", " x=\"petal_length\",\n", " y=\"petal_width\",\n", ")\n", "\n", "# map regplot for petal length and width on outset plots\n", "# use wrapper around regplot from outset library to enable facet by hue\n", "outset_grid.map_dataframe_outset(\n", " otst_patched.regplot,\n", " x=\"petal_length\",\n", " y=\"petal_width\",\n", ")\n", "\n", "# display marquee plot and add legend\n", "outset_grid.marqueeplot()\n", "outset_grid.add_legend()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## scatterplot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from https://seaborn.pydata.org/examples/scatter_bubbles.html\n", "\n", "# initialize outset grid with specific configurations for marquee plot\n", "outset_grid = otst.OutsetGrid(\n", " data=otst_util.NamedFrames(\n", " moped=(73.5, 23.5, 78.5, 31.5), # (x0, y0, x1, y1) region to outset\n", " ),\n", " color=\"black\",\n", " col=\"market\", # used for axis title\n", " marqueeplot_kws={\n", " \"mark_glyph\": otst.mark.MarkAlphabeticalBadges(start=\"A\"), # uppercase\n", " \"frame_outer_pad\": 0.2,\n", " \"frame_outer_pad_unit\": \"inches\",\n", " \"frame_face_kws\": {\"facecolor\": \"none\"}, # remove marquee frame fill\n", " \"leader_face_kws\": {\"alpha\": 1},\n", " \"leader_edge_kws\": {\"lw\": 2, \"ec\": \"k\", \"alpha\": 0.5},\n", " \"leader_stretch\": 0.8,\n", " },\n", ")\n", "\n", "# broadcast scatterplot with mpg dataset to all subplots\n", "outset_grid.broadcast(\n", " sns.scatterplot,\n", " data=sns.load_dataset(\"mpg\").dropna(),\n", " x=\"horsepower\",\n", " y=\"mpg\",\n", " hue=\"origin\",\n", " size=\"weight\",\n", " sizes=(40, 400),\n", " alpha=0.5,\n", " palette=\"muted\",\n", " zorder=0,\n", ")\n", "\n", "# display marquee plot\n", "# disable aspect equalization to fill outset axes with outset region\n", "outset_grid.marqueeplot(equalize_aspect=False)\n", "outset_grid.add_legend()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from https://seaborn.pydata.org/examples/scatter_bubbles.html\n", "\n", "# initialize outset grid with specific marqueeplot configurations\n", "outset_grid = otst.OutsetGrid(\n", " aspect=1.6,\n", " data=otst_util.NamedFrames(\n", " urban=(58.5, 30.5, 65.5, 38.5),\n", " rural=(73.5, 23.5, 78.5, 31.5), # (x0, y0, x1, y1) region to outset\n", " ),\n", " # color=sns.color_palette()[-1],\n", " palette=sns.hls_palette(2),\n", " hue=\"consumer\",\n", " marqueeplot_kws={ # tweak marquee plot geometry and style\n", " \"frame_outer_pad\": 0.2,\n", " \"frame_outer_pad_unit\": \"inches\",\n", " \"frame_edge_kws\": {\"lw\": 1, \"ec\": \"k\"},\n", " \"frame_face_kws\": {\"facecolor\": \"none\"},\n", " \"leader_edge_kws\": {\"lw\": 1, \"ec\": \"k\", \"alpha\": 0.5},\n", " \"leader_face_kws\": {\"alpha\": 0.8, \"zorder\": -2},\n", " \"leader_stretch\": 1,\n", " },\n", " marqueeplot_outset_kws={ # specialize outset marqueeplot configuraiton\n", " \"frame_outer_pad\": 0.1,\n", " \"frame_outer_pad_unit\": \"axes\",\n", " \"leader_face_kws\": {\"alpha\": 1.0},\n", " \"leader_stretch\": 0.15,\n", " \"leader_stretch_unit\": \"inches\",\n", " },\n", ")\n", "outset_grid.add_legend()\n", "\n", "# broadcast scatterplot with mpg dataset to all subplots\n", "outset_grid.broadcast(\n", " sns.scatterplot,\n", " data=sns.load_dataset(\"mpg\").dropna(),\n", " x=\"horsepower\",\n", " y=\"mpg\",\n", " hue=\"origin\",\n", " size=\"weight\",\n", " sizes=(40, 400),\n", " alpha=0.5,\n", " palette=\"muted\",\n", " zorder=0,\n", ")\n", "\n", "# scatterplot created a scatter legend, move it to the right of the figure\n", "sns.move_legend(\n", " outset_grid.source_axes, loc=\"center left\", bbox_to_anchor=(1, 0.3)\n", ")\n", "# style: remove figure box outline\n", "outset_grid.source_axes.get_legend().get_frame().set_linewidth(0.0)\n", "\n", "# move outset legend created earlier into position up top\n", "sns.move_legend(outset_grid.figure, loc=\"center left\", bbox_to_anchor=(0.8, 1))\n", "\n", "# inset outsets without equalizing aspect and display marquee plot\n", "otst.inset_outsets(outset_grid, equalize_aspect=False)\n", "outset_grid.marqueeplot(equalize_aspect=False)\n", "\n", "# set title for source axes, with newline to move it a little higher\n", "outset_grid.source_axes.set_title(\"vroom vroom\\n\")\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# initialize outset grid\n", "outset_grid = otst.OutsetGrid(\n", " # pick marquee frame positions automatically from data\n", " data=sns.load_dataset(\"penguins\").dropna(),\n", " x=\"bill_length_mm\",\n", " y=\"bill_depth_mm\",\n", " col=\"island\",\n", " hue=\"species\", # split within columns using different colored marquees\n", " marqueeplot_kws={\"mark_glyph\": otst_mark.MarkRomanBadges},\n", " marqueeplot_source_kws={\n", " \"leader_face_kws\": {\"alpha\": 0.2},\n", " # this kwarg pushes markers away from the region's centroid,\n", " # resolving overlap between blue marquee markers in source plot\n", " \"leader_tweak\": otst_tweak.TweakSpreadArea(\n", " spread_factor=(2, 2.5),\n", " xlim=(45.5, 52),\n", " ylim=(21, 24),\n", " ),\n", " },\n", ")\n", "\n", "# map scatterplot for bill length vs. depth\n", "outset_grid.map_dataframe(\n", " sns.scatterplot,\n", " x=\"bill_length_mm\",\n", " y=\"bill_depth_mm\",\n", ")\n", "\n", "# display marquee plot, set axis labels, and add legend\n", "outset_grid.marqueeplot()\n", "\n", "# add nicer axis labels and a legend\n", "outset_grid.set_axis_labels(\"bill length (mm)\", \"bill depth (mm)\")\n", "outset_grid.add_legend()\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# initialize outset grid\n", "outset_grid = otst.OutsetGrid(\n", " # pick marquee frame positions automatically from data\n", " data=sns.load_dataset(\"diamonds\").dropna(),\n", " aspect=1.5,\n", " x=\"carat\",\n", " y=\"price\",\n", " hue=\"cut\", # split within columns using different colored marquees\n", " hue_order=[\"Good\", \"Fair\"],\n", " col=\"cut\",\n", " col_order=[\"Good\", \"Fair\"],\n", " row=\"clarity\", # trick to subset outsets plots (not source) to IF clarity\n", " row_order=[\"IF\"], # note that row order contains only one item\n", " marqueeplot_kws={ # style marquee elements\n", " \"mark_glyph\": otst_mark.MarkRomanBadges,\n", " \"leader_stretch\": 0.4,\n", " \"leader_tweak\": otst_tweak.TweakReflect(),\n", " },\n", " marqueeplot_outset_kws={ # specialize marquee styling on outsets\n", " \"leader_stretch\": 0.2,\n", " \"leader_stretch_unit\": \"inches\",\n", " \"leader_tweak\": otst_tweak.TweakReflect(vertical=True),\n", " \"mark_glyph_kws\": {\"zorder\": 10}, # ensure glyph above axes\n", " },\n", ")\n", "\n", "# map scatterplot across faceted outset axes AND source axes\n", "outset_grid.map_dataframe(\n", " otst_patched.scatterplot, # patching for mwaskom/seaborn issue #3601\n", " x=\"carat\",\n", " y=\"price\",\n", " legend=False,\n", " marker=otst_util.SplitKwarg( # specialize styling between source/outsets\n", " source=\"o\", outset=\"+\"\n", " ),\n", " alpha=otst_util.SplitKwarg(source=0.3, outset=1.0),\n", " s=otst_util.SplitKwarg(source=6, outset=20), # marker size\n", " zorder=-2, # ensure scatter behind marquees\n", ")\n", "# title source plot\n", "outset_grid.source_axes.set_title(\"clarity = all | cut = {Fair, Good}\")\n", "\n", "otst.inset_outsets( # rearrange move outset axes on top of source plot\n", " outset_grid,\n", " insets=otst_util.layout_corner_insets( # lay out insets semi-manually\n", " 2, # two insets\n", " corner=\"SE\", # lower right corner\n", " inset_grid_size=(0.5, 1.0),\n", " inset_margin_size=(0, 0.05),\n", " inset_pad_ratio=(0.1, 0.3),\n", " transpose=True, # stack insets vertically instead of side by side\n", " ),\n", " strip_titles=False, # keep nice faceted titles\n", ")\n", "outset_grid.marqueeplot() # render marquee annotations\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from https://stackoverflow.com/a/72778992/17332200\n", "\n", "xdata = [1, 2, 3, 4, 5, 55, 1, 6, 7, 24, 67, 33, 41, 75, 100_000, 1_000_000]\n", "ydata = xdata[1:] + xdata[:1] # slightly vary from x coords for nice plot\n", "# Determine outlier status using 1.5x interquartile range threshold\n", "outlier_bounds_x = otst_stub.CalcBoundsIQR(1.5)(xdata)\n", "outlier_bounds_y = otst_stub.CalcBoundsIQR(1.5)(ydata)\n", "is_outlier = (np.clip(xdata, *outlier_bounds_x) != xdata) | (\n", " np.clip(ydata, *outlier_bounds_y) != ydata\n", ")\n", "\n", "data = pd.DataFrame({\"x\": xdata, \"y\": ydata, \"outlier\": is_outlier})\n", "\n", "# Initialize an OutsetGrid object\n", "outset_grid = otst.OutsetGrid(\n", " data=data,\n", " x=\"x\",\n", " y=\"y\",\n", " col=\"outlier\", # split plots based on outlier status\n", " col_order=[False], # only magnify non-outlier data\n", " marqueeplot_source_kws={ # style zoom indicator elements\n", " \"leader_stretch\": 0.5,\n", " \"leader_stretch_unit\": \"inches\",\n", " },\n", ")\n", "\n", "outset_grid.map_dataframe(sns.scatterplot, x=\"x\", y=\"y\")\n", "\n", "otst.inset_outsets(outset_grid) # rearrange move outset axes on top of source\n", "outset_grid.marqueeplot() # render marquee annotations\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ----- prepare data -----\n", "df = pd.read_csv(\"https://osf.io/bvrjm/download\")\n", "df[\"Wealth\"] = ( # must out pesky non-numeric characters before conversion\n", " df[\"Net Worth\"].str.replace(r\"[^.0-9]+\", \"\", regex=True).astype(float)\n", ")\n", "df[\"Who\"] = ( # shorten by abbreviating first name and chopping long names\n", " df[\"Name\"]\n", " .str.replace(r\"\\s*&.*\", \"\", regex=True)\n", " .replace(r\"(\\b[A-Za-z])\\w+\\s+\", r\"\\1. \", regex=True)\n", " .str.slice(0, 12)\n", ")\n", "df[\"Industry Rank\"] = df.groupby(\"Industry\")[\"Wealth\"].rank(\n", " method=\"dense\", ascending=False\n", ")\n", "focal_industries = [\n", " \"Technology\",\n", " \"Fashion & Retail\",\n", " \"Sports\",\n", " \"Finance & Investments\",\n", " \"Automotive\",\n", "]\n", "\n", "with plt.style.context(\"bmh\"): # temporarily switch matplotlib aesthetics\n", " # ----- setup axes grid -----\n", " grid = otst.OutsetGrid( # setup axes grid manager\n", " df[(df[\"Industry Rank\"] < 8)].dropna(), # only top 8 in each industry\n", " x=\"Age\",\n", " y=\"Wealth\",\n", " col=\"Industry\",\n", " col_order=focal_industries,\n", " hue=\"Industry\",\n", " hue_order=focal_industries,\n", " aspect=1.5, # widen subplots\n", " col_wrap=3,\n", " )\n", "\n", " # ----- plot content -----\n", " grid.map_dataframe( # map scatterplot over all axes\n", " otst_patched.scatterplot,\n", " x=\"Age\",\n", " y=\"Wealth\",\n", " legend=False,\n", " )\n", " grid.map_dataframe_outset( # map name annotations over all outset axes\n", " otst_patched.annotateplot,\n", " x=\"Age\",\n", " y=\"Wealth\",\n", " text=\"Who\",\n", " fontsize=8, # make text slightly smaller so it's easier to lay out\n", " rotation=-5,\n", " adjusttext_kws=dict( # tweak fiddly params for text layout solver\n", " avoid_self=True,\n", " autoalign=True,\n", " expand_points=(1.8, 1.3),\n", " arrowprops=dict(arrowstyle=\"-\", color=\"k\", lw=0.5),\n", " expand_text=(1.8, 2),\n", " force_points=(1.2, 1),\n", " ha=\"center\",\n", " va=\"top\",\n", " ),\n", " )\n", "\n", " # ----- finalize plot -----\n", " grid.set_ylabels(\"Net Worth (Billion USD)\")\n", " # render marquee annotations\n", " grid.marqueeplot(equalize_aspect=False) # allow axes aspect ratios to vary\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "___\n", "\n", "## streamplot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from # adapted from https://matplotlib.org/stable/gallery/images_contours_and_fields/plot_streamplot.html#streamplot\n", "\n", "# set up grid and calculate stream flow\n", "w = 3\n", "Y, X = np.mgrid[-w:w:100j, -w:w:100j]\n", "U = -1 - X**2 + Y\n", "V = 1 + X - Y**2\n", "speed = np.sqrt(U**2 + V**2)\n", "args = [X, Y, U, V]\n", "\n", "# create dummy axis for color reference\n", "dummy_ax = plt.gca()\n", "stream = dummy_ax.streamplot(\n", " *args,\n", " color=U,\n", " cmap=\"viridis\",\n", ")\n", "plt.close(dummy_ax.figure)\n", "\n", "# initialize outset grid with marqueeplot configurations\n", "outset_grid = otst.OutsetGrid(\n", " [(0.5, 1.25, 1, 1.5)], # (x0, y0, x1, y1) region to outset\n", " color=\"fuchsia\",\n", " marqueeplot_outset_kws={\"color\": \"mediumorchid\"},\n", " zorder=10,\n", ")\n", "\n", "# broadcast content across all axes\n", "outset_grid.broadcast(\n", " plt.streamplot,\n", " *args,\n", " color=U,\n", " linewidth=1,\n", " cmap=\"viridis\",\n", " # specialize style between source axes and outset axes\n", " density=otst_util.SplitKwarg(source=2, outset=6),\n", " arrowstyle=\"->\",\n", " arrowsize=1.5,\n", ")\n", "\n", "# render marquees\n", "outset_grid.marqueeplot()\n", "\n", "# add color after rendering marquee to prevent layout issues\n", "cbar = outset_grid.figure.colorbar(\n", " stream.lines,\n", " ax=outset_grid.axes.flat[-1],\n", " location=\"right\",\n", " fraction=1e-9, # minimize space stolen when inserting cbar axis\n", ")\n", "cbar.ax.set_position([1, 0.15, 0.2, 0.73]) # move and grow to size\n", "\n", "pass # sponge up last return value, if any" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# adapted from # adapted from https://matplotlib.org/stable/gallery/images_contours_and_fields/plot_streamplot.html#streamplot\n", "\n", "# set up grid and calculate stream flow\n", "w = 3\n", "Y, X = np.mgrid[-w:w:100j, -w:w:100j]\n", "U = -1 - X**2 + Y\n", "V = 1 + X - Y**2\n", "speed = np.sqrt(U**2 + V**2)\n", "args = [X, Y, U, V]\n", "\n", "# create dummy axis for color reference\n", "dummy_ax = plt.gca()\n", "stream = dummy_ax.streamplot(\n", " *args,\n", " color=U,\n", " cmap=\"viridis\",\n", ")\n", "plt.close(dummy_ax.figure)\n", "\n", "# initialize grid\n", "og = otst.OutsetGrid(\n", " [(0.5, 1.25, 1, 1.5)], # (x0, y0, x1, y1) region to outset\n", " color=\"fuchsia\",\n", " zorder=10,\n", ")\n", "\n", "# draw content to all axes\n", "og.broadcast(\n", " plt.streamplot,\n", " *args,\n", " color=U,\n", " linewidth=1,\n", " cmap=\"viridis\",\n", " # specialize style between source axes and outset axes\n", " density=otst_util.SplitKwarg(source=2, outset=6),\n", " arrowstyle=\"->\",\n", " arrowsize=1.5,\n", ")\n", "\n", "# render marquee annotations\n", "og.marqueeplot()\n", "\n", "# add the colorbar before insetting to prevent layout issues\n", "cbar = og.figure.colorbar(\n", " stream.lines,\n", " ax=og.axes.flat[-1],\n", " location=\"right\",\n", " fraction=1e-9, # minimize space stolen when inserting cbar axis\n", ")\n", "\n", "# move outset axes onto source axis\n", "otst.inset_outsets(\n", " og,\n", " insets=\"SW\",\n", " equalize_aspect=False,\n", ")\n", "# thicken axes spines and set white to act like a frame around inset\n", "for ax in og.outset_axes:\n", " for spine in ax.spines.values():\n", " spine.set_linewidth(6)\n", " spine.set_edgecolor(\"white\")\n", " spine.set_visible(True)\n", "\n", "# grow c bar and move into position\n", "cbar.ax.set_position([1, 0.15, 0.2, 0.73])\n", "\n", "pass # sponge up last return value, if any" ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 2 }