{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "considerable-start", "metadata": {}, "outputs": [], "source": [ "from lets_plot import *" ] }, { "cell_type": "code", "execution_count": 2, "id": "grateful-williams", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "LetsPlot.setup_html()" ] }, { "cell_type": "code", "execution_count": 3, "id": "color-smith", "metadata": {}, "outputs": [], "source": [ "LetsPlot.set_theme(theme_minimal())" ] }, { "cell_type": "markdown", "id": "breeding-sheet", "metadata": {}, "source": [ "#### Draw arrows\n", "\n", "- the arrowhead always points to the specified coordinate of the segment\n", "- the segment itself is drawn so that it does not intersect with its arrow\n", "- no beveled corners (previously, acute angle could be drawn beveled)" ] }, { "cell_type": "code", "execution_count": 4, "id": "amateur-security", "metadata": {}, "outputs": [], "source": [ "def plot_with_arrows(angle, type='open'):\n", " return ggplot() \\\n", " + geom_vline(xintercept=0) \\\n", " + geom_vline(xintercept=1) \\\n", " + geom_segment(x=0, xend=1, y=0, yend=0,\n", " size=5,\n", " alpha=0.5,\n", " arrow=arrow(ends='both', type=type, length=75, angle=angle)) \\\n", " + ggsize(600, 350)" ] }, { "cell_type": "code", "execution_count": 5, "id": "demonstrated-template", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "plot_with_arrows(10)" ] }, { "cell_type": "code", "execution_count": 6, "id": "cognitive-partition", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "plot_with_arrows(30)" ] }, { "cell_type": "markdown", "id": "integral-migration", "metadata": {}, "source": [ "#### 'space' parameter - additional space to shorten a segment by moving the start/end" ] }, { "cell_type": "code", "execution_count": 7, "id": "blocked-trader", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ggplot() \\\n", " + geom_vline(xintercept=0) \\\n", " + geom_vline(xintercept=1) \\\n", " + geom_segment(x=0, xend=1, y=0, yend=0,\n", " size=5, \n", " alpha=0.5,\n", " arrow=arrow(ends='both', type='open', length=75, angle=15),\n", " spacer=15) \\\n", " + ggsize(600, 200)" ] }, { "cell_type": "markdown", "id": "unlikely-corruption", "metadata": {}, "source": [ "#### New aesthetics: `size_start,start_end` and `stroke_start,stroke_end`" ] }, { "cell_type": "code", "execution_count": 8, "id": "hollow-activity", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "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", "size_end = size[1:] + [size[0]]\n", "stroke_end = stroke[1:] + [stroke[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", " 'size_end': size_end,\n", " 'stroke_end': stroke_end,\n", "}\n", "\n", "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", "plot" ] }, { "cell_type": "code", "execution_count": 9, "id": "disturbed-survival", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Draw segments using offsets from start/end using aesthetics\n", "\n", "# Also need to set the same scale settings:\n", "se_scales = scale_continuous(['size_start','size_end'], range=[20,30], guide='none') \\\n", " + scale_continuous(['stroke_start','stroke_end'], range=[0,10], guide='none')\n", "\n", "plot \\\n", " + geom_segment(aes(xend='x_end', yend='y_end', \n", " size_start='size', size_end='size_end',\n", " stroke_start='stroke', stroke_end='stroke_end'),\n", " arrow=arrow(type='open', ends='both', length=15, angle=15)) \\\n", " + se_scales" ] }, { "cell_type": "code", "execution_count": 10, "id": "cooked-celebrity", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# For start coordfinate only\n", "\n", "plot \\\n", " + geom_segment(aes(xend='x_end', yend='y_end', \n", " size_end='size_end', stroke_end='stroke_end'),\n", " arrow=arrow(type='open', ends='last', length=15, angle=15)) \\\n", " + se_scales" ] }, { "cell_type": "code", "execution_count": 11, "id": "engaging-effects", "metadata": {}, "outputs": [], "source": [ "# Example from https://stackoverflow.com/questions/14647988/arranging-arrows-between-points-nicely-in-ggplot2\n", "\n", "import pandas as pd\n", "import numpy as np\n", "\n", "np.random.seed(7)\n", "\n", "points = pd.DataFrame({\n", " 'x': np.random.uniform(size=10),\n", " 'y': np.random.uniform(size=10),\n", " 'class': np.arange(1, 11),\n", " 'size': np.random.uniform(1000, 100000, size=10)\n", "})\n", "\n", "trans = pd.DataFrame({\n", " 'from': np.repeat(np.arange(1, 11), 10),\n", " 'to': np.tile(np.arange(1, 11), 10),\n", " 'amount': np.random.uniform(size=100) ** 3\n", "})\n", "\n", "trans = pd.merge(trans, points, left_on='from', right_on='class')\n", "trans = pd.merge(trans, points, left_on='to', right_on='class', suffixes=('.to', '.from'))\n", "\n", "trans = trans.loc[(trans['amount'] >= 0.6)]" ] }, { "cell_type": "code", "execution_count": 12, "id": "becoming-middle", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ggplot(points, aes(x='x', y='y')) \\\n", " + geom_point(aes(size='size'), color=\"red\", shape=1, stroke=0.75) \\\n", " + geom_segment(aes(x='x.from', y='y.from', xend='x.to', yend='y.to', \n", " size_start='size.from', size_end='size.to'), \n", " data=trans,\n", " stroke_start=0.75, stroke_end=0.75,\n", " alpha=0.7, \n", " arrow=arrow(type='open', ends='both')) \\\n", " + scale_continuous(['size','size_start','size_end'], range=[4,20])" ] }, { "cell_type": "code", "execution_count": 13, "id": "imported-sequence", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# From doc\n", "\n", "import numpy as np\n", "\n", "T = 25\n", "np.random.seed(42)\n", "t = [0, *np.random.normal(size=T)]\n", "x = np.cumsum(np.cos(t))\n", "y = np.cumsum(np.sin(t))\n", "\n", "ggplot({'x': x[:-1], 'y': y[:-1], 'xend': x[1:], 'yend': y[1:]}, aes(x='x', y='y')) \\\n", " + geom_segment(aes(xend='xend', yend='yend', color='xend'),\n", " arrow=arrow(type='closed', angle=10)) \\\n", " + scale_color_gradient(low='#2c7bb6', high='#d7191c') \\\n", " + coord_fixed()" ] }, { "cell_type": "code", "execution_count": 14, "id": "measured-franchise", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# on LiveMap\n", "\n", "# use constants\n", "\n", "ggplot(data, aes('x','y')) \\\n", " + geom_livemap() \\\n", " + geom_point(size=20, color='#4575b4', alpha=0.5) \\\n", " + geom_segment(aes(xend='x_end', yend='y_end'),\n", " size_start=20, size_end=20,\n", " size=4,\n", " arrow=arrow(type='closed', ends='last', angle=15))" ] }, { "cell_type": "code", "execution_count": 15, "id": "sharp-civilian", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Mappings\n", "\n", "ggplot(data, aes('x', 'y')) \\\n", " + geom_livemap() \\\n", " + geom_point(aes(size='size', shape='shape', stroke='stroke'),data=data, color='#4575b4', fill='#abd9e9') \\\n", " + geom_segment(aes(xend='x_end', yend='y_end', \n", " size_start='size', size_end='size_end',\n", " stroke_start='stroke', stroke_end='stroke_end'),\n", " size=4,\n", " arrow=arrow(type='closed', ends='last', angle=15)) \\\n", " + scale_shape_identity() \\\n", " + scale_continuous(['size', 'size_start','size_end'], range=[20,30], guide='none') \\\n", " + scale_continuous(['stroke', 'stroke_start','stroke_end'], range=[0,10], guide='none')" ] }, { "cell_type": "markdown", "id": "decimal-alberta", "metadata": {}, "source": [ "#### From 'Pushkin as Travller' notebook" ] }, { "cell_type": "code", "execution_count": 16, "id": "original-documentary", "metadata": {}, "outputs": [], "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)" ] }, { "cell_type": "code", "execution_count": 17, "id": "wound-cable", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ggplot() + \\\n", " geom_livemap(const_size_zoomin=3) + \\\n", " geom_segment(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", " arrow=arrow(type='closed', length=10, angle=15), size=1) + \\\n", " geom_point(aes(x='longitude', y='latitude'), data=places_df, size=3, color='#F7F4F0',\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": 18, "id": "swedish-guard", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mX = 37.618423\n", "mY = 55.751244\n", "pointSize = 2\n", "\n", "ggplot(places_df, aes(x='longitude', y='latitude')) + \\\n", " geom_livemap() + \\\n", " geom_point(size=pointSize, color='red') + \\\n", " geom_segment(xend=mX, yend=mY,\n", " size=0.2,\n", " size_start=pointSize, size_end=pointSize)" ] } ], "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 }