{ "cells": [ { "cell_type": "markdown", "id": "earlier-discipline", "metadata": {}, "source": [ "## `geom_curve()`\n", "\n", "Arguments:\n", "\n", "* `curvature`\n", "A numeric value that indicates the amount of curvature. Negative values produce left-hand curves, positive values produce right-hand curves and zero produces a straight line. Default = 0.5.\n", "\n", "* `angle`\n", "A numeric value between 0 and 180 that indicates the amount by which the control points of the curve should be skewed. Values less than 90 skew the curve towards the start point and values greater than 90 skew the curve towards the end point. Default = 90.\n", "\n", "* `ncp` \n", "The number of control points used to draw the curve. More control points produce a smoother curve. Default = 5." ] }, { "cell_type": "code", "execution_count": 1, "id": "educational-night", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from lets_plot import *" ] }, { "cell_type": "code", "execution_count": 2, "id": "dutch-swiss", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "LetsPlot.setup_html()" ] }, { "cell_type": "code", "execution_count": 3, "id": "nearby-violin", "metadata": {}, "outputs": [], "source": [ "LetsPlot.set_theme(theme_grey())" ] }, { "cell_type": "code", "execution_count": 4, "id": "urban-arlington", "metadata": {}, "outputs": [], "source": [ "DEF_ANGLE = 90.0\n", "DEF_CURVATURE = 0.5\n", "DEF_NCP = 5\n", "\n", "def curve_plot(curvature=DEF_CURVATURE, angle=DEF_ANGLE, ncp=DEF_NCP):\n", " return ggplot() \\\n", " + geom_point(aes('x', 'y'), data = { 'x': [-30, 30], 'y': [-10,10] }) \\\n", " + geom_curve(x = -20, y = 1, xend = 10, yend = -1, \n", " curvature = curvature, angle = angle, ncp = ncp,\n", " arrow=arrow(ends='both')) \\\n", " + ggtitle(\"curvature={0}, angle={1}, ncp={2}\".format(curvature, angle, ncp))\n", "\n", "def curves(curvature=DEF_CURVATURE, angle=DEF_ANGLE, ncp=DEF_NCP):\n", " return gggrid([\n", " curve_plot(curvature=curvature, angle=angle, ncp=ncp),\n", " curve_plot(curvature=curvature, angle=-angle, ncp=ncp),\n", " curve_plot(curvature=-curvature, angle=angle, ncp=ncp),\n", " curve_plot(curvature=-curvature, angle=-angle, ncp=ncp)\n", " ], ncol = 2)\n", "\n", "\n", "def curves_flipped(curvature=DEF_CURVATURE, angle=DEF_ANGLE, ncp=DEF_NCP):\n", " return gggrid([\n", " curve_plot(curvature=curvature, angle=angle, ncp=ncp) + coord_flip(),\n", " curve_plot(curvature=curvature, angle=-angle, ncp=ncp)+ coord_flip(),\n", " curve_plot(curvature=-curvature, angle=angle, ncp=ncp)+ coord_flip(),\n", " curve_plot(curvature=-curvature, angle=-angle, ncp=ncp)+ coord_flip()\n", " ], ncol = 2)" ] }, { "cell_type": "code", "execution_count": 5, "id": "spanish-blond", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# straight line\n", "\n", "curve_plot(curvature=0)" ] }, { "cell_type": "code", "execution_count": 6, "id": "wanted-outline", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# straight line\n", "\n", "gggrid([\n", " curve_plot(angle=0),\n", " curve_plot(angle=180)\n", "])" ] }, { "cell_type": "code", "execution_count": 7, "id": "described-premises", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gggrid([\n", " curve_plot(curvature=0.5, ncp=1),\n", " curve_plot(curvature=-0.5, ncp=1),\n", " curve_plot(curvature=0.5),\n", " curve_plot(curvature=-0.5) \n", "], ncol=2)" ] }, { "cell_type": "code", "execution_count": 8, "id": "level-citizen", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gggrid([\n", " curve_plot(curvature=1, ncp=1),\n", " curve_plot(curvature=-1, ncp=1),\n", " curve_plot(curvature=1),\n", " curve_plot(curvature=-1)\n", "], ncol=2)" ] }, { "cell_type": "code", "execution_count": null, "id": "emerging-rogers", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 9, "id": "single-china", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# angle: negative\n", "# -30 == 150\n", "# -240 == 120\n", "\n", "gggrid([\n", " curve_plot(angle=-30), curve_plot(angle=150),\n", " curve_plot(angle=-240), curve_plot(angle=120),\n", "], ncol=2)" ] }, { "cell_type": "code", "execution_count": 10, "id": "decimal-screen", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 45 == -315 == 405\n", "gggrid([\n", " curve_plot(angle=45),\n", " curve_plot(angle=405),\n", " curve_plot(angle=-315)\n", "])" ] }, { "cell_type": "code", "execution_count": 11, "id": "plastic-warehouse", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "curves(curvature=0.9, angle=45)" ] }, { "cell_type": "code", "execution_count": 12, "id": "metropolitan-pilot", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "curves_flipped(curvature=0.9, angle=45)" ] }, { "cell_type": "code", "execution_count": 13, "id": "major-synthetic", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "curves(curvature=1.3, angle=135)" ] }, { "cell_type": "markdown", "id": "posted-spare", "metadata": {}, "source": [ "#### Aesthetics to Adjust Start/End\n", "`size_start,start_end` and `stroke_start,stroke_end`\n", "\n", "\n", "When a curve is used in combination with a point layer, objects may overlap.\n", "To avoid this, use the aesthetics to offset the start and end coordinates according to the size of points:\n", "`size_start, size_end` and `stroke_start,stroke_end`.\n", "\n", "There is also an optional `spacer` parameter that defines a value in pixels to shorten the curve by moving the start/end." ] }, { "cell_type": "code", "execution_count": 14, "id": "afraid-dividend", "metadata": {}, "outputs": [], "source": [ "x = [-1, 0, 1]\n", "y = [-1, 1, -1]\n", "shape = [1, 16, 21]\n", "size = [1, 2, 3]\n", "stroke = [1, 0, 2]\n", "\n", "x_end = x[1:] + [x[0]]\n", "y_end = y[1:] + [y[0]]\n", "\n", "data = {\n", " 'x': x,\n", " 'y': y,\n", " 'shape': shape,\n", " 'size': size,\n", " 'stroke': stroke,\n", " 'x_end': x_end,\n", " 'y_end': y_end\n", "}" ] }, { "cell_type": "markdown", "id": "illegal-instrumentation", "metadata": {}, "source": [ "Default plot:" ] }, { "cell_type": "code", "execution_count": 15, "id": "strong-wallet", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "plot = ggplot(data, aes('x','y')) \\\n", " + geom_point(aes(size='size', shape='shape', stroke='stroke'), color='#4575b4', fill='#abd9e9') \\\n", " + scale_size(range=[20,30], guide='none') \\\n", " + scale_stroke(range=[0,10], guide='none') \\\n", " + scale_shape_identity() \\\n", " + lims(x=[-1.5, 1.5], y=[-1.5, 1.5])\n", "\n", "\n", "plot + geom_curve(aes(xend='x_end', yend='y_end'),\n", " curvature=-0.2,\n", " arrow=arrow(ends='both')) " ] }, { "cell_type": "markdown", "id": "allied-circumstances", "metadata": {}, "source": [ "Let's define new aesthetics:" ] }, { "cell_type": "code", "execution_count": 16, "id": "assured-kingdom", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "size_end = size[1:] + [size[0]]\n", "stroke_end = stroke[1:] + [stroke[0]]\n", "\n", "data.update({\n", " 'size_end': size_end,\n", " 'stroke_end': stroke_end,\n", "})\n", "\n", "plot + geom_curve(aes(xend='x_end', yend='y_end', \n", " size_start='size', size_end='size_end',\n", " stroke_start='stroke', stroke_end='stroke_end'),\n", " curvature=-0.2,\n", " arrow=arrow(ends='both'))" ] }, { "cell_type": "markdown", "id": "forward-universal", "metadata": {}, "source": [ "Additional padding:" ] }, { "cell_type": "code", "execution_count": 17, "id": "designed-jaguar", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "plot + geom_curve(aes(xend='x_end', yend='y_end', \n", " size_start='size', size_end='size_end',\n", " stroke_start='stroke', stroke_end='stroke_end'),\n", " curvature=-0.2,\n", " arrow=arrow(ends='both'),\n", " spacer=5) " ] }, { "cell_type": "code", "execution_count": 18, "id": "republican-maximum", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# More examples\n", "\n", "def curve(curvature, angle):\n", " return ggplot() + \\\n", " geom_point(x=0, y=0, size=12, alpha=0.5, color=\"red\") + \\\n", " geom_curve(x=0, y=-0.5, xend=0, yend=0, \n", " size=1, \n", " size_end=12, \n", " curvature=curvature, angle=angle,\n", " arrow=arrow(ends='last')) + \\\n", " ylim(-0.5, 0.1)\n", "\n", "(gggrid([\n", " curve(0.5, 90),curve(-0.5, 30),\n", " curve(1.2,-20),curve(-1.2, 80),\n", " curve(0.9,-20),curve(-0.9, 5)],\n", " ncol=2)) " ] }, { "cell_type": "markdown", "id": "fleet-artwork", "metadata": {}, "source": [ "#### Simple Graph Visualization" ] }, { "cell_type": "code", "execution_count": 19, "id": "joint-darwin", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
fromtorelationnodexynode_tox_toy_to
0AnimalsLiving\\nThingsisAnimals-10Living\\nThings01
1PlantsLiving\\nThingsisPlants10Living\\nThings01
2DogsAnimalsisDogs-2-1Animals-10
3CowsAnimalsisCows0-1Animals-10
4CowsHerbseatCows0-1Herbs2-1
5HerbsPlantsisHerbs2-1Plants10
\n", "
" ], "text/plain": [ " from to relation node x y node_to x_to y_to\n", "0 Animals Living\\nThings is Animals -1 0 Living\\nThings 0 1\n", "1 Plants Living\\nThings is Plants 1 0 Living\\nThings 0 1\n", "2 Dogs Animals is Dogs -2 -1 Animals -1 0\n", "3 Cows Animals is Cows 0 -1 Animals -1 0\n", "4 Cows Herbs eat Cows 0 -1 Herbs 2 -1\n", "5 Herbs Plants is Herbs 2 -1 Plants 1 0" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nodes = pd.DataFrame({\n", " 'node': [\"Living\\nThings\", \"Animals\", \"Plants\", \"Dogs\", \"Cows\", \"Herbs\"],\n", " 'x': [0, -1, 1, -2, 0, 2],\n", " 'y': [1, 0, 0, -1, -1, -1]\n", "})\n", "\n", "edges = pd.DataFrame({\n", " 'from': [\"Animals\", \"Plants\", \"Dogs\", \"Cows\", \"Cows\", \"Herbs\"],\n", " 'to': [\"Living\\nThings\", \"Living\\nThings\", \"Animals\", \"Animals\", \"Herbs\", \"Plants\"],\n", " 'relation': [\"is\", \"is\", \"is\", \"is\", \"eat\", \"is\"]\n", "})\n", "edges = pd.merge(edges, nodes, left_on='from', right_on='node')\n", "edges = pd.merge(edges, nodes, left_on='to', right_on='node', suffixes=('', '_to'))\n", "edges" ] }, { "cell_type": "code", "execution_count": 20, "id": "sustainable-deadline", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ggplot(nodes, aes(x='x', y='y')) \\\n", " + geom_curve(aes(x='x', y='y', xend='x_to', yend='y_to', color='relation'), data=edges, \n", " size_end=25,\n", " curvature=0.2, arrow=arrow()) \\\n", " + geom_point(color='#2166ac', fill='#d1e5f0', shape=21, size=25) \\\n", " + scale_color_manual(['#2166ac', '#d6604d']) \\\n", " + geom_text(aes(label='node')) \\\n", " + coord_cartesian([-3,3],[-1.5,1.5]) \\\n", " + theme_void()" ] }, { "cell_type": "markdown", "id": "regulation-representation", "metadata": {}, "source": [ "#### On LiveMap" ] }, { "cell_type": "code", "execution_count": 21, "id": "million-roommate", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namelatitudelongitudeyears
0Erzurum39.90861041.2769401829
1Boldino55.00447745.3088501830, 1833
2Vitebsk55.18722230.2051161820, 1824
\n", "
" ], "text/plain": [ " name latitude longitude years\n", "0 Erzurum 39.908610 41.276940 1829\n", "1 Boldino 55.004477 45.308850 1830, 1833\n", "2 Vitebsk 55.187222 30.205116 1820, 1824" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def set_stay_time(data):\n", " current_moves = moves_df[(moves_df.departure == data['name'])|(moves_df.arrival == data['name'])]\n", " years = current_moves.year.sort_values().unique()\n", " years_combined = []\n", " for year in years:\n", " if len(years_combined) == 0 or years_combined[-1][1] + 1 != year:\n", " years_combined.append((year, year))\n", " else:\n", " years_combined[-1] = (years_combined[-1][0], year)\n", " years_combined = [str(year_from) if year_from == year_to else '{0}-{1}'.format(year_from, year_to) \\\n", " for year_from, year_to in years_combined]\n", " data['years'] = ', '.join(years_combined)\n", " return data\n", "\n", "places_df = pd.read_csv('https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/pushkin/places.csv')\n", "moves_df = pd.read_csv('https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/pushkin/moves.csv')\n", "df = moves_df.merge(places_df, left_on='departure', right_on='name')\\\n", " .rename(columns=dict(longitude='from_lon', latitude='from_lat'))\\\n", " .drop(columns=['name'])\\\n", " .merge(places_df, left_on='arrival', right_on='name')\\\n", " .rename(columns=dict(longitude='to_lon', latitude='to_lat'))\\\n", " .drop(columns=['name'])\n", "places_df['years'] = ''\n", "places_df = places_df.apply(set_stay_time, axis=1)\n", "places_df.head(3)" ] }, { "cell_type": "code", "execution_count": 22, "id": "turned-feature", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ggplot() + \\\n", " geom_livemap(const_size_zoomin=3) + \\\n", " geom_curve(aes(x='from_lon', y='from_lat', xend='to_lon', yend='to_lat', color='path'), data=df,\n", " size_start=3, size_end=3,\n", " tooltips=layer_tooltips().title(\"Trip\").line(\"@path\"),\n", " curvature=0.2,\n", " arrow=arrow(type='open', length=4), size=0.5) + \\\n", " geom_point(aes(x='longitude', y='latitude'), data=places_df, size=3, color='light_grey',\n", " tooltips=layer_tooltips().title('@name').line('visited in|@years')) + \\\n", " geom_point(aes(x='longitude', y='latitude'), data=places_df, size=1.7, color='black',\n", " tooltips='none') + \\\n", " scale_color_manual(name='trip name', values=['#addd8e', '#e34a33', '#8856a7', '#2c7fb8',\n", " '#1c9099', '#006d2c', '#fec44f', '#636363']) + \\\n", " coord_cartesian(xlim=[26, 58], ylim=[38, 62]) + \\\n", " ggtitle(\"Alexander Pushkin's Trips\") + \\\n", " ggsize(800, 600) + \\\n", " theme(axis_title='blank', axis_text='blank', axis_ticks='blank', axis_line='blank')" ] }, { "cell_type": "code", "execution_count": 23, "id": "million-alias", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "msk = places_df.loc[places_df['name'] == 'Moscow'].iloc[0]\n", "pointSize = 2\n", "\n", "def fromPoint(onMap: bool):\n", " p = ggplot(places_df, aes(x='longitude', y='latitude'))\n", " if (onMap):\n", " p += geom_livemap()\n", " p += geom_point(size=pointSize, color='red')\n", " p += geom_curve(xend=msk['longitude'], yend=msk['latitude'],\n", " size=0.2,\n", " size_start=pointSize, size_end=pointSize)\n", " return p\n", "\n", "fromPoint(True)" ] }, { "cell_type": "code", "execution_count": 24, "id": "baking-accent", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fromPoint(False)" ] } ], "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", "version": "3.7.10" } }, "nbformat": 4, "nbformat_minor": 5 }