Beautiful JavaScript Charts in Jupyter Notebooks

Jupyter Notebooks tell stories by blending explanations, visualizations, and the code producing them. In my opinion, the most compelling charts are interactive, Javascript based. Here's how you can blend beautiful Javascript charts into Jupyter Notebooks to tell your story.

For simple, plots iPlotter brings the latest D3.js and canvas charting libraries to Jupyter Notebooks using native python data structures. iPlotter integrates with C3.js, plotly.js, Chart.js, Chartist.js, and Google Charts.

To get started:

$ pip install iplotter

When this fails, you can directly render JavaScript by passing Python data structures either as strings or through dictionaries as json.

In [1]:
import iplotter
from IPython.core.display import HTML

Before we dive in to the charts, let's adjust the iframe style jupyter notebooks use so charts render more cleanly.

In [2]:
# remove iFrame border for cleaner chart rendering
# increase size of text explanations
HTML("""
<style>
iframe {border:0;}
</style>
""")
Out[2]:

To use iPlotter, select your JavaScript charting library of choice. Then, pass a python data structure in a format corresponding to the json the library expects.

C3.js

is a charting library based on D3.js that makes it easy to build and reuse beautiful charts.

In [37]:
# define chart + data
chart = {
    "data": {
        "columns": [
            ["setosa_x", 3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.4, 3.0, 3.0, 4.0, 4.4, 3.9, 3.5, 3.8, 3.8, 3.4, 3.7, 3.6, 3.3, 3.4, 3.0, 3.4, 3.5, 3.4, 3.2, 3.1, 3.4, 4.1, 4.2, 3.1, 3.2, 3.5, 3.6, 3.0, 3.4, 3.5, 2.3, 3.2, 3.5, 3.8, 3.0, 3.8, 3.2, 3.7, 3.3],
            ["versicolor_x", 3.2, 3.2, 3.1, 2.3, 2.8, 2.8, 3.3, 2.4, 2.9, 2.7, 2.0, 3.0, 2.2, 2.9, 2.9, 3.1, 3.0, 2.7, 2.2, 2.5, 3.2, 2.8, 2.5, 2.8, 2.9, 3.0, 2.8, 3.0, 2.9, 2.6, 2.4, 2.4, 2.7, 2.7, 3.0, 3.4, 3.1, 2.3, 3.0, 2.5, 2.6, 3.0, 2.6, 2.3, 2.7, 3.0, 2.9, 2.9, 2.5, 2.8],
            ["setosa", 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.2, 0.1, 0.1, 0.2, 0.4, 0.4, 0.3, 0.3, 0.3, 0.2, 0.4, 0.2, 0.5, 0.2, 0.2, 0.4, 0.2, 0.2, 0.2, 0.2, 0.4, 0.1, 0.2, 0.2, 0.2, 0.2, 0.1, 0.2, 0.2, 0.3, 0.3, 0.2, 0.6, 0.4, 0.3, 0.2, 0.2, 0.2, 0.2],
            ["versicolor", 1.4, 1.5, 1.5, 1.3, 1.5, 1.3, 1.6, 1.0, 1.3, 1.4, 1.0, 1.5, 1.0, 1.4, 1.3, 1.4, 1.5, 1.0, 1.5, 1.1, 1.8, 1.3, 1.5, 1.2, 1.3, 1.4, 1.4, 1.7, 1.5, 1.0, 1.1, 1.0, 1.2, 1.6, 1.5, 1.6, 1.5, 1.3, 1.3, 1.3, 1.2, 1.4, 1.2, 1.0, 1.3, 1.2, 1.3, 1.3, 1.1, 1.3]
        ],
     "type": 'scatter'
            },
    "axis": {
        "x": {
            "label": 'Sepal.Width',
            "tick": {
                "fit": "false"
            }
        },
        "y": {
            "label": 'Petal.Width'
        }
    }
}
In [38]:
c3_plotter = iplotter.C3Plotter()
c3_plotter.plot(chart)
Out[38]:

How about line charts?

In [5]:
chart = {
    "data": {
        "columns": [
            ['dogs', 300, 350, 300, 0, 0, 120],
            ['cats', 130, 100, 140, 200, 150, 50],
            ['people', 180, 75, 265, 100, 50, 100]
        ],
        "types": {
            "dogs": 'area-spline',
            "cats": 'area-spline',
            "people": 'area-spline'
        },
        "groups": [['dogs', 'cats', 'people']]
    }
}
In [6]:
c3_plotter = iplotter.C3Plotter()
c3_plotter.plot(chart)
Out[6]:

Chart.js

Chart.js requires a slightly different input format, but works similarly. Chart.js is a canvas charting library so it can handle many points!

In [7]:
labels = ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"]
values = [{"x":20, "y": 30, "r":15}, {"x":40, "y":10, "r":10}]
In [19]:
data = {    
    "labels": labels,
    "datasets": [
        {
            "label": "My First dataset",
            "backgroundColor": "rgba(179,181,198,0.2)",
            "borderColor": "rgba(179,181,198,1)",
            "pointBackgroundColor": "rgba(179,181,198,1)",
            "pointBorderColor": "#fff",
            "pointHoverBackgroundColor": "#fff",
            "pointHoverBorderColor": "rgba(179,181,198,1)",
            "data": [65, 59, 90, 81, 56, 55, 40]
        },
        {
            "label": "My Second dataset",
            "backgroundColor": "rgba(255,99,132,0.2)",
            "borderColor": "rgba(255,99,132,1)",
            "pointBackgroundColor": "rgba(255,99,132,1)",
            "pointBorderColor": "#fff",
            "pointHoverBackgroundColor": "#fff",
            "pointHoverBorderColor": "rgba(255,99,132,1)",
            "data": [28, 48, 40, 19, 96, 27, 100]
        }
    ]
}
In [20]:
chart_js = iplotter.ChartJSPlotter()

chart_js.plot(data, chart_type="radar", w=600, h=600)
Out[20]:

Google Charts

License is Creative Commons Attribution 3.0 License, which requires attribution, but allows for free commercial/personal use. No data is not sent to a Google server. It's rendered in the browser.

In [21]:
data = [
    ['Genre', 'Fantasy & Sci Fi', 'Romance', 'Mystery/Crime', 'General',
     'Western', 'Literature', {"role": 'annotation'}],
    ['2010', 10, 24, 20, 32, 18, 5, ''],
    ['2020', 16, 22, 23, 30, 16, 9, ''],
    ['2030', 28, 19, 29, 30, 12, 13, '']
]

options = {
    "width": 600,
    "height": 400,
    "legend": {"position": 'top', "maxLines": 3},
    "bar": {"groupWidth": '75%'},
    "isStacked": "true",
}
In [22]:
gc_plotter = iplotter.GCPlotter()
gc_plotter.plot(data, chart_type="ColumnChart",chart_package='corechart', options=options)
Out[22]:

When Rendering Fails

sometimes, iPlotter just doesn't quite render... I couldn't get the Bubble Chart or Polar Area Chart documented on Chart.js to render or the pie chart in Google Charts.

In [23]:
data = [
          ['Task', 'Hours per Day'],
          ['Work',     11],
          ['Eat',      2],
          ['Commute',  2],
          ['Watch TV', 2],
          ['Sleep',    7]
]

options = {
"title": "hi"
}
In [24]:
gc_plotter = iplotter.GCPlotter()
gc_plotter.plot(data, chart_type="piechart", options=options)
# sad face :<
Out[24]:

In those cases, call in the JavaScript: (from this gist)

In [25]:
import json
from IPython.display import display, Javascript

def chartjs(chartType, data, options={}, width="500px", height="400px"):
    """ Custom iphython extension allowing chartjs visualizations
    
    Usage:
        chartjs(chartType, data, options, width=1000, height=400)
    
    Args:
        chartType: one of the supported chart type options (line, bar, radar, polarArea, pie, doughnut)
        data: a python dictionary with datasets to be rapresented and related visualization settings, as expected 
              by chart js (see data parameter in http://www.chartjs.org/docs/)
        options: defaults {}; a python dictionary with additional graph options, as expected 
              by chart js (see options parameter in http://www.chartjs.org/docs/)
        width: default 700px
        height: default 400px
        
        NB. data and options structure depends on the chartType
    """
    display(
        Javascript("""
            require(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js'], function(chartjs){
                var chartType="%s";
                var data=%s;
                var options=%s;
                var width="%s";
                var height="%s";
                
                element.append('<canvas width="' + width + '" height="' + height + '">s</canvas>');
                var ctx = element.children()[0].getContext("2d");
                
                switch(chartType.toLowerCase()) {
                    
                    case "line":
                        var myChart = new Chart(ctx).Line(data, options);
                        break;
                    case "bar":
                        var myChart = new Chart(ctx).Bar(data, options);
                        break;
                    case "radar":
                        var myChart = new Chart(ctx).Radar(data, options);
                        break;
                    case "polarArea":
                        var myChart = new Chart(ctx).PolarArea(data, options);
                        break;
                    case "pie":
                        var myChart = new Chart(ctx).Pie(data, options);
                        break;
                    case "doughnut":
                        var myChart = new Chart(ctx).Doughnut(data, options);
                        break;
                }
            });
            """ % (chartType, json.dumps(data), json.dumps(options), width, height)
        )
    )

The above is Javascript in a string with the data converted from Python dictionaries into strings.

In [26]:
# to run 
data = {
    "labels": [1,2,3,4,5,6],
    "datasets": [
        {
            "label": "Sample dataset",
            "fillColor": "#ffce56",
            "strokeColor": "rgba(151,187,205,1)",
            "pointColor": "rgba(151,187,205,1)",
            "pointStrokeColor": "#fff",
            "pointHighlightFill": "#fff",
            "pointHighlightStroke": "rgba(151,187,205,1)",
            "data": [1, 10, 3, 2, 7, 8]
        }
]}

chartjs("Line", data, width=600)

For Google Charts, simply use Jupyter magic methods to turn the cell into html!

In [32]:
%%html
<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      // Load the Visualization API and the corechart package.
      google.charts.load('current', {'packages':['corechart']});

      // Set a callback to run when the Google Visualization API is loaded.
      google.charts.setOnLoadCallback(drawChart);

      // Callback that creates and populates a data table,
      // instantiates the pie chart, passes in the data and
      // draws it.
      function drawChart() {

        // Create the data table.
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Topping');
        data.addColumn('number', 'Slices');
        data.addRows([
          ['Mushrooms', 3],
          ['Onions', 1],
          ['Olives', 1],
          ['Zucchini', 1],
          ['Pepperoni', 2]
        ]);

        // Set chart options
        var options = {'title':'How Much Pizza I Ate Last Night',
                       'width':600,
                       'height':400};

        // Instantiate and draw our chart, passing in some options.
        var chart = new google.visualization.PieChart(document.getElementById('chart_div'));
        chart.draw(data, options);
      }
    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
    <div id="chart_div" style="width: 700px; height: 410px;"></div>
  </body>
</html>

To pass Python Functions, copy the HTML into a python string to embed the python data:

In [33]:
# python list
data = [
          ['Task', 'Hours per Day'],
          ['Work',     11],
          ['Eat',      2],
          ['Commute',  2],
          ['Watch TV', 2],
          ['Sleep',    7]
]

# note the double escape to ensure apostrophe is rendered correctly
title = "Mark\\'s Daily Activities"
In [34]:
html_code = """
<html>
  <head>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      google.charts.load("current", {packages:["corechart"]});
      google.charts.setOnLoadCallback(drawChart);
      function drawChart() {
        var data = google.visualization.arrayToDataTable(%s);

        var options = {
          title: '%s',
          pieHole: 0.4,
        };

        var chart = new google.visualization.PieChart(document.getElementById('donutchart'));
        chart.draw(data, options);
      }
    </script>
  </head>
  <body>
    <div id="donutchart" style="width: 900px; height: 520px;"></div>
  </body>
</html>
"""% (data, title)

You can also use the newer string.format() in Python to create the html_code string.

In [35]:
# render use jupyter's html function
HTML(html_code)
Out[35]:

Who cares? Code, visualizations, and text turn a Jupyter notebook into a compelling story—one that others can replicate too!