{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# PART 1: The super basics" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import ipycytoscape\n", "import json\n", "import ipywidgets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What is a graph?\n", "Mathematical structures used to model pairwise relations between objects. \n", "Examples: \n", "- Twitter connections. \n", "- Rail net of a country\n", "- Post system of a country\n", "- Facebook connections.\n", "\n", "Nomenclature:\n", "The basic nomenclature consists of (based on an example of the train rail system):\n", "- nodes (rail stations) and \n", "- edges (rail connections between train stations)\n", "\n", "Let's use ipycytoscape to dive into graphs. \n", "\n", "One way to create an ipycytoscape graph is using a JSON input as follows:\n", "(We will be following the train-rail example)\n", "\n", "Later on it might become clear that other ways to pass data to ipycytoscape are not only possible but probably desirable in many circumstances. For the moment we intend to create a really small graph to get up and running understanding graphs and ipycytoscape. \n", "Moreover be aware that normally the data itself is in an external separate file, but if we would proceed reading the data from an external file we would not be able to see it in the notebook and it would not serve the teaching purpose.\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# we create the graph that is an object of ipycytoscape\n", "ipycytoscape_obj = ipycytoscape.CytoscapeWidget()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The data" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "railnet= '''{\n", " \"nodes\": [\n", " {\"data\": { \"id\": \"BER\" }},\n", " {\"data\": { \"id\": \"MUN\"}},\n", " {\"data\": { \"id\": \"FRA\"}},\n", " {\"data\": { \"id\": \"HAM\"}}\n", " ],\n", " \"edges\": [\n", " {\"data\": { \"source\": \"BER\", \"target\": \"MUN\" }},\n", " {\"data\": { \"source\": \"MUN\", \"target\": \"FRA\" }},\n", " {\"data\": { \"source\": \"FRA\", \"target\": \"BER\" }},\n", " {\"data\": { \"source\": \"BER\", \"target\": \"HAM\" }}\n", " \n", " ]\n", " }'''\n", "print(type(railnet))\n", "railnetJSON = json.loads(railnet)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see our mini German rail system that joins the three main German cities BERlin, MUNich and FRAnkfurt\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "ipycytoscape_obj.graph.add_graph_from_json(railnetJSON)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "5ad9f090231d40ec9eaf9a07a23bbe75", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ipycytoscape_obj" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some observations:\n", "- The train stations has a color (but we did not specified that)\n", "- Between the train stations there is a connection (edge) representing the rail. Think about this, it can be unidirectional (train goes only in one direction, or bidirectional, the connections are in both directions. This is what \"directionality\" stands for.\n", "- We dont know which station is which (no names)\n", " \n", "Lets try to solve those problems.\n", "\n", "IMPORTANT NOTE: Multiple graphs are being created so it's possible for you to compare the results." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Direcionality\n", "How would have been the JSON file if we dont want directionality?\n", "Compare the two graphs below, the first example is using directionality and the second isn't." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "aa751e6fc87d4754a1896e7d8387016a", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ipycytoscape_obj2 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj2.graph.add_graph_from_json(railnetJSON, directed=True) # I am telling I dont want directions\n", "ipycytoscape_obj2" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ae38046c18dd44c494f2e6c2a1c1fb50", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ipycytoscape_obj3 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj3.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions\n", "ipycytoscape_obj3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding names\n", "Lets say we want to see the names of the stations on top of the nodes.\n", "Those names are called labels.\n", "It's necessary to add the corresponding labels to all the nodes." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "scrolled": true }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d0965d888a3545b5935c341544f7fd56", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "railnet= '''{\n", " \"nodes\": [\n", " {\"data\": { \"id\": \"BER\", \"label\":\"HBf BER\"}},\n", " {\"data\": { \"id\": \"MUN\", \"label\":\"HBf MUN\"}},\n", " {\"data\": { \"id\": \"FRA\", \"label\":\"HBf FRA\"}},\n", " {\"data\": { \"id\": \"HAM\", \"label\":\"HBf HAM\"}}\n", " ],\n", " \"edges\": [\n", " {\"data\": { \"source\": \"BER\", \"target\": \"MUN\" }},\n", " {\"data\": { \"source\": \"MUN\", \"target\": \"FRA\" }},\n", " {\"data\": { \"source\": \"FRA\", \"target\": \"BER\" }},\n", " {\"data\": { \"source\": \"BER\", \"target\": \"HAM\" }}\n", " \n", " ]\n", " }'''\n", "\n", "railnetJSON = json.loads(railnet)\n", "ipycytoscape_obj4 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj4.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions\n", "ipycytoscape_obj4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "mmmmmm, as you can see we did not achieve our objective of adding the name of the stations. The stations being the nodes of the graph. Be aware that in all examples of this notebook stations and nodes might be used interchangeably \"node\" being the graph technical term and rail station his representation of it in real life. (btw, HBf states for central main station in German).\n", "In order to affect and change the appearance of the graph we not only have to change the graph's data but also its style.\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "my_style = [\n", " {'selector': 'node','style': {\n", " 'font-family': 'helvetica',\n", " 'font-size': '20px',\n", " 'label': 'data(label)'}},\n", " ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What are we doing here? \n", "We're writing the style of each one of the labels. With data(label) we specify that the property label of the attribute data of our graph should be printed with the font-size 20 and family helvetica. \n", "Lets create a new graph with the labels \n", "Here we're using CSS nomenclature.\n", "You select one element and pass a style to that element. \n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9fabbdeba2ff4f4f8d9b6aaa54cb603d", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ipycytoscape_obj5 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj5.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions\n", "ipycytoscape_obj5.set_style(my_style)\n", "ipycytoscape_obj5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets just play around and change the size of the font and the type of font.\n", "Now we want to change the style of an existing graph, namely the number 5." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "2055e2f28a7f4b65b1b38ea05c78d148", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ipycytoscape_obj6 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj6.graph.add_graph_from_json(railnetJSON, directed=False) # We're specifying that the graph should be undirected\n", "ipycytoscape_obj6.set_style(my_style)\n", "ipycytoscape_obj6 # which is the same as graph 5, but in the next cell we will try to change it" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "2aecff536501486bb54f7c0a66df64d9", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "my_style = [\n", " {'selector': 'node','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',\n", " 'background-color': 'red'}},\n", " ]\n", "ipycytoscape_obj6 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj6.graph.add_graph_from_json(railnetJSON, directed=False) # We're specifying that the graph should be undirected\n", "ipycytoscape_obj6.set_style(my_style)\n", "ipycytoscape_obj6 # which is the same\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see when running the previous cell the appearance of the graph changed: the `font-family` is different and the `font-size` matches the node size. And the circles are now red.\n", "The first question that comes to mind is if one can change the attributes of only one node.\n", "Let's see." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d647ac9019cc41c7842796a70065a316", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "my_style = [\n", " {'selector': 'node','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',\n", " 'background-color': 'red'}},\n", " \n", " {'selector': 'node[id = \"BER\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',\n", " 'background-color': 'green'}}\n", " \n", " ]\n", "ipycytoscape_obj7 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj7.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions\n", "ipycytoscape_obj7.set_style(my_style)\n", "ipycytoscape_obj7.set_style(my_style)\n", "ipycytoscape_obj7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### What did we do? \n", "We gave a particular style to ALL the nodes ('selector': 'node') and afterwards we gave the color green just to the berlin central station node: `('node[id = \"BER\"]')` \n", "\n", "As you can see the way to refer to the node is 'node[id = \"BER\"]'. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## more customization layout\n", "What else can we change in the apperance? \n", "There is quite a few other attributes of the graph that we can change. \n", "Let's assume that the train connections between the cities are as follows: \n", "\n", "- BER - HAM -> 300km/h\n", "- BER - MUN -> 200km/h\n", "- MUN - FRA -> 100km/h\n", "- FRA - BER -> 250km/h\n", "\n", "We can also add information to the edges.\n", "It is necessary to add labels to the edges and also to identify every edge." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "da2bff73b8284c94bc510c469a00ea7f", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "railnet= '''{\n", " \"nodes\": [\n", " {\"data\": { \"id\": \"BER\", \"label\":\"HBf BER\"}},\n", " {\"data\": { \"id\": \"MUN\", \"label\":\"HBf MUN\"}},\n", " {\"data\": { \"id\": \"FRA\", \"label\":\"HBf FRA\"}},\n", " {\"data\": { \"id\": \"HAM\", \"label\":\"HBf HAM\"}}\n", " ],\n", " \"edges\": [\n", " {\"data\": { \"id\": \"line1\", \"source\": \"BER\", \"target\": \"MUN\",\"label\":\"200km/h\"}},\n", " {\"data\": { \"id\": \"line2\", \"source\": \"MUN\", \"target\": \"FRA\",\"label\":\"200km/h\"}},\n", " {\"data\": { \"id\": \"line3\", \"source\": \"FRA\", \"target\": \"BER\",\"label\":\"250km/h\" }},\n", " {\"data\": { \"id\": \"line4\", \"source\": \"BER\", \"target\": \"HAM\",\"label\":\"300km/h\" }}\n", " \n", " ]\n", " }'''\n", "\n", "my_style = [\n", " {'selector': 'node','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',\n", " 'background-color': 'red'}},\n", " \n", " {'selector': 'node[id = \"BER\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',\n", " 'background-color': 'green'}},\n", " \n", " {'selector': 'edge[id = \"line1\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',}},\n", " \n", " {'selector': 'edge[id = \"line2\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',}},\n", " \n", " {'selector': 'edge[id = \"line3\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',}},\n", " \n", " {'selector': 'edge[id = \"line4\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',}}\n", " \n", " ]\n", "railnetJSON = json.loads(railnet)\n", "ipycytoscape_obj8 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj8.graph.add_graph_from_json(railnetJSON, directed=True) # We're specifying that the graph should be undirected\n", "ipycytoscape_obj8.set_style(my_style)\n", "ipycytoscape_obj8.set_style(my_style)\n", "ipycytoscape_obj8" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Classes. What are they and what do they do?\n", "Imagine we want to divide the rail net into two parts. \n", "- cities belonging to the former east Germany and \n", "- cities belonging to the former west Germany\n", "And that we also want to paint these nodes in one go with a particular color. Meaning that we don't want to paint node by node but \"paint all the west cities blue and east cities green\" at once. \n", "We can use classes for that. \n", "We add a class to each node. \n", "Let's revisit how to do this using an example from the very beginning.\n" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "df8cb964a6fd49a4948c3f6096c5c3ce", "version_major": 2, "version_minor": 0 }, "text/plain": [ "CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "railnet= '''{\n", " \"nodes\": [\n", " {\"data\": { \"id\": \"BER\", \"label\":\"HBf BER\"}, \"classes\":\"east\"},\n", " {\"data\": { \"id\": \"MUN\", \"label\":\"HBf MUN\"}, \"classes\":\"west\"},\n", " {\"data\": { \"id\": \"FRA\", \"label\":\"HBf FRA\"}, \"classes\":\"west\"},\n", " {\"data\": { \"id\": \"HAM\", \"label\":\"HBf HAM\"}, \"classes\":\"west\"},\n", " {\"data\": { \"id\": \"LEP\", \"label\":\"HBf LEP\"}, \"classes\":\"east\"}\n", " ],\n", " \"edges\": [\n", " {\"data\": { \"id\": \"line1\", \"source\": \"BER\", \"target\": \"MUN\",\"label\":\"200km/h\"}},\n", " {\"data\": { \"id\": \"line2\", \"source\": \"MUN\", \"target\": \"FRA\",\"label\":\"200km/h\"}},\n", " {\"data\": { \"id\": \"line3\", \"source\": \"FRA\", \"target\": \"BER\",\"label\":\"250km/h\" }},\n", " {\"data\": { \"id\": \"line4\", \"source\": \"BER\", \"target\": \"HAM\",\"label\":\"300km/h\" }},\n", " {\"data\": { \"id\": \"line5\", \"source\": \"BER\", \"target\": \"LEP\",\"label\":\"300km/h\" }}\n", " \n", " ]\n", " }'''\n", "\n", "my_style = [\n", " {'selector': 'node','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',}},\n", " \n", " {'selector': 'node.east','style': {\n", " 'background-color': 'yellow'}},\n", " \n", " {'selector': 'node.west','style': {\n", " 'background-color': 'blue'}},\n", " \n", " \n", " {'selector': 'node[id = \"BER\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)',\n", " 'background-color': 'green'}},\n", " \n", " {'selector': 'edge[id = \"line1\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)'}},\n", " \n", " {'selector': 'edge[id = \"line2\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)'}},\n", " \n", " {'selector': 'edge[id = \"line3\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)'}},\n", " \n", " {'selector': 'edge[id = \"line4\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)'}},\n", " \n", " {'selector': 'edge[id = \"line5\"]','style': {\n", " 'font-family': 'arial',\n", " 'font-size': '10px',\n", " 'label': 'data(label)'}}\n", " \n", " ]\n", "railnetJSON = json.loads(railnet)\n", "ipycytoscape_obj9 = ipycytoscape.CytoscapeWidget()\n", "ipycytoscape_obj9.graph.add_graph_from_json(railnetJSON, directed=True) # We're specifying that the graph should be undirected\n", "ipycytoscape_obj9.set_style(my_style)\n", "ipycytoscape_obj9" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What happended? \n", "With \n", "{'selector': 'node.east]', \n", "'style': {'background-color': 'yellow'}}, \n", "\n", "We painted all east German cities yellow. BER as well, but BER color is overwritten by the green color of the BER node." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Next\n", "There is still a lot to uncover from ipycytoscape's functionalities:\n", "- How to change attributes programmatically? For instance, if we have an input field with number of passengers the user can input the data and the color of the rail station. The node then could turn red if the number of passengers per day is greater than 200000\n", "- How to add and delete elements of the graph: A new station is built in a city called Cologne. How to add that node and several edges to an existing rail net.\n", "- How to add events: You are building an application for the rail company and they want to display information about the station when the user hovers over the node.\n", "\n", "stay tuned." ] } ], "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.9.7" } }, "nbformat": 4, "nbformat_minor": 4 }