geom_curve()
¶Arguments:
curvature
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.
angle
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.
ncp
The number of control points used to draw the curve. More control points produce a smoother curve. Default = 5.
import pandas as pd
from lets_plot import *
LetsPlot.setup_html()
LetsPlot.set_theme(theme_grey())
DEF_ANGLE = 90.0
DEF_CURVATURE = 0.5
DEF_NCP = 5
def curve_plot(curvature=DEF_CURVATURE, angle=DEF_ANGLE, ncp=DEF_NCP):
return ggplot() \
+ geom_point(aes('x', 'y'), data = { 'x': [-30, 30], 'y': [-10,10] }) \
+ geom_curve(x = -20, y = 1, xend = 10, yend = -1,
curvature = curvature, angle = angle, ncp = ncp,
arrow=arrow(ends='both')) \
+ ggtitle("curvature={0}, angle={1}, ncp={2}".format(curvature, angle, ncp))
def curves(curvature=DEF_CURVATURE, angle=DEF_ANGLE, ncp=DEF_NCP):
return gggrid([
curve_plot(curvature=curvature, angle=angle, ncp=ncp),
curve_plot(curvature=curvature, angle=-angle, ncp=ncp),
curve_plot(curvature=-curvature, angle=angle, ncp=ncp),
curve_plot(curvature=-curvature, angle=-angle, ncp=ncp)
], ncol = 2)
def curves_flipped(curvature=DEF_CURVATURE, angle=DEF_ANGLE, ncp=DEF_NCP):
return gggrid([
curve_plot(curvature=curvature, angle=angle, ncp=ncp) + coord_flip(),
curve_plot(curvature=curvature, angle=-angle, ncp=ncp)+ coord_flip(),
curve_plot(curvature=-curvature, angle=angle, ncp=ncp)+ coord_flip(),
curve_plot(curvature=-curvature, angle=-angle, ncp=ncp)+ coord_flip()
], ncol = 2)
# straight line
curve_plot(curvature=0)
# straight line
gggrid([
curve_plot(angle=0),
curve_plot(angle=180)
])
gggrid([
curve_plot(curvature=0.5, ncp=1),
curve_plot(curvature=-0.5, ncp=1),
curve_plot(curvature=0.5),
curve_plot(curvature=-0.5)
], ncol=2)
gggrid([
curve_plot(curvature=1, ncp=1),
curve_plot(curvature=-1, ncp=1),
curve_plot(curvature=1),
curve_plot(curvature=-1)
], ncol=2)
# angle: negative
# -30 == 150
# -240 == 120
gggrid([
curve_plot(angle=-30), curve_plot(angle=150),
curve_plot(angle=-240), curve_plot(angle=120),
], ncol=2)
# 45 == -315 == 405
gggrid([
curve_plot(angle=45),
curve_plot(angle=405),
curve_plot(angle=-315)
])
curves(curvature=0.9, angle=45)
curves_flipped(curvature=0.9, angle=45)
curves(curvature=1.3, angle=135)
size_start,start_end
and stroke_start,stroke_end
When a curve is used in combination with a point layer, objects may overlap.
To avoid this, use the aesthetics to offset the start and end coordinates according to the size of points:
size_start, size_end
and stroke_start,stroke_end
.
There is also an optional spacer
parameter that defines a value in pixels to shorten the curve by moving the start/end.
x = [-1, 0, 1]
y = [-1, 1, -1]
shape = [1, 16, 21]
size = [1, 2, 3]
stroke = [1, 0, 2]
x_end = x[1:] + [x[0]]
y_end = y[1:] + [y[0]]
data = {
'x': x,
'y': y,
'shape': shape,
'size': size,
'stroke': stroke,
'x_end': x_end,
'y_end': y_end
}
Default plot:
plot = ggplot(data, aes('x','y')) \
+ geom_point(aes(size='size', shape='shape', stroke='stroke'), color='#4575b4', fill='#abd9e9') \
+ scale_size(range=[20,30], guide='none') \
+ scale_stroke(range=[0,10], guide='none') \
+ scale_shape_identity() \
+ lims(x=[-1.5, 1.5], y=[-1.5, 1.5])
plot + geom_curve(aes(xend='x_end', yend='y_end'),
curvature=-0.2,
arrow=arrow(ends='both'))
Let's define new aesthetics:
size_end = size[1:] + [size[0]]
stroke_end = stroke[1:] + [stroke[0]]
data.update({
'size_end': size_end,
'stroke_end': stroke_end,
})
plot + geom_curve(aes(xend='x_end', yend='y_end',
size_start='size', size_end='size_end',
stroke_start='stroke', stroke_end='stroke_end'),
curvature=-0.2,
arrow=arrow(ends='both'))
Additional padding:
plot + geom_curve(aes(xend='x_end', yend='y_end',
size_start='size', size_end='size_end',
stroke_start='stroke', stroke_end='stroke_end'),
curvature=-0.2,
arrow=arrow(ends='both'),
spacer=5)
# More examples
def curve(curvature, angle):
return ggplot() + \
geom_point(x=0, y=0, size=12, alpha=0.5, color="red") + \
geom_curve(x=0, y=-0.5, xend=0, yend=0,
size=1,
size_end=12,
curvature=curvature, angle=angle,
arrow=arrow(ends='last')) + \
ylim(-0.5, 0.1)
(gggrid([
curve(0.5, 90),curve(-0.5, 30),
curve(1.2,-20),curve(-1.2, 80),
curve(0.9,-20),curve(-0.9, 5)],
ncol=2))
nodes = pd.DataFrame({
'node': ["Living\nThings", "Animals", "Plants", "Dogs", "Cows", "Herbs"],
'x': [0, -1, 1, -2, 0, 2],
'y': [1, 0, 0, -1, -1, -1]
})
edges = pd.DataFrame({
'from': ["Animals", "Plants", "Dogs", "Cows", "Cows", "Herbs"],
'to': ["Living\nThings", "Living\nThings", "Animals", "Animals", "Herbs", "Plants"],
'relation': ["is", "is", "is", "is", "eat", "is"]
})
edges = pd.merge(edges, nodes, left_on='from', right_on='node')
edges = pd.merge(edges, nodes, left_on='to', right_on='node', suffixes=('', '_to'))
edges
from | to | relation | node | x | y | node_to | x_to | y_to | |
---|---|---|---|---|---|---|---|---|---|
0 | Animals | Living\nThings | is | Animals | -1 | 0 | Living\nThings | 0 | 1 |
1 | Plants | Living\nThings | is | Plants | 1 | 0 | Living\nThings | 0 | 1 |
2 | Dogs | Animals | is | Dogs | -2 | -1 | Animals | -1 | 0 |
3 | Cows | Animals | is | Cows | 0 | -1 | Animals | -1 | 0 |
4 | Cows | Herbs | eat | Cows | 0 | -1 | Herbs | 2 | -1 |
5 | Herbs | Plants | is | Herbs | 2 | -1 | Plants | 1 | 0 |
ggplot(nodes, aes(x='x', y='y')) \
+ geom_curve(aes(x='x', y='y', xend='x_to', yend='y_to', color='relation'), data=edges,
size_end=25,
curvature=0.2, arrow=arrow()) \
+ geom_point(color='#2166ac', fill='#d1e5f0', shape=21, size=25) \
+ scale_color_manual(['#2166ac', '#d6604d']) \
+ geom_text(aes(label='node')) \
+ coord_cartesian([-3,3],[-1.5,1.5]) \
+ theme_void()
def set_stay_time(data):
current_moves = moves_df[(moves_df.departure == data['name'])|(moves_df.arrival == data['name'])]
years = current_moves.year.sort_values().unique()
years_combined = []
for year in years:
if len(years_combined) == 0 or years_combined[-1][1] + 1 != year:
years_combined.append((year, year))
else:
years_combined[-1] = (years_combined[-1][0], year)
years_combined = [str(year_from) if year_from == year_to else '{0}-{1}'.format(year_from, year_to) \
for year_from, year_to in years_combined]
data['years'] = ', '.join(years_combined)
return data
places_df = pd.read_csv('https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/pushkin/places.csv')
moves_df = pd.read_csv('https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/pushkin/moves.csv')
df = moves_df.merge(places_df, left_on='departure', right_on='name')\
.rename(columns=dict(longitude='from_lon', latitude='from_lat'))\
.drop(columns=['name'])\
.merge(places_df, left_on='arrival', right_on='name')\
.rename(columns=dict(longitude='to_lon', latitude='to_lat'))\
.drop(columns=['name'])
places_df['years'] = ''
places_df = places_df.apply(set_stay_time, axis=1)
places_df.head(3)
name | latitude | longitude | years | |
---|---|---|---|---|
0 | Erzurum | 39.908610 | 41.276940 | 1829 |
1 | Boldino | 55.004477 | 45.308850 | 1830, 1833 |
2 | Vitebsk | 55.187222 | 30.205116 | 1820, 1824 |
ggplot() + \
geom_livemap(const_size_zoomin=3) + \
geom_curve(aes(x='from_lon', y='from_lat', xend='to_lon', yend='to_lat', color='path'), data=df,
size_start=3, size_end=3,
tooltips=layer_tooltips().title("Trip").line("@path"),
curvature=0.2,
arrow=arrow(type='open', length=4), size=0.5) + \
geom_point(aes(x='longitude', y='latitude'), data=places_df, size=3, color='light_grey',
tooltips=layer_tooltips().title('@name').line('visited in|@years')) + \
geom_point(aes(x='longitude', y='latitude'), data=places_df, size=1.7, color='black',
tooltips='none') + \
scale_color_manual(name='trip name', values=['#addd8e', '#e34a33', '#8856a7', '#2c7fb8',
'#1c9099', '#006d2c', '#fec44f', '#636363']) + \
coord_cartesian(xlim=[26, 58], ylim=[38, 62]) + \
ggtitle("Alexander Pushkin's Trips") + \
ggsize(800, 600) + \
theme(axis_title='blank', axis_text='blank', axis_ticks='blank', axis_line='blank')
msk = places_df.loc[places_df['name'] == 'Moscow'].iloc[0]
pointSize = 2
def fromPoint(onMap: bool):
p = ggplot(places_df, aes(x='longitude', y='latitude'))
if (onMap):
p += geom_livemap()
p += geom_point(size=pointSize, color='red')
p += geom_curve(xend=msk['longitude'], yend=msk['latitude'],
size=0.2,
size_start=pointSize, size_end=pointSize)
return p
fromPoint(True)
fromPoint(False)