{
 "cells": [
  {
   "cell_type": "raw",
   "id": "6cb222bb-4781-48b6-9675-c0ba195b5efb",
   "metadata": {},
   "source": [
    ".. _properties_tutorial:\n",
    "\n",
    "Properties of Mark objects\n",
    "==========================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae9d52dc-55ad-4804-a533-f2b724d0b85b",
   "metadata": {
    "tags": [
     "hide"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib as mpl\n",
    "import seaborn.objects as so\n",
    "from seaborn import axes_style, color_palette"
   ]
  },
  {
   "cell_type": "raw",
   "id": "dd828c60-3895-46e4-a2f4-782a6e6cd9a6",
   "metadata": {},
   "source": [
    "Coordinate properties\n",
    "---------------------"
   ]
  },
  {
   "cell_type": "raw",
   "id": "fa97cc40-f02f-477b-90ec-a764b7253b68",
   "metadata": {},
   "source": [
    ".. _coordinate_property:\n",
    "\n",
    "x, y, xmin, xmax, ymin, ymax\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "Coordinate properties determine where a mark is drawn on a plot. Canonically, the `x` coordinate is the horizontal position and the `y` coordinate is the vertical position. Some marks accept a span (i.e., `min`, `max`) parameterization for one or both variables. Others may accept `x` and `y` but also use a `baseline` parameter to show a span. The layer's `orient` parameter determines how this works.\n",
    "\n",
    "If a variable does not contain numeric data, its scale will apply a conversion so that data can be drawn on a screen. For instance, :class:`Nominal` scales assign an integer index to each distinct category, and :class:`Temporal` scales represent dates as the number of days from a reference \"epoch\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b418365-b99c-45d6-bf1e-e347e2b9012a",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(y=[0, 0, 0])\n",
    "    .pair(x=[\n",
    "        [1, 2, 3],\n",
    "        [\"A\", \"B\", \"C\"],\n",
    "        np.array([\"2020-01-01\", \"2020-02-01\", \"2020-03-01\"], dtype=\"datetime64\"),\n",
    "    ])\n",
    "    .limit(\n",
    "        x0=(0, 10),\n",
    "        x1=(-.5, 2.5),\n",
    "        x2=(pd.Timestamp(\"2020-01-01\"), pd.Timestamp(\"2020-03-01\"))\n",
    "    )\n",
    "    .scale(y=so.Continuous().tick(count=0), x2=so.Temporal().label(concise=True))\n",
    "    .layout(size=(7, 1), engine=\"tight\")\n",
    "    .label(x0=\"Continuous\", x1=\"Nominal\", x2=\"Temporal\")\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n",
    "    })\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "0ae06665-2ce5-470d-b90a-02d990221fc5",
   "metadata": {},
   "source": [
    "A :class:`Continuous` scale can also apply a nonlinear transform between data values and spatial positions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b731a3bb-a52e-4b12-afbb-b036753adcbe",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "(\n",
    "    so.Plot(y=[0, 0, 0])\n",
    "    .pair(x=[[1, 10, 100], [-100, 0, 100], [0, 10, 40]])\n",
    "    .limit(\n",
    "    )\n",
    "    .add(so.Dot(marker=\"\"))\n",
    "    .scale(\n",
    "        y=so.Continuous().tick(count=0),\n",
    "        x0=so.Continuous(trans=\"log\"),\n",
    "        x1=so.Continuous(trans=\"symlog\").tick(at=[-100, -10, 0, 10, 100]),\n",
    "        x2=so.Continuous(trans=\"sqrt\").tick(every=10),\n",
    "    )\n",
    "    .layout(size=(7, 1), engine=\"tight\")\n",
    "    .label(x0=\"trans='log'\", x1=\"trans='symlog'\", x2=\"trans='sqrt'\")\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n",
    "        \"axes.labelpad\": 8,\n",
    "    })\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e384941a-da38-4e12-997d-d750b19b1fa6",
   "metadata": {
    "tags": [
     "hide-input",
     "hide"
    ]
   },
   "outputs": [],
   "source": [
    "# Hiding from the page but keeping around for now\n",
    "(\n",
    "    so.Plot()\n",
    "    .add(\n",
    "        so.Dot(edgewidth=3, stroke=3),\n",
    "        so.Dodge(by=[\"group\"]),\n",
    "        x=[\"A\", \"A\", \"A\", \"A\", \"A\"],\n",
    "        y=[1.75, 2.25, 2.75, 2.0, 2.5],\n",
    "        color=[1, 2, 3, 1, 3],\n",
    "        marker=[mpl.markers.MarkerStyle(x) for x in \"os^+o\"],\n",
    "        pointsize=(9, 9, 9, 13, 10),\n",
    "        fill=[True, False, True, True, False],\n",
    "        group=[1, 2, 3, 4, 5], width=.5, legend=False,\n",
    "    )\n",
    "    .add(\n",
    "        so.Bar(edgewidth=2.5, alpha=.2, width=.9),\n",
    "        so.Dodge(gap=.05),\n",
    "        x=[\"B\", \"B\", \"B\",], y=[2, 2.5, 1.75], color=[1, 2, 3],\n",
    "        legend=False,\n",
    "    )\n",
    "    .add(\n",
    "        so.Range({\"capstyle\": \"round\"}, linewidth=3),\n",
    "        so.Dodge(by=[\"group\"]),\n",
    "        x=[\"C\", \"C\", \"C\"], ymin=[1.5, 1.75, 1.25], ymax=[2.5, 2.75, 2.25],\n",
    "        color=[1, 2, 2], linestyle=[\"-\", \"-\", \":\"],\n",
    "        group=[1, 2, 3], width=.5, legend=False,\n",
    "    )\n",
    "    .layout(size=(4, 4), engine=None)\n",
    "    .limit(x=(-.5, 2.5), y=(0, 3))\n",
    "    .label(x=\"X Axis (nominal)\", y=\"Y Axis (continuous)\")\n",
    "    .scale(\n",
    "        color=\"dark:C0_r\", #None,\n",
    "        fill=None, marker=None,\n",
    "        pointsize=None, linestyle=None,\n",
    "        y=so.Continuous().tick(every=1, minor=1)\n",
    "    )\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        \"axes.spines.top\": False, \"axes.spines.right\": False,\n",
    "        \"axes.labelsize\": 14,\n",
    "    })\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "8279d74f-0cd0-4ba8-80ed-c6051541d956",
   "metadata": {},
   "source": [
    "Color properties\n",
    "----------------"
   ]
  },
  {
   "cell_type": "raw",
   "id": "fca25527-6bbe-42d6-beea-a996a46d9761",
   "metadata": {},
   "source": [
    ".. _color_property:\n",
    "\n",
    "color, fillcolor, edgecolor\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "All marks can be given a `color`, and many distinguish between the color of the mark's \"edge\" and \"fill\". Often, simply using `color` will set both, while the more-specific properties allow further control:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff7a1e64-7b02-45b8-b1e7-d7ec2bf1e7f7",
   "metadata": {
    "tags": [
     "hide"
    ]
   },
   "outputs": [],
   "source": [
    "no_spines = {\n",
    "    f\"axes.spines.{side}\": False\n",
    "    for side in [\"left\", \"right\", \"bottom\", \"top\"]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1dda4c42-31f4-4316-baad-f30a465d3fd9",
   "metadata": {
    "tags": [
     "hide"
    ]
   },
   "outputs": [],
   "source": [
    "color_mark = so.Dot(marker=\"s\", pointsize=20, edgewidth=2.5, alpha=.7, edgealpha=1)\n",
    "color_plot = (\n",
    "    so.Plot()\n",
    "    .theme({\n",
    "        **axes_style(\"white\"),\n",
    "        **no_spines,\n",
    "        \"axes.titlesize\": 15,\n",
    "        \"figure.subplot.wspace\": .1,\n",
    "        \"axes.xmargin\": .1,\n",
    "    })\n",
    "    .scale(\n",
    "        x=so.Continuous().tick(count=0),\n",
    "        y=so.Continuous().tick(count=0),\n",
    "        color=None, edgecolor=None,\n",
    "    )\n",
    "    .layout(size=(9, .5), engine=None)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54fc98b4-dc4c-45e1-a2a7-840a724fc746",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "n = 6\n",
    "rgb = [f\"C{i}\" for i in range(n)]\n",
    "(\n",
    "    color_plot\n",
    "    .facet([\"color\"] * n + [\"edgecolor\"] * n + [\"fillcolor\"] * n)\n",
    "    .add(\n",
    "        color_mark,\n",
    "        x=np.tile(np.arange(n), 3),\n",
    "        y=np.zeros(n * 3),\n",
    "        color=rgb + [\".8\"] * n + rgb,\n",
    "        edgecolor=rgb + rgb + [\".3\"] * n,\n",
    "        legend=False,\n",
    "    )\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "0dc26a01-6290-44f4-9815-5cea531207e2",
   "metadata": {},
   "source": [
    "When the color property is mapped, the default palette depends on the type of scale. Nominal scales use discrete, unordered hues, while continuous scales (including temporal ones) use a sequential gradient:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6927a0d3-687b-4ca0-a425-0376b39f1b1f",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "n = 9\n",
    "rgb = color_palette(\"deep\", n) + color_palette(\"ch:\", n)\n",
    "(\n",
    "    color_plot\n",
    "    .facet([\"nominal\"] * n + [\"continuous\"] * n)\n",
    "    .add(\n",
    "        color_mark,\n",
    "        x=list(range(n)) * 2,\n",
    "        y=[0] * n * 2,\n",
    "        color=rgb,\n",
    "        legend=False,\n",
    "    )\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "e79d0da7-a53e-468c-9952-726eeae810d1",
   "metadata": {},
   "source": [
    ".. note::\n",
    "    The default continuous scale is subject to change in future releases to improve discriminability.\n",
    "\n",
    "Color scales are parameterized by the name of a palette, such as `'viridis'`, `'rocket'`, or `'deep'`. Some palette names can include parameters, including simple gradients (e.g. `'dark:blue'`) or the cubehelix system (e.g. `'ch:start=.2,rot=-.4``). See the :doc:`color palette tutorial </tutorial/color_palettes>` for guidance on making an appropriate selection.\n",
    "\n",
    "Continuous scales can also be parameterized by a tuple of colors that the scale should interpolate between. When using a nominal scale, it is possible to provide either the name of the palette (which will be discretely-sampled, if necessary), a list of individual color values, or a dictionary directly mapping data values to colors.\n",
    "\n",
    "Individual colors may be specified `in a wide range of formats <https://matplotlib.org/stable/tutorials/colors/colors.html>`_. These include indexed references to the current color cycle (`'C0'`), single-letter shorthands (`'b'`), grayscale values (`'.4'`), RGB hex codes (`'#4c72b0'`), X11 color names (`'seagreen'`), and XKCD color survey names (`'purpleish'`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce7300dc-0ed2-4eb3-bd6f-2e42280f5e54",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "color_dict = {\n",
    "    \"cycle\": [\"C0\", \"C1\", \"C2\"],\n",
    "    \"short\": [\"r\", \"y\", \"b\"],\n",
    "    \"gray\": [\".3\", \".7\", \".5\"],\n",
    "    \"hex\": [\"#825f87\", \"#05696b\", \"#de7e5d\"],\n",
    "    \"X11\": [\"seagreen\", \"sienna\", \"darkblue\"],\n",
    "    \"XKCD\": [\"xkcd:gold\", \"xkcd:steel\", \"xkcd:plum\"],\n",
    "}\n",
    "groups = [k for k in color_dict for _ in range(3)]\n",
    "colors = [c for pal in color_dict.values() for c in pal]\n",
    "(\n",
    "    so.Plot(\n",
    "        x=[0] * len(colors),\n",
    "        y=[f\"'{c}'\" for c in colors],\n",
    "        color=colors,\n",
    "    )\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **no_spines,\n",
    "        \"axes.ymargin\": .2,\n",
    "        \"axes.titlesize\": 14,\n",
    "        \n",
    "    })\n",
    "    .facet(groups)\n",
    "    .layout(size=(8, 1.15), engine=\"constrained\")\n",
    "    .scale(x=so.Continuous().tick(count=0))\n",
    "    .add(color_mark)\n",
    "    .limit(x=(-.2, .5))\n",
    "    # .label(title=\"{}      \".format)\n",
    "    .label(title=\"\")\n",
    "    .scale(color=None)\n",
    "    .share(y=False)\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "4ea6ac35-2a73-4dec-8b9b-bf15ba67f01b",
   "metadata": {},
   "source": [
    ".. _alpha_property:\n",
    "\n",
    "alpha, fillalpha, edgealpha\n",
    "~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "The `alpha` property determines the mark's opacity. Lowering the alpha can be helpful for representing density in the case of overplotting:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e73839d2-27c4-42b8-8587-9f6e99c8a464",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(3)\n",
    "n_samp = 300\n",
    "x = 1 - rng.exponential(size=n_samp)\n",
    "y = rng.uniform(-1, 1, size=n_samp)\n",
    "keep = np.sqrt(x ** 2 + y ** 2) < 1\n",
    "x, y = x[keep], y[keep]\n",
    "n = keep.sum()\n",
    "alpha_vals = np.linspace(.1, .9, 9).round(1)\n",
    "xs = np.concatenate([x for _ in alpha_vals])\n",
    "ys = np.concatenate([y for _ in alpha_vals])\n",
    "alphas = np.repeat(alpha_vals, n)\n",
    "(\n",
    "    so.Plot(x=xs, y=ys, alpha=alphas)\n",
    "    .facet(alphas)\n",
    "    .add(so.Dot(color=\".2\", pointsize=3))\n",
    "    .scale(\n",
    "        alpha=None,\n",
    "        x=so.Continuous().tick(count=0),\n",
    "        y=so.Continuous().tick(count=0)\n",
    "    )\n",
    "    .layout(size=(9, 1), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"white\"),\n",
    "        **no_spines,\n",
    "    })\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "a551732e-e8f5-45f0-9345-7ef45248d9d7",
   "metadata": {},
   "source": [
    "Mapping the `alpha` property can also be useful even when marks do not overlap because it conveys a sense of importance and can be combined with a `color` scale to represent two variables. Moreover, colors with lower alpha appear less saturated, which can improve the appearance of larger filled marks (such as bars).\n",
    "\n",
    "As with `color`, some marks define separate `edgealpha` and `fillalpha` properties for additional control."
   ]
  },
  {
   "cell_type": "raw",
   "id": "77d168e4-0539-409f-8542-750d3981e22b",
   "metadata": {},
   "source": [
    "Style properties\n",
    "----------------"
   ]
  },
  {
   "cell_type": "raw",
   "id": "95e342fa-1086-4e63-81ae-dce1c628df9b",
   "metadata": {},
   "source": [
    ".. _fill_property:\n",
    "\n",
    "fill\n",
    "~~~~\n",
    "\n",
    "The `fill` property is relevant to marks with a distinction between the edge and interior and determines whether the interior is visible. It is a boolean state: `fill` can be set only to `True` or `False`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5fb3b839-8bae-4392-b5f0-70dfc5a33c7a",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "nan = float(\"nan\")\n",
    "x_bar = [0, 1]\n",
    "y_bar = [2, 1]\n",
    "f_bar = [True, False]\n",
    "\n",
    "x_dot = [2.2, 2.5, 2.8, 3.2, 3.5, 3.8]\n",
    "y_dot = [1.2, 1.7, 1.4, 0.7, 1.2, 0.9]\n",
    "f_dot = [True, True, True, False, False, False]\n",
    "\n",
    "xx = np.linspace(0, .8, 100)\n",
    "yy = xx ** 2 * np.exp(-xx * 10)\n",
    "x_area = list(4.5 + xx) + list(5.5 + xx)\n",
    "y_area = list(yy / yy.max() * 2) + list(yy / yy.max())\n",
    "f_area = [True] * 100 + [False] * 100\n",
    "\n",
    "(\n",
    "    so.Plot()\n",
    "    .add(\n",
    "        so.Bar(color=\".3\", edgecolor=\".2\", edgewidth=2.5),\n",
    "        x=x_bar + [nan for _ in x_dot + x_area],\n",
    "        y=y_bar + [nan for _ in y_dot + y_area],\n",
    "        fill=f_bar + [nan for _ in f_dot + f_area]\n",
    "    )\n",
    "    .add(\n",
    "        so.Dot(color=\".2\", pointsize=13, stroke=2.5),\n",
    "        x=[nan for _ in x_bar] + x_dot + [nan for _ in x_area],\n",
    "        y=[nan for _ in y_bar] + y_dot + [nan for _ in y_area],\n",
    "        fill=[nan for _ in f_bar] + f_dot + [nan for _ in f_area],\n",
    "    )\n",
    "    .add(\n",
    "        so.Area(color=\".2\", edgewidth=2.5),\n",
    "        x=[nan for _ in x_bar + x_dot] + x_area,\n",
    "        y=[nan for _ in y_bar + y_dot] + y_area,\n",
    "        fill=[nan for _ in f_bar + f_dot] + f_area,\n",
    "    )\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        \"axes.spines.left\": False,\n",
    "        \"axes.spines.top\": False,\n",
    "        \"axes.spines.right\": False,\n",
    "        \"xtick.labelsize\": 14,\n",
    "    })\n",
    "    .layout(size=(9, 1.25), engine=None)\n",
    "    .scale(\n",
    "        fill=None,\n",
    "        x=so.Continuous().tick(at=[0, 1, 2.5, 3.5, 4.8, 5.8]).label(\n",
    "            like={\n",
    "                0: True, 1: False, 2.5: True, 3.5: False, 4.8: True, 5.8: False\n",
    "            }.get,\n",
    "        ),\n",
    "        y=so.Continuous().tick(count=0),\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "119741b0-9eca-45a1-983e-35effc49c7fa",
   "metadata": {},
   "source": [
    ".. _marker_property:\n",
    "\n",
    "marker\n",
    "~~~~~~\n",
    "\n",
    "The `marker` property is relevant for dot marks and some line marks. The API for specifying markers is very flexible, as detailed in the matplotlib API docs: :mod:`matplotlib.markers`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ba9c5aa-3d9c-47c7-8aee-5851e1f3c4dd",
   "metadata": {
    "tags": [
     "hide"
    ]
   },
   "outputs": [],
   "source": [
    "marker_plot = (\n",
    "    so.Plot()\n",
    "    .scale(marker=None, y=so.Continuous().tick(count=0))\n",
    "    .layout(size=(10, .5), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        \"axes.spines.left\": False,\n",
    "        \"axes.spines.top\": False,\n",
    "        \"axes.spines.right\": False,\n",
    "        \"xtick.labelsize\":12,\n",
    "        \"axes.xmargin\": .02,\n",
    "    })\n",
    "\n",
    ")\n",
    "marker_mark = so.Dot(pointsize=15, color=\".2\", stroke=1.5)"
   ]
  },
  {
   "cell_type": "raw",
   "id": "3c07a874-18a1-485a-8d65-70ea3f246340",
   "metadata": {},
   "source": [
    "Markers can be specified using a number of simple string codes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a764efd-df55-412b-8a01-8eba6f897893",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "marker_codes = [\n",
    "    \"o\", \"^\", \"v\", \"<\", \">\",\"s\", \"D\", \"d\", \"p\", \"h\", \"H\", \"8\",\n",
    "    \"X\", \"*\", \".\", \"P\", \"x\", \"+\", \"1\", \"2\", \"3\", \"4\", \"|\", \"_\",\n",
    "]\n",
    "x, y = [f\"'{m}'\" for m in marker_codes], [0] * len(marker_codes)\n",
    "marker_objs = [mpl.markers.MarkerStyle(m) for m in marker_codes]\n",
    "marker_plot.add(marker_mark, marker=marker_objs, x=x, y=y).plot()"
   ]
  },
  {
   "cell_type": "raw",
   "id": "1c614f08-3aa4-450d-bfe2-3295c29155d5",
   "metadata": {},
   "source": [
    "They can also be programatically generated using a `(num_sides, fill_style, angle)` tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9c1efe7-33e1-4add-9c4e-567d8dfbb821",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "marker_codes = [\n",
    "    (4, 0, 0), (4, 0, 45), (8, 0, 0),\n",
    "    (4, 1, 0), (4, 1, 45), (8, 1, 0),\n",
    "    (4, 2, 0), (4, 2, 45), (8, 2, 0),\n",
    "]\n",
    "x, y = [f\"{m}\" for m in marker_codes], [0] * len(marker_codes)\n",
    "marker_objs = [mpl.markers.MarkerStyle(m) for m in marker_codes]\n",
    "marker_plot.add(marker_mark, marker=marker_objs, x=x, y=y).plot()"
   ]
  },
  {
   "cell_type": "raw",
   "id": "dc518508-cb08-4508-a7f3-5762841da6fc",
   "metadata": {},
   "source": [
    "See the matplotlib docs for additional formats, including mathtex character codes (`'$...$'`) and arrays of vertices.\n",
    "\n",
    "A marker property is always mapped with a nominal scale; there is no inherent ordering to the different shapes. If no scale is provided, the plot will programmatically generate a suitably large set of unique markers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3466dc10-07a5-470f-adac-c3c05326945d",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "from seaborn._core.properties import Marker\n",
    "n = 14\n",
    "marker_objs = Marker()._default_values(n)\n",
    "x, y = list(map(str, range(n))), [0] * n\n",
    "marker_plot.add(marker_mark, marker=marker_objs, x=x, y=y).plot()"
   ]
  },
  {
   "cell_type": "raw",
   "id": "30916c65-6d4c-4294-a5e2-58af8b9392f3",
   "metadata": {},
   "source": [
    "While this ensures that the shapes are technically distinct, bear in mind that — in most cases — it will be difficult to tell the markers apart if more than a handful are used in a single plot.\n",
    "\n",
    ".. note::\n",
    "    The default marker scale is subject to change in future releases to improve discriminability."
   ]
  },
  {
   "cell_type": "raw",
   "id": "3b1d0630-808a-4099-8bd0-768718f86f72",
   "metadata": {},
   "source": [
    ".. _linestyle_property:\n",
    "\n",
    "linestyle, edgestyle\n",
    "~~~~~~~~~~~~~~~~~~~~\n",
    "\n",
    "The `linestyle` property is relevant to line marks, and the `edgestyle` property is relevant to a number of marks with \"edges. Both properties determine the \"dashing\" of a line in terms of on-off segments.\n",
    "\n",
    "Dashes can be specified with a small number of shorthand codes (`'-'`, `'--'`, `'-.'`, and `':'`) or programatically using `(on, off, ...)` tuples. In the tuple specification, the unit is equal to the linewidth:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33a729db-84e4-4619-bd1a-1f60c77f7073",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "xx = np.linspace(0, 1, 100)\n",
    "dashes = [\"-\", \"--\", \"-.\", \":\", (6, 2), (2, 1), (.5, .5), (4, 1, 2, 1)] \n",
    "dash_data = (\n",
    "    pd.DataFrame({i: xx for i in range(len(dashes))})\n",
    "    .stack()\n",
    "    .reset_index(1)\n",
    "    .set_axis([\"y\", \"x\"], axis=1)\n",
    "    .reset_index(drop=True)\n",
    ")\n",
    "(\n",
    "    so.Plot(dash_data, \"x\", \"y\", linestyle=\"y\")\n",
    "    .add(so.Line(linewidth=1.7, color=\".2\"), legend=None)\n",
    "    .scale(\n",
    "        linestyle=dashes,\n",
    "        x=so.Continuous().tick(count=0),\n",
    "        y=so.Continuous().tick(every=1).label(like={\n",
    "            i: f\"'$\\mathtt{{{pat}}}$'\" if isinstance(pat, str) else pat\n",
    "            for i, pat in enumerate(dashes)\n",
    "        }.get)\n",
    "    )\n",
    "    .label(x=\"\", y=\"\")\n",
    "    .limit(x=(0, 1), y=(7.5, -0.5))\n",
    "    .layout(size=(9, 2.5), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"white\"),\n",
    "        **no_spines,\n",
    "        \"ytick.labelsize\": 12,\n",
    "    })\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "41063f3b-a207-4f03-a606-78e2826be522",
   "metadata": {},
   "source": [
    "Size properties\n",
    "---------------"
   ]
  },
  {
   "cell_type": "raw",
   "id": "7a909d91-9d60-4e95-a855-18b2779f19ce",
   "metadata": {},
   "source": [
    ".. _pointsize_property:\n",
    "\n",
    "pointsize\n",
    "~~~~~~~~~\n",
    "\n",
    "The `pointsize` property is relevant to dot marks and to line marks that can show markers at individual data points. The units correspond to the diameter of the mark in points.\n",
    "\n",
    "Note that, while the parameterization corresponds to diameter, scales will be applied with a square root transform so that data values are linearly proportional to area:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b55b106d-ba14-43ec-ab9b-5d7a04fb813c",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "x = np.arange(1, 21)\n",
    "y = [0 for _ in x]\n",
    "(\n",
    "    so.Plot(x, y)\n",
    "    .add(so.Dots(color=\".2\", stroke=1), pointsize=x)\n",
    "    .layout(size=(9, .5), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n",
    "        \"xtick.labelsize\": 12,\n",
    "        \"axes.xmargin\": .025,\n",
    "    })\n",
    "    .scale(\n",
    "        pointsize=None,\n",
    "        x=so.Continuous().tick(every=1),\n",
    "        y=so.Continuous().tick(count=0),\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "66660d74-0252-4cb1-960a-c2c4823bb0e6",
   "metadata": {},
   "source": [
    ".. _linewidth_property:\n",
    "\n",
    "linewidth\n",
    "~~~~~~~~~\n",
    "\n",
    "The `linewidth` property is relevant to line marks and determines their thickness. The value should be non-negative and has point units:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a77c60d5-0d21-43a5-ab8c-f3f4abbc70ad",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "lw = np.arange(0.5, 5, .5)\n",
    "x = [i for i in [0, 1] for _ in lw]\n",
    "y = [*lw, *lw]\n",
    "(\n",
    "    so.Plot(x=x, y=y, linewidth=y)\n",
    "    .add(so.Line(color=\".2\"))\n",
    "    .limit(y=(4.9, .1))\n",
    "    .layout(size=(9, 1.4), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"bottom\", \"right\", \"top\"]},\n",
    "        \"xtick.labelsize\": 12,\n",
    "        \"axes.xmargin\": .015,\n",
    "        \"ytick.labelsize\": 12,\n",
    "    })\n",
    "    .scale(\n",
    "        linewidth=None,\n",
    "        x=so.Continuous().tick(count=0),\n",
    "        y=so.Continuous().tick(every=1, between=(.5, 4.5), minor=1),\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "dcbdfcb9-d55e-467a-8514-bdb4cc2bec90",
   "metadata": {},
   "source": [
    ".. _edgewidth_property:\n",
    "\n",
    "edgewidth\n",
    "~~~~~~~~~\n",
    "\n",
    "The `edgewidth` property is akin to `linewidth` but applies to marks with an edge/fill rather than to lines. It also has a different default range when used in a scale. The units are the same:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a1f1d5a-a2d5-4b8e-a172-73104f5ec715",
   "metadata": {
    "tags": [
     "hide"
    ]
   },
   "outputs": [],
   "source": [
    "x = np.arange(0, 21) / 5\n",
    "y = [0 for _ in x]\n",
    "edge_plot = (\n",
    "    so.Plot(x, y)\n",
    "    .layout(size=(9, .5), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n",
    "        \"xtick.labelsize\": 12,\n",
    "        \"axes.xmargin\": .02,\n",
    "    })\n",
    "    .scale(\n",
    "        x=so.Continuous().tick(every=1, minor=4),\n",
    "        y=so.Continuous().tick(count=0),\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba70ed6c-d902-41b0-a043-d8f27bf65e9b",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "(\n",
    "    edge_plot\n",
    "    .add(so.Dot(color=\".75\", edgecolor=\".2\", marker=\"o\", pointsize=14), edgewidth=x)\n",
    "    .scale(edgewidth=None)\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "98a25a16-67fa-4467-a425-6a78a17c63ab",
   "metadata": {},
   "source": [
    ".. _stroke_property:\n",
    "\n",
    "stroke\n",
    "~~~~~~\n",
    "\n",
    "The `stroke` property is akin to `edgewidth` but applies when a dot mark is defined by its stroke rather than its fill. It also has a slightly different default scale range, but otherwise behaves similarly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f73a0428-a787-4f21-8098-848eb1c816fb",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "(\n",
    "    edge_plot\n",
    "    .add(so.Dot(color=\".2\", marker=\"x\", pointsize=11), stroke=x)\n",
    "    .scale(stroke=None)\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "c2ca33db-df52-4958-889a-320b4833a0d7",
   "metadata": {},
   "source": [
    "Text properties\n",
    "---------------"
   ]
  },
  {
   "cell_type": "raw",
   "id": "b75af2fe-4d81-407c-9858-23362710f25f",
   "metadata": {},
   "source": [
    ".. _horizontalalignment_property:\n",
    "\n",
    ".. _verticalalignment_property:\n",
    "\n",
    "halign, valign\n",
    "~~~~~~~~~~~~~~\n",
    "\n",
    "The `halign` and `valign` properties control the *horizontal* and *vertical* alignment of text marks. The options for horizontal alignment are `'left'`, `'right'`, and `'center'`, while the options for vertical alignment are `'top'`, `'bottom'`, `'center'`, `'baseline'`, and `'center_baseline'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9588309-bee4-4b97-b428-eb91ea582105",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "x = [\"left\", \"right\", \"top\", \"bottom\", \"baseline\", \"center\"]\n",
    "ha = x[:2] + [\"center\"] * 4\n",
    "va = [\"center_baseline\"] * 2 + x[2:]\n",
    "y = np.zeros(len(x))\n",
    "(\n",
    "    so.Plot(x=[f\"'{_x_}'\" for _x_ in x], y=y, halign=ha, valign=va)\n",
    "    .add(so.Dot(marker=\"+\", color=\"r\", alpha=.5, stroke=1, pointsize=24))\n",
    "    .add(so.Text(text=\"XyZ\", fontsize=14, offset=0))\n",
    "    .scale(y=so.Continuous().tick(at=[]), halign=None, valign=None)\n",
    "    .limit(x=(-.25, len(x) - .75))\n",
    "    .layout(size=(9, .6), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n",
    "        \"xtick.labelsize\": 12,\n",
    "        \"axes.xmargin\": .015,\n",
    "        \"ytick.labelsize\": 12,\n",
    "    })\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "ea74c7e5-798b-47bc-bc18-9086902fb5c6",
   "metadata": {},
   "source": [
    ".. _fontsize_property:\n",
    "\n",
    "fontsize\n",
    "~~~~~~~~\n",
    "\n",
    "The `fontsize` property controls the size of textual marks. The value has point units:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c515b790-385d-4521-b14a-0769c1902928",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "from string import ascii_uppercase\n",
    "n = 26\n",
    "s = np.arange(n) + 1\n",
    "y = np.zeros(n)\n",
    "t = list(ascii_uppercase[:n])\n",
    "(\n",
    "    so.Plot(x=s, y=y, text=t, fontsize=s)\n",
    "    .add(so.Text())\n",
    "    .scale(x=so.Nominal(), y=so.Continuous().tick(at=[]))\n",
    "    .layout(size=(9, .5), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n",
    "        \"xtick.labelsize\": 12,\n",
    "        \"axes.xmargin\": .015,\n",
    "        \"ytick.labelsize\": 12,\n",
    "    })\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "4b367f36-fb96-44fa-83a3-1cc66c7a3279",
   "metadata": {},
   "source": [
    ".. _offset_property:\n",
    "\n",
    "offset\n",
    "~~~~~~\n",
    "\n",
    "The `offset` property controls the spacing between a text mark and its anchor position. It applies when *not* using `center` alignment (i.e., when using left/right or top/bottom). The value has point units. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25a49331-9580-4578-8bdb-d0d1829dde71",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "n = 17\n",
    "x = np.linspace(0, 8, n)\n",
    "y = np.full(n, .5)\n",
    "(\n",
    "    so.Plot(x=x, y=y, offset=x)\n",
    "    .add(so.Bar(color=\".6\", edgecolor=\"k\"))\n",
    "    .add(so.Text(text=\"abc\", valign=\"bottom\"))\n",
    "    .scale(\n",
    "        x=so.Continuous().tick(every=1, minor=1),\n",
    "        y=so.Continuous().tick(at=[]),\n",
    "        offset=None,\n",
    "    )\n",
    "    .limit(y=(0, 1.5))\n",
    "    .layout(size=(9, .5), engine=None)\n",
    "    .theme({\n",
    "        **axes_style(\"ticks\"),\n",
    "        **{f\"axes.spines.{side}\": False for side in [\"left\", \"right\", \"top\"]},\n",
    "        \"axes.xmargin\": .015,\n",
    "        \"xtick.labelsize\": 12,\n",
    "        \"ytick.labelsize\": 12,\n",
    "    })\n",
    "    .plot()\n",
    ")"
   ]
  },
  {
   "cell_type": "raw",
   "id": "77723ffd-2da3-4ece-a97a-3c00e864c743",
   "metadata": {},
   "source": [
    "Other properties\n",
    "----------------"
   ]
  },
  {
   "cell_type": "raw",
   "id": "287bb259-0194-4c8c-8836-5e3eb6d88e79",
   "metadata": {},
   "source": [
    ".. _property_property:\n",
    "\n",
    "text\n",
    "~~~~\n",
    "\n",
    "The `text` property is used to set the content of a textual mark. It is always used literally (not mapped), and cast to string when necessary.\n",
    "\n",
    "group\n",
    "~~~~~\n",
    "\n",
    "The `group` property is special in that it does not change anything about the mark's appearance but defines additional data subsets that transforms should operate on independently."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f23c9251-1685-4150-b5c2-ab5b0589d8e6",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py310",
   "language": "python",
   "name": "py310"
  },
  "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.10.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
