Example of DC-JS library usage cite


In [1]:
from matplotlib import pyplot as plt
from IPython.display import display, Javascript, HTML

import jinja2
import json
import numpy as np
import pandas as pd

In [2]:
def inline_dc(stuff):
    """
    Embeds the HTML source of the dc charts directly into the IPython notebook.
    
    This method will not work if the dc charts depends on any files(json data). 
    Also this uses the HTML5 srcdoc attribute, which may not be supported in 
    all browsers.
    """

    return HTML('''
        <iframe srcdoc="{srcdoc}" 
        style="width: 100%; height: 300px; border: none"></iframe>
    '''.format(srcdoc=stuff.replace('"', '&quot;')))

def import_js(path_js):
    with open(path_js) as f:
        return Javascript(f.read())

Data


In [3]:
with open('./maps/data/br-states.json') as f:
    br_states = f.read()
    br_states_json = json.loads(br_states)

states = [
    br_states_json['features'][i]['properties']['nome'] 
    for i in range(27)
]

In [5]:
np.random.seed(seed=2016)
df = pd.DataFrame({
    'incidence': np.random.randint(low=2, high=100, size=54),
    'age': np.random.randint(low=2, high=60, size=54),
    'state': [st for st in states for t in range(2)],
    'alert': np.random.randint(low=1, high=5, size=54),
    'w': [i for t in range(27) for i in range(1, 3)]
})

df


Out[5]:
age alert incidence state w
0 50 1 64 Acre 1
1 50 2 12 Acre 2
2 43 2 85 Alagoas 1
3 39 1 15 Alagoas 2
4 18 2 41 Amazonas 1
5 56 3 36 Amazonas 2
6 17 1 13 Amapá 1
7 36 1 37 Amapá 2
8 34 1 45 Bahia 1
9 34 4 46 Bahia 2
10 7 2 54 Ceará 1
11 7 4 77 Ceará 2
12 50 4 36 Distrito Federal 1
13 13 3 88 Distrito Federal 2
14 41 2 99 Espírito Santo 1
15 3 2 11 Espírito Santo 2
16 44 2 99 Goiás 1
17 9 3 20 Goiás 2
18 9 2 31 Maranhão 1
19 15 2 62 Maranhão 2
20 19 3 74 Minas Gerais 1
21 53 4 29 Minas Gerais 2
22 25 4 32 Mato Grosso do Sul 1
23 24 2 58 Mato Grosso do Sul 2
24 43 4 39 Mato Grosso 1
25 8 4 38 Mato Grosso 2
26 39 3 44 Pará 1
27 17 4 39 Pará 2
28 21 1 26 Paraíba 1
29 48 1 86 Paraíba 2
30 7 2 18 Pernambuco 1
31 2 3 26 Pernambuco 2
32 3 1 52 Piauí 1
33 21 3 64 Piauí 2
34 22 3 93 Paraná 1
35 47 2 21 Paraná 2
36 48 1 30 Rio de Janeiro 1
37 17 3 29 Rio de Janeiro 2
38 25 2 4 Rio Grande do Norte 1
39 42 3 20 Rio Grande do Norte 2
40 8 2 60 Rondônia 1
41 6 2 34 Rondônia 2
42 32 3 27 Roraima 1
43 36 2 50 Roraima 2
44 38 1 80 Rio Grande do Sul 1
45 19 4 62 Rio Grande do Sul 2
46 11 4 28 Santa Catarina 1
47 48 2 63 Santa Catarina 2
48 20 3 27 Sergipe 1
49 30 1 39 Sergipe 2
50 27 3 31 São Paulo 1
51 32 4 93 São Paulo 2
52 26 4 57 Tocantins 1
53 45 3 42 Tocantins 2

Templates


In [6]:
# import js libs
head = HTML('''
<head> 
<meta http-equiv="content-type" content="text/html; charset=UTF8">

<link 
    rel="stylesheet" 
    type="text/css" 
    href="https://cdnjs.cloudflare.com/ajax/libs/dc/2.0.0-alpha.5/dc.css"/> 
<script 
    src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"
></script> 
<script 
    src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"
></script> 

<link
    rel="stylesheet" 
    href="https://unpkg.com/leaflet@1.0.0-rc.3/dist/leaflet.css" 
/>
<script 
    src="https://unpkg.com/leaflet@1.0.0-rc.3/dist/leaflet.js"
></script>

<script 
    src="https://code.jquery.com/jquery-2.2.4.min.js" 
    integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" 
    crossorigin="anonymous"
></script>

<script
    src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"
></script>

<script>
{import_js}
</script>

<style>

.chart {
    margin-left:90px;
}
.table-hover tbody tr:hover td, .table-hover tbody tr:hover th {
    background-color: #f5f5f5;
}
.table th, .table td {
    padding: 8px;
    line-height: 20px;
    text-align: left;
    vertical-align: top;
    border-top: 1px solid #ddd;
}
user agent stylesheettd, th {
    display: table-cell;
    vertical-align: inherit;
}

</style>

</head>
'''.replace(
    '{import_js}', import_js('static/libs/dc/dc.min.js').data
))

In [7]:
# body
body_html = HTML("""
<div>
Period (week):
<select id="week">
    <option value="">All Weeks</option>
    <option value="1" selected="selected">1</option>
    <option value="2">2</option>
</select>

<input type="hidden" id="selected_state" value=""/>
</div>

<div id="map" style="height:250px;width:500px;"></div>

<div>
<table id="data-table" class='table'>
 <!-- create a custom header -->
    <thead class="header">
        <tr class="header">
        <th>State</th>
        <th>Incidence</th>
        <th>Age</th>
        <th>Alert</th>
        </tr>
    </thead>
</table>
<div>

<!-- A div anchor that can be identified by id -->
<div id="linechart" class="dc-chart" style="display:none;">
    <!-- Title or anything you want to add above the chart -->
    <strong>Series Chart</strong>
    <!--
        This will create a reset button when ever the user selects a filter.
        Clicking on the reset button will remove all filters.
     -->
    <a class="reset" href="javascript:void(0);" onclick="javascript:LineChart.filterAll();dc.redrawAll();" style="display: none;">reset</a>
    <div class="clearfix"></div>
    <!--
        dc.js will also automatically inject applied current filter value into
        any html element with css class set to "filter"
    -->
    <span class="reset" style="display: none;">Current filter: <span class="filter"></span></span>
    <br/>
    <br/>
</div>

<div id="rowchart" class="dc-chart" style="display:none;">
    <!-- Title or anything you want to add above the chart -->
    <strong>Age Chart</strong>
    <!--
        This will create a reset button when ever the user selects a filter.
        Clicking on the reset button will remove all filters.
     -->
    <a class="reset" href="javascript:void(0);" onclick="javascript:RowChart.filterAll();dc.redrawAll();" style="display: none;">reset</a>
    <div class="clearfix"></div>
    <!--
        dc.js will also automatically inject applied current filter value into
        any html element with css class set to "filter"
    -->
    <span class="reset" style="display: none;">Current filter: <span class="filter"></span></span>
    <br/>
    <br/>
</div>
""")

In [8]:
# js template

dc_template = jinja2.Template(
"""   
console.clear();

function filter_apply(filter) {
    var f=eval(filter);
    
    if (typeof(f.length) != "undefined") {} else {};
    
    if (typeof(f.top) != "undefined") {f=f.top(Infinity);} else {};
    
    if (typeof(f.dimension) != "undefined") {
        f=f.dimension(function(d) { return "";}).top(Infinity);
    } else {};
    
    return f;
}


function print_filter(filter){
    f = filter_apply(filter);
    
    console.log(
        filter + "(" + f.length + ") = " + 
        JSON.stringify(f)
    );
}

// Create Global Variables
/*var RowChart = dc.rowChart("#rowchart");
var LineChart = dc.lineChart("#linechart");*/
var DataTable = dc.dataTable("#data-table");

// Load data
var dataset = {{ data }};
var br_states = {{ br_states }};

var flu_colors = {
    1: '#dfdfdf',
    2: '#ffcc00',
    3: '#ff0000',
    4: '#00ff00',
};

function style_map(feature) {
    return {
        weight: 2,
        opacity: 1,
        color: 'white',
        dashArray: '3',
        fillOpacity: 0.3,
        fillColor: '#ff0000'
    };
}

// Create function
function Graph(data) {
    var map = L.map('map');
    map.setView([-16, -50.528742], 3);
    
    // create the tile layer with correct attribution
    var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
    var osmAttrib='Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors';
    var osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 12, attribution: osmAttrib});
    
    function onEachFeature(feature, layer) {
        //bind click
        layer.on({
            click: function(){
                // reset line weight
                geojson_layer.eachLayer(function (_layer) {  
                    _layer.setStyle({
                        weight: 1
                    });
                });
                // bold the selected state
                layer.setStyle({
                    weight: 2
                });
                
                $('#selected_state').val(feature.properties.nome);
            }
        });
    }
    
    // Feed it through crossfilter  
    var ndx = crossfilter(data);
    
    // for testing
    //console.log(data);
    
    //define a dimension
    //Here we will group by state
    var stateDim = ndx.dimension(dc.pluck('state'));
    var weekDim = ndx.dimension(dc.pluck('w'));
    
    // start the map in South-East England
    geojson_layer = L.geoJson(br_states, {
        onEachFeature: onEachFeature,
        style: function(feature) {
            l_name = feature.properties.nome;
            df = filter_apply(weekDim.filter($('#week').val()));
            result = {};
            $(df).each(function(i){ 
                if (l_name == df[i]['state']){
                    result = {
                        fillColor: flu_colors[df[i]['alert']],
                        color: '#333333',
                        opacity: 1,
                        weight: 1
                    };
                    return;
                }
            });
            return result;
        }
    });
    geojson_layer.addTo(map);
    osm.addTo(map)
    
    d3.select('#week')
        .on('change', function(){ 
            df = filter_apply(
                weekDim.filter($('#week').val()));
            //print_filter(df);
            geojson_layer.eachLayer(function (layer) {  
                l_name = layer.feature.properties.nome;
                $(df).each(function(i){ 
                    if (l_name == df[i]['state']){
                        layer.setStyle({
                            fillColor: flu_colors[df[i]['alert']],
                            color: '#333333',
                            opacity: 1
                        });
                    }
                });
            });
            dc.renderAll(); // render all charts on the page
    });
    
    //Here we group by state and sum on column population
    var stateGroup = stateDim.group().reduceSum(dc.pluck('incidence'));
    
    /*
    //Lets create a row chart
    RowChart.dimension(stateDim)
        .group(stateGroup)
        .width(500);
        
    RowChart.getColor = function(d, i){
        var flu_colors = {
            1: '#dfdfdf',
            2: '#ffcc00',
            3: '#ff0000',
            4: '#00ff00',
        };
        return flu_colors[d.alert];
    };
    */
    
    DataTable.dimension(weekDim)
        // data table does not use crossfilter group but rather a closure
        // as a grouping function
        .group(function(d) {
            return '';
        })
        .columns([
            dc.pluck('state'),
            dc.pluck('incidence'),
            dc.pluck('age'),
            dc.pluck('alert'),
        ])
        .size(100)
        .sortBy(dc.pluck('state'))
        .order(d3.ascending);
    
    dc.renderAll(); // render all charts on the page
    
}; // end graph function    

// Call function
Graph(dataset);
    
""")

In [9]:
# bind date to js template
body_js = Javascript(dc_template.render(
    data=df.to_json(orient='records'),
    br_states=br_states
))

# push data to notebook

inline_dc(
    head.data + body_html.data + 
    "<script>" + body_js.data + "</script>"
)


Out[9]:

References