{
"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",
" from | \n",
" to | \n",
" relation | \n",
" node | \n",
" x | \n",
" y | \n",
" node_to | \n",
" x_to | \n",
" y_to | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Animals | \n",
" Living\\nThings | \n",
" is | \n",
" Animals | \n",
" -1 | \n",
" 0 | \n",
" Living\\nThings | \n",
" 0 | \n",
" 1 | \n",
"
\n",
" \n",
" | 1 | \n",
" Plants | \n",
" Living\\nThings | \n",
" is | \n",
" Plants | \n",
" 1 | \n",
" 0 | \n",
" Living\\nThings | \n",
" 0 | \n",
" 1 | \n",
"
\n",
" \n",
" | 2 | \n",
" Dogs | \n",
" Animals | \n",
" is | \n",
" Dogs | \n",
" -2 | \n",
" -1 | \n",
" Animals | \n",
" -1 | \n",
" 0 | \n",
"
\n",
" \n",
" | 3 | \n",
" Cows | \n",
" Animals | \n",
" is | \n",
" Cows | \n",
" 0 | \n",
" -1 | \n",
" Animals | \n",
" -1 | \n",
" 0 | \n",
"
\n",
" \n",
" | 4 | \n",
" Cows | \n",
" Herbs | \n",
" eat | \n",
" Cows | \n",
" 0 | \n",
" -1 | \n",
" Herbs | \n",
" 2 | \n",
" -1 | \n",
"
\n",
" \n",
" | 5 | \n",
" Herbs | \n",
" Plants | \n",
" is | \n",
" Herbs | \n",
" 2 | \n",
" -1 | \n",
" Plants | \n",
" 1 | \n",
" 0 | \n",
"
\n",
" \n",
"
\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",
" name | \n",
" latitude | \n",
" longitude | \n",
" years | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Erzurum | \n",
" 39.908610 | \n",
" 41.276940 | \n",
" 1829 | \n",
"
\n",
" \n",
" | 1 | \n",
" Boldino | \n",
" 55.004477 | \n",
" 45.308850 | \n",
" 1830, 1833 | \n",
"
\n",
" \n",
" | 2 | \n",
" Vitebsk | \n",
" 55.187222 | \n",
" 30.205116 | \n",
" 1820, 1824 | \n",
"
\n",
" \n",
"
\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
}