{ "cells": [ { "cell_type": "markdown", "id": "ffaf3148-442e-4879-87a4-0253b5c186f2", "metadata": {}, "source": [ "# Drawing Graph Edges Using `geom_segment()` and `geom_curve()`\n", "\n", "Beyond merely connecting two points on a chart, the `geom_segment()` and `geom_curve()` geometries
\n", "can, with some fine-tuning, help to visualize graph-like data.\n", "\n", "Use aesthetics `size_start/end` and `stroke_start/end` to allow `segment/curve`
\n", "to take into account the size of the point from which it starts/ends and to avoid drawing over it.\n", "\n", "Utilize the `spacer` parameter for further manual fine-tuning." ] }, { "cell_type": "code", "execution_count": 1, "id": "b4297632-5b34-42bd-9472-1ccff49ba27e", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from lets_plot import *" ] }, { "cell_type": "code", "execution_count": 2, "id": "9895b4ed-7d29-41ab-b051-27c82629f81c", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "LetsPlot.setup_html()" ] }, { "cell_type": "code", "execution_count": 3, "id": "a942c867-0036-4483-a355-5a9d982bc459", "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", "vertices_data = {\n", " 'x': x,\n", " 'y': y,\n", " 'shape': shape,\n", " 'size': size,\n", " 'stroke': stroke,\n", "}" ] }, { "cell_type": "code", "execution_count": 4, "id": "730b5140-0ed3-4296-8be3-d6eb016e8c13", "metadata": {}, "outputs": [], "source": [ "vertices_layer = geom_point(aes('x', 'y', size='size', shape='shape', stroke='stroke'),\n", " color='#4575b4', fill='#abd9e9')\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "a08e6134-6d72-4863-9bde-2d03af0cea25", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "graph_vertices = (\n", " ggplot(vertices_data) + vertices_layer\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", "graph_vertices" ] }, { "cell_type": "code", "execution_count": 6, "id": "676b3585-c724-4559-b6af-7adb76815406", "metadata": {}, "outputs": [], "source": [ "edges_data = {\n", " 'x_end': x[1:] + [x[0]],\n", " 'y_end': y[1:] + [y[0]]\n", "}\n" ] }, { "cell_type": "markdown", "id": "e8b14459-16c8-46ce-8f7a-bd757c7708d4", "metadata": {}, "source": [ "#### 1. Draw Ugly Graph Edges" ] }, { "cell_type": "code", "execution_count": 7, "id": "77392ba0-5384-4f00-8666-4977b3192e1f", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ugly_edges = geom_segment(aes('x', 'y', xend='x_end', yend='y_end'),\n", " data=edges_data, \n", " arrow=arrow(ends='both')) \n", "\n", "graph_vertices + ugly_edges" ] }, { "cell_type": "markdown", "id": "b5580564-4c4a-459c-8c84-ca999e1a5d7c", "metadata": {}, "source": [ "#### 2. Draw Nice Graph Edges" ] }, { "cell_type": "code", "execution_count": 8, "id": "99ed22b4-d065-45ca-a16a-88baac4cf99f", "metadata": {}, "outputs": [], "source": [ "# Uppend an info on the sizes of vertices in the graph.\n", "\n", "edges_data_2 = dict(edges_data)\n", "edges_data_2.update({\n", " 'size_end': size[1:] + [size[0]],\n", " 'stroke_end': stroke[1:] + [stroke[0]]\n", "})\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "dfd72fa4-4ac7-4428-b180-8f5db9927e54", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Use `segment` and then `curve` to draw \"nice\" edges.\n", "\n", "nice_edges_S = geom_segment(\n", " aes('x', 'y', xend='x_end', yend='y_end', \n", " size_start='size', size_end='size_end', # New! Take into account sizes of points connected by the edge.\n", " stroke_start='stroke', stroke_end='stroke_end'), # New! Take into account stroke (width) of points connected by the edge.\n", " spacer=5, # New! Add a \"spacer\".\n", " data=edges_data_2, \n", " arrow=arrow(ends='both')) \n", "\n", "nice_edges_C = geom_curve(\n", " aes('x', 'y', xend='x_end', yend='y_end', \n", " size_start='size', size_end='size_end',\n", " stroke_start='stroke', stroke_end='stroke_end'),\n", " spacer=5, \n", " data=edges_data_2, \n", " curvature=-0.3,\n", " arrow=arrow(ends='both')) \n", "\n", "gggrid([\n", " graph_vertices + nice_edges_S,\n", " graph_vertices + nice_edges_C\n", " ])\n" ] }, { "cell_type": "markdown", "id": "19b82ced-dc47-41e1-b620-e93e57ab6835", "metadata": {}, "source": [ "#### 3. Another Example of Graph Visualization" ] }, { "cell_type": "code", "execution_count": 10, "id": "5c78764d-6d49-4ea1-a8e5-8322a7cd5e60", "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": 10, "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": 11, "id": "095db2ee-4972-4328-a155-f210ec8d3b21", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ggplot(nodes, aes(x='x', y='y')) \\\n", " + geom_segment(aes(x='x', y='y', xend='x_to', yend='y_to',\n", " color='relation'), data=edges, \n", " size_end=25,\n", " 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()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.8.18" } }, "nbformat": 4, "nbformat_minor": 5 }