{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Preamble" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ ":dep chord = {Version = \"0.8.1\"}\n", "use chord::{Chord, Plot};" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction\n", "\n", "In a chord diagram (or radial network), entities are arranged radially as segments with their relationships visualised by arcs that connect them. The size of the segments illustrates the numerical proportions, whilst the size of the arc illustrates the significance of the relationships1.\n", "\n", "Chord diagrams are useful when trying to convey relationships between different entities, and they can be beautiful and eye-catching.\n", "\n", "## Get Chord Pro\n", "\n", "[Click here](https://datacrayon.com/shop/product/chord-pro/) to get lifetime access to the full-featured chord visualization API, producing beautiful interactive visualizations, e.g. those featured on the front page of Reddit.\n", "\n", "\"chord\n", "\n", "- Produce beautiful interactive Chord diagrams.\n", "- Customize colours and font-sizes.\n", "- Access Divided mode, enabling two sides to your diagram.\n", "- Symmetric and Asymmetric modes,\n", "- Add images and text on hover,\n", "- Access finer-customisations including HTML injection.\n", "- Allows commercial use without open source requirement.\n", "- Currently supports Python, JavaScript, and Rust, with many more to come (accepting requests).\n", "\n", "\"chord\n", "\n", "## The Chord Package\n", "\n", "With Python in mind, there are many libraries available for creating Chord diagrams, such as [Plotly](https://plotly.com/python/v3/filled-chord-diagram/), [Bokeh](http://docs.bokeh.org/en/0.12.0/docs/gallery/chord_chart.html), and a few that are lesser-known. However, I wanted to use the implementation from [d3](https://observablehq.com/@d3/chord-diagram?collection=@d3/d3-chord) because it can be customised to be highly interactive and to look beautiful.\n", "\n", "I couldn't find anything that ticked all the boxes, [so I made](https://pypi.org/project/chord/) a wrapper around d3-chord myself. It took some time to get it working, but I wanted to hide away everything behind a single constructor and method call. The tricky part was enabling multiple chord diagrams on the same page, and then loading resources in a way that would support Jupyter Notebooks.\n", "\n", "You can get the package either from [PyPi](https://pypi.org/project/chord/) using `pip install chord` or from the [GitHub repository](https://github.com/shahinrostami/chord). With your processed data, you should be able to plot something beautiful with just a single line, `Chord(data, names).show()`. To enable the pro features of the `chord` package, [get Chord Pro](https://store.shahinrostami.com/product/chord-pro/).\n", "\n", "## The Chord Crate\n", "\n", "I wasn't able to find any Rust crates for plotting chord diagrams, so I ported [my own](https://pypi.org/project/chord/) from Python to Rust.\n", "\n", "\n", "You can get the crate either from [crates.io](https://crates.io/crates/chord) or from the [GitHub repository](https://github.com/shahinrostami/chord_rs). With your processed data, you should be able to plot something beautiful with just a single line, `Chord{ matrix : matrix, names : names, .. Chord::default() }.show()`. To enable the pro features of the `chord` crate, [get Chord Pro](https://store.shahinrostami.com/product/chord-pro/)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Dataset\n", "\n", "The focus for this section will be the demonstration of the `chord` crate. To keep it simple, we will use synthetic data that illustrates the co-occurrences between movie genres within the same movie." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "let matrix: Vec> = vec![\n", " vec![0., 5., 6., 4., 7., 4.],\n", " vec![5., 0., 5., 4., 6., 5.],\n", " vec![6., 5., 0., 4., 5., 5.],\n", " vec![4., 4., 4., 0., 5., 5.],\n", " vec![7., 6., 5., 5., 0., 4.],\n", " vec![4., 5., 5., 5., 4., 0.],\n", "];\n", "\n", "let names: Vec = vec![\n", " \"Action\",\n", " \"Adventure\",\n", " \"Comedy\",\n", " \"Drama\",\n", " \"Fantasy\",\n", " \"Thriller\",\n", "]\n", ".into_iter()\n", ".map(String::from)\n", ".collect();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Chord Diagrams\n", "\n", "Let's see what the `Chord` defaults produce when we invoke the `show()` method.\n", "
" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: true,\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Different Colours\n", "\n", "The defaults are nice, but what if we want different colours? You can pass in almost anything from [d3-scale-chromatic](https://github.com/d3/d3-scale-chromatic#categorical), or you could pass in a list of hexadecimal colour codes." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: true,\n", " colors: vec![String::from(\"d3.schemeSet2\")],\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: true,\n", " colors: vec![String::from(format!(\"d3.schemeGnBu[{:?}]\",names.len()))],\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: true,\n", " colors: vec![String::from(\"d3.schemeSet3\")],\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: true,\n", " colors: vec![String::from(format!(\"d3.schemePuRd[{:?}]\",names.len()))],\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: true,\n", " colors: vec![String::from(format!(\"d3.schemeYlGnBu[{:?}]\",names.len()))],\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let hex_colours : Vec = vec![\"#222222\", \"#333333\", \"#4c4c4c\", \"#666666\", \"#848484\", \"#9a9a9a\"].into_iter()\n", ".map(String::from)\n", ".collect();\n", "\n", "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: true,\n", " colors: hex_colours,\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Label Styling\n", "\n", "We can disable wrapped labels, and even change the colour." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " wrap_labels: false,\n", " label_color:\"#4c40bf\".to_string(),\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Opacity\n", "\n", "We can also change the default opacity of the relationships." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " opacity: 0.1,\n", " ..Chord::default()\n", "}\n", ".show();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Width\n", "\n", "We can also change the maximum width the plot." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \t\n", " Chord Diagram\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "()" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Chord {\n", " matrix: matrix.clone(),\n", " names: names.clone(),\n", " width: 400.0,\n", " wrap_labels: true,\n", " ..Chord::default()\n", "}\n", ".show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Conclusion\n", "\n", "In this section, we've introduced the chord diagram and `chord` crate. We used the crate and some synthetic data to demonstrate several chord diagram visualisations with different configurations. The chord Python crate is available for free from [crates.io](https://crates.io/crates/chord) or from the [GitHub repository](https://github.com/shahinrostami/chord_rs)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
\n", "
    \n", "
  1. \n", "

    Tintarev, N., Rostami, S., & Smyth, B. (2018, April). Knowing the unknown: visualising consumption blind-spots in recommender systems. In Proceedings of the 33rd Annual ACM Symposium on Applied Computing (pp. 1396-1399). 

    \n", "
  2. \n", "
\n", "
" ] } ], "metadata": { "kernelspec": { "display_name": "Rust", "language": "rust", "name": "rust" }, "language_info": { "codemirror_mode": "rust", "file_extension": ".rs", "mimetype": "text/rust", "name": "Rust", "pygment_lexer": "rust", "version": "" }, "nikola": { "category": "rust-notebooks", "date": "2020-04-15 22:02:01 UTC+01:00", "description": "", "link": "", "slug": "chord-diagrams", "tags": "", "title": "Interactive Chord Diagrams", "type": "text" } }, "nbformat": 4, "nbformat_minor": 4 }