Skip to content
This repository has been archived by the owner on May 4, 2019. It is now read-only.

Tutorial

Adil edited this page Sep 2, 2015 · 32 revisions

THIS TUTORIAL DOES NOT REFLECT RECENT KESHIF VERSION. THE SUGGESTED WAY TO LEARN ABOUT THE LIBRARY IS TO PICK YOUR DATASET AMONG MANY EXAMPLES, EXPLORE IT IN THE UI, AND CHECK ITS SOURCE CODE.

Let's create a Keshif interface for a common and interesting dataset: Nobel Prizes.

First, We need to find the data and prepare it for use in Keshif. It is here in Google Docs, with public read access.

Nobel dataset

Let's create the barebone HTML page that will hold our interface.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Nobel Prize Winners</title>
    <meta charset="utf-8">
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript" src="../jquery/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="../d3.v3/d3.v3.js" charset="utf-8"></script>
    <script type="text/javascript" src="../keshif.js" charset="utf-8"></script>
    <link rel="stylesheet" href="../keshif.css"/ type="text/css">
    <link rel="stylesheet" href="../font-awesome/css/font-awesome.min.css">
  </head>
  <body><div id="nobelDiv"></div></body>
</html>

We included Google API, jquery and d3 scripts in addition to keshif. We also linked keshif css style and font-awesome for their nice logo set. The body includes just a single empty div, with id "nobelDiv". You can put this element in any place in your document.

Let's bring our data to this page & create the keshif browser, simply by adding a few lines of javascript in HTML head component, under a script tag:

$(document).ready(function() {
    new kshf.Browser({
        domID : "#nobelDiv",
        categoryTextWidth: 250,
        source : {
            gdocId : '0Ai6LdDWgaqgNdDNVcXlscjl4RzRZNl9ZSkNJLU1DWVE',
            sheets : [ {name:"Prizes"}, {name:"Laureates"} ]
        },
        columnsSkip : ["Overall Motivation", "laureate_id"],
        itemDisplay: {
            sortColWidth: 45,
            sortingOpts : [ {name: 'Year'} ],
            contentFunc : function(d) {
                var laureateID = d.data[kshf.dt_ColNames.Prizes.laureate_id];
                var laureate = kshf.dt_id.Laureates[laureateID];
                return "<div class=\"iteminfo iteminfo_0\">" +
                    laureate.data[kshf.dt_ColNames.Laureates.firstname] + " " + 
                    laureate.data[kshf.dt_ColNames.Laureates.surname] +
                    "</div>";
            }
        }
    });
});

We used jquery's $(document).ready callback to run kshf.init to create our interface. The parameters used are:

  • domID is where you want keshif to insert the interface.
  • categoryTextWidth is how wide you want the left panel width to be (in pixels), which hosts category facets. You can set the value around 150, and modify it as your category labels require.
  • source defines our datasource, and is mostly self explanatory, and configurable. Note that our document has google-id: 0Ai6LdDWgaqgNdDNVcXlscjl4RzRZNl9ZSkNJLU1DWVE. While this document has 3 sheets, we are interested in two of them, Prizes and Laureates.
  • By default, Keshif will try to create a search facet for all columns in the first table you specified. We don't want to create search on two of the columns, so we list them in columnsSkip.
  • We also set our listDisplay options, which includes sortColWidth, sortOpts, and contentFunc. Details of config options are in API docs.
  • sortOpts is a list of sorting options. The simplest approach is to use the column names as sorting options, yet customizations are possible. You need to include at least one sorting item.
  • contentFunc first retrieves the laureateID from prize columns, with the correct integer index retrieved using kshf.dt_ColNames.Prizes.laureate_id. Then, this laureateID is used to look up the second table, as in kshf.dt_id.Laureates[laureateID], which includes the data for this item (row) under .data member.

Let's take a look at the result of our simple approach so far: here

We can improve this result further to offer the optimum exploration experience:

Nobelfull

Let's look at the differences on how we've made this initial result more useful. First and foremost, we will define charts parameter (an array) in our keshif init call, and remove columnsSkip parameter since we no longer will let keshif auto generate the facets and will define our facets one by one.

Adding TimeChart

Since none of our columns included a complete date informations (years were processed as integers) keshif didn't know to apply them to the first category facet. Let's define this mix of category & time filtering as first facet inside charts parameter:

{
    facetTitle: "Category",
    timeTitle: "Date",
    timeItemMap : function(prize){ return new Date(moment(prize.data[prizeCols['Year']]+"01-01","YYYY-MM-DD")); },
    textFilter: 'about'
}

We needed to set timeItemMap configuration, since we did not have a Date column with complete date objects for each row. This function converts the integer 'Year' column to the date, and parse it using moment library, and return a Date object.

Filtering by columns found in other table

We can make our filtering options richer by using columns in other sheets for filtering. First, let's add filter for countries the prize winners were born in.

{
    facetTitle: "Born Country",
    catItemMap : function(prize){ 
        var laureate = kshf.dt_id.Laureates[prize.data[prizeCols.laureate_id]];
        return laureate.data[kshf.dt_ColNames.Laureates.bornCountry];
    },
    catLabelText: function(d) { return getGender(d.data[1]); }
}

We defined "Born Country" as the facetTitle, the catItemMap function gets a prize (row from main table), access the laureate id column, and then use kshf.dt_id.Laureates table (where Laureates is the sheet name) to retrieve the laureate information, and finally return its data indexed at kshf.dt_ColNames.Laureates.bornCountry.

We then repeat this approach in creating gender and affiliation filters. We will just access different columns in Laureate sheet to create the two facets.

{
   facetTitle: "Gender",
   catItemMap : function(prize){ 
       var laureate = kshf.dt_id.Laureates[prize.data[prizeCols.laureate_id]];
       return kshf.dt_ColNames.Laureates.data[lautCols.gender];
   },
   catLabelText: function(d) { return getGender(d.data[1]); }
},{
    facetTitle: "Affiliation",
    catItemMap : function(prize){ 
        var laureate = kshf.dt_id.Laureates[prize.data[prizeCols.laureate_id]];
        return kshf.dt_ColNames.Laureates.data[lautCols.name];
    }
}

You may have noticed a new parameter here: catLabelText. If we hadn't defined this, gender would be seen as 1,2 or 3!, as it is stored in the document. But you want the user to see appropriate labels as male, female and organization (no gender) instead. This new parameter is different than catItemMap, taking a different parameter, the column data which is named as d in this example. By default, d.data[1] includes the automatically extracted column name. Let's define getGender function below, to complete this label mapping feature.

function getGender(v){
    switch(v){
        case 1: return "Male";
        case 2: return "Female";
        case 3: return "Organization";
    }
}

Let's also apply this function to improve the mostly automated and simple column "Joint Awards". It would be nice to include some text in addition to number, so that your users understand the quantities better. This time, we won't need to modify catItemMap, since system automatically could extract it from main table using column name.

{
    facetTitle: "Joint Awards",
    catLabelText: function(d) { return d.data[1]+" awardee"+(d.data[1]===1?"":"s"); }
}

Decade Filter

Lastly, we will include another time based filter. While it is not generally appropriate to include same property twice in different filters, it will also help use revisit catItemMap and catLabelText options, and introduce a new one, sortingFuncs, which should be a list of sorting options. Details regarding this sorting configuration is explained here. Multiple sorting options are displayed under a combobox inside the facet.

{
    facetTitle: "Decade",
    catItemMap : function(prize){ 
        var x=prize.data[prizeCols['Year']];
        return x - (x%10);
    },
    catLabelText: function(d) { return d.data[1]+"s"; },
    sortingFuncs: [{
            name:"Year",
            no_resort: true,
            func:function(a,b){ return b.data[1] -a.data[1]; }
        },{ name:"Prize count"
        }
    ],
    textFilter: 'in', textGroup: 'decades'
}

Adding text search for list view

We add the following options to list config to help users filter items by text, searching prize winner first name and family name together.

textSearch : "names",
textSearchFunc : function (d) {
    var laureate = kshf.dt_id.Laureates[d.data[prizeCols.laureate_id]];
    return laureate.data[lautCols.firstname] + " " + laureate.data[lautCols.surname];
},

Improving item display in the list view

We finally modify the contentFunc to include photos, and more information per item, such as categories, birth date and place, motivations for the awards, etc

contentFunc : function(d) {
    var j;
    var str="";
    var laureate = kshf.dt_id.Laureates[d.data[prizeCols.laureate_id]];
    var surname_short = laureate.data[lautCols.surname].toLowerCase().replace(/ /g,"_");
    surname_short = surname_short.replace("von_","").replace("la_","");
    var imgUrl="./nobel_photo/"+surname_short+".jpg";

    if(laureate.data[lautCols.gender]!=='org'){
        str+="<img src=\\""+imgUrl+"\\" width=\\"80\\" style=\\"float:left\\">";
        str+="<div style=\\"position: absolute; left:85px;\\">";
    } else {
        str+="<div style=\\"position: relative; left:85px;\\">";
    }

    // description
    str+="<div class=\\"iteminfo iteminfo_0\\">";
    str+=laureate.data[lautCols.firstname] + " " + laureate.data[lautCols.surname];
    str+="</div>";

    str+="<div class=\\"iteminfo iteminfo_3\\">";
    if(laureate.data[lautCols.gender]!=='org'){
        var born=laureate.data[lautCols.born];
        var died=laureate.data[lautCols.died];
        str+="Born: "+laureate.data[lautCols.bornCountry]+", "+laureate.data[lautCols.bornCity]+
            (born!==null?(", "+moment(born).format("MMM. DD YYYY")):"")+
            (died!==null?(", Died in "+moment(died).format("MMM. DD YYYY")):"")
            ;
    }
    str+="</div>";

    str+="<div class=\\"iteminfo iteminfo_1\\">Nobel Prize In: ";
    str+=d.data[prizeCols['Category']];
    str+="</div>";

    str+="<div class=\\"iteminfo iteminfo_2\\">Motivation: ";
    str+=d.data[prizeCols['Motivation']];
    str+="</div>";

    var mot = d.data[prizeCols['Overall Motivation']];
    if(mot!=="" && mot!== null){
        str+="<div class=\\"iteminfo iteminfo_3\\">Overall motivation: ";
        str+=mot;
        str+="</div>";
    }

    str+="</div>"

    return str;
}

After we make these changes, we are done with this tutorial, and we have a fine looking and easy to use interface for browsing and exploring Nobel Prize Winners!