biojs-vis-inchlib
Version:
Interactive Cluster Heatmap library
1,312 lines (1,108 loc) • 604 kB
JavaScript
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
/*
* biojs-vis-inchlib
* https://github.com/skutac/biojs-vis-inchlib
*
* Copyright (c) 2014 Ctibor Škuta
* Licensed under the MIT license.
*/
/**
* @class InChlib
* InCHlib is an interactive JavaScript library which facilitates data
* visualization and exploration by means of a cluster heatmap. InCHlib
* is a versatile tool, and its use is not limited only to chemical or
* biological data. Source code, tutorial, documentation, and example
* data are freely available from InCHlib website <a
* href="http://openscreen.cz/software/inchlib"
* target=blank>http://openscreen.cz/software/inchlib</a>. At the
* website, you can also find a Python script <a
* href="http://openscreen.cz/software/inchlib/inchlib_clust"
* target=blank>inchlib_clust</a> which performs data clustering and
* prepares <a href="http://openscreen.cz/software/inchlib/input_format"
* target=blank>input data for InCHlib</a>.
*
* @author <a href="mailto:ctibor.skuta@img.cas.cz">Ctibor Škuta</a>
* @author <a href="mailto:petr.bartunek@img.cas.cz">Petr Bartůněk</a>
* @author <a href="mailto:svozild@vscht.cz">Daniel Svozil</a>
* @version 1.1.2
* @category 1
* @license InCHlib - Interactive Cluster Heatmap Library http://openscreen.cz/software/inchlib Copyright 2014, Ctibor Škuta, Petr Bartůněk, Daniel Svozil Licensed under the MIT license.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* @requires <a href='http://code.jquery.com/jquery-2.0.3.min.js'>jQuery Core 2.0.3</a>
* @dependency <script language="JavaScript" type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
*
* @requires <a href='http://kineticjs.com/'>KineticJS 5.1.0</a>
* @dependency <script language="JavaScript" type="text/javascript" src="http://openscreen.cz/software/inchlib/static/js/kinetic-v5.1.0.min.js"></script>
*
* @param {Object} options An object with the options for the InCHlib component.
*
* @option {string} target
* identifier of the DIV tag where the component should be displayed
* @option {boolean} [column_dendrogram=false]
* turn on/off the column dendrogram
* @option {boolean} [count_column=false]
* turn on/off the count column
* @option {boolean} [dendrogram=true]
* turn on/off the row dendrogram
* @option {string} [font="Trebuchet MS"]
* font family
* @option {string} [heatmap_colors="Greens"]
* the heatmap color scale
* @option {number} [heatmap_part_width=0.7]
* define the heatmap part width from the width of the whole graph
* @option {string} [highlight_colors="Reds"]
* color scale for highlighted rows
* @option {obejct} [highlighted_rows=[]]
* array of row IDs to highlight
* @option {boolean} [independent_columns=true]
* determines whether the color scale is based on the values from all columns together or for each column separately
* @option {string} [label_color=grey]
* color of column label
* @option {number} [max_column_width=100]
* maximum column width in pixels
* @option {number} [max_height=800]
* maximum graph height in pixels
* @option {number} [max_row_height=25]
* maximum row height in pixels
* @option {boolean} [metadata=false]
* turn on/off the metadata
* @option {string} [metadata_colors="Oranges"]
* the metadata color scale
* @option {number} [min_row_height=false]
* minimum row height in pixels
* @option {number} [width="the width of target DIV"]
* width of the graph in pixels
* @option {boolean} [heatmap=true]
* turn on/off the heatmap
* @option {string} [heatmap_font_color="black"]
* the color of the text values in the heatmap
* @option {string} [count_column_colors="Reds"]
* the color scale of count column
* @option {boolean} [draw_row_ids=false]
* draws the row IDs next to the heatmap when there is enough space to visualize them
* @option {number} [max_percentile=100]
* the value percentile above which the color will be equal to the terminal color of the color scale
* @option {number} [min_percentile=0]
* the value percentile below which the color will be equal to the beginning color of the color scale
* @option {number} [middle_percentile=50]
* the value percentile which defines where the middle color of the color scale will be used
*
* @example
* window.instance = new InCHlib({
* target : "YourOwnDivId",
* metadata: true,
* max_height: 800,
* width: 700,
* metadata_colors: "RdLrBu"
* });
* instance.read_data_from_file("../biojs/data/chembl_gr.json");
* instance.draw();
*/
var _ = require("backbone");
var InCHlib, _this;
var _date = new Date();
module.exports = InCHlib = function(settings){
_this = this;
_this.user_settings = settings;
_this.target_element = $("#" + settings.target);
var target_width = _this.target_element.width();
_this.target_element.css({"position": "relative"});
/**
* Default values for the settings
* @name InCHlib#settings
*/
_this.settings = {
"target" : "YourOwnDivId",
"heatmap" : true,
"heatmap_header": true,
"dendrogram": true,
"metadata": false,
"column_metadata": false,
"column_metadata_row_height": 8,
"column_metadata_colors": "RdLrBu",
"max_height" : 800,
"width" : target_width,
"heatmap_colors" : "Greens",
"heatmap_font_color" : "black",
"heatmap_part_width" : 0.7,
"column_dendrogram" : false,
"independent_columns" : true,
"metadata_colors" : "Reds",
"highlight_colors" : "Oranges",
"highlighted_rows" : [],
"label_color": "#9E9E9E",
"count_column": false,
"count_column_colors": "Reds",
"min_row_height": false,
"max_row_height": 25,
"max_column_width": 150,
"font": "Helvetica",
"draw_row_ids": false,
"show_export_button": true,
"max_percentile": 100,
"min_percentile": 0,
"middle_percentile": 50,
};
$.extend(_this.settings, settings);
_this.settings.width = (settings.max_width && settings.max_width < target_width)?settings.max_width:_this.settings.width;
_this.settings.heatmap_part_width = (_this.settings.heatmap_part_width>0.9)?0.9:_this.settings.heatmap_part_width;
_this.header_height = 150;
_this.footer_height = 70;
_this.dendrogram_heatmap_distance = 5;
/**
* Default color scales
* @name InCHlib#colors
*/
_this.colors = {
"YlGn": {"start": {"r":255, "g": 255, "b": 204}, "end": {"r": 35, "g": 132, "b": 67}},
"GnBu": {"start": {"r":240, "g": 249, "b": 232}, "end": {"r": 43, "g": 140, "b": 190}},
"BuGn": {"start": {"r":237, "g": 248, "b": 251}, "end": {"r": 35, "g": 139, "b": 69}},
"PuBu": {"start": {"r":241, "g": 238, "b": 246}, "end": {"r": 5, "g": 112, "b": 176}},
"BuPu": {"start": {"r":237, "g": 248, "b": 251}, "end": {"r": 136, "g": 65, "b": 157}},
"RdPu": {"start": {"r":254, "g": 235, "b": 226}, "end": {"r": 174, "g": 1, "b": 126}},
"PuRd": {"start": {"r":241, "g": 238, "b": 246}, "end": {"r": 206, "g": 18, "b": 86}},
"OrRd": {"start": {"r":254, "g": 240, "b": 217}, "end": {"r": 215, "g": 48, "b": 31}},
"Purples2": {"start": {"r":242, "g": 240, "b": 247}, "end": {"r": 106, "g": 81, "b": 163}},
"Blues": {"start": {"r":239, "g": 243, "b": 255}, "end": {"r": 33, "g": 113, "b": 181}},
"Greens": {"start": {"r":237, "g": 248, "b": 233}, "end": {"r": 35, "g": 139, "b": 69}},
"Oranges": {"start": {"r":254, "g": 237, "b": 222}, "end": {"r": 217, "g": 71, "b": 1}},
"Reds": {"start": {"r":254, "g": 229, "b": 217}, "end": {"r": 203, "g": 24, "b": 29}},
"Greys": {"start": {"r":247, "g": 247, "b": 247}, "end": {"r": 82, "g": 82, "b": 82}},
"PuOr": {"start": {"r":230, "g": 97, "b": 1}, "end": {"r": 94, "g": 60, "b": 153}},
"BrBG": {"start": {"r":166, "g": 97, "b": 26}, "end": {"r": 1, "g": 133, "b": 113}},
"RdBu": {"start": {"r":202, "g": 0, "b": 32}, "end": {"r": 5, "g": 113, "b": 176}},
"RdGy": {"start": {"r":202, "g": 0, "b": 32}, "end": {"r": 64, "g": 64, "b": 64}},
"BuYl": {"start": {"r": 5, "g": 113, "b": 176}, "end": {"r": 250, "g": 233, "b": 42}},
"YlOrR": {"start": {"r":255, "g": 255, "b": 178}, "end": {"r": 227, "g": 26, "b": 28}, "middle": {"r": 204, "g": 76, "b": 2}},
"YlOrB": {"start": {"r":255, "g": 255, "b": 212}, "end": {"r": 5, "g": 112, "b": 176}, "middle": {"r": 204, "g": 76, "b": 2}},
"PRGn2": {"start": {"r":123, "g": 50, "b": 148}, "end": {"r": 0, "g": 136, "b": 55}, "middle": {"r":202, "g": 0, "b": 32}},
"PiYG2": {"start": {"r":208, "g": 28, "b": 139}, "end": {"r": 77, "g": 172, "b": 38}, "middle": {"r":255, "g": 255, "b": 178},},
"YlGnBu": {"start": {"r":255, "g": 255, "b": 204}, "end": {"r": 34, "g": 94, "b": 168}, "middle": {"r": 35, "g": 132, "b": 67}},
"RdYlBu": {"start": {"r":215, "g": 25, "b": 28}, "end": {"r": 44, "g": 123, "b": 182}, "middle": {"r":255, "g": 255, "b": 178}},
"RdYlGn": {"start": {"r":215, "g": 25, "b": 28}, "end": {"r": 26, "g": 150, "b": 65}, "middle": {"r":255, "g": 255, "b": 178}},
"BuWhRd": {"start": {"r": 33, "g": 113, "b": 181}, "middle": {"r": 255, "g": 255, "b": 255}, "end": {"r":215, "g": 25, "b": 28}},
"RdLrBu": {"start": {"r":215, "g": 25, "b": 28}, "middle": {"r":254, "g": 229, "b": 217}, "end": {"r": 44, "g": 123, "b": 182}},
"RdBkGr": {"start": {"r":215, "g": 25, "b": 28}, "middle": {"r": 0, "g": 0, "b": 0}, "end": {"r": 35, "g": 139, "b": 69}},
"RdLrGr": {"start": {"r":215, "g": 25, "b": 28}, "middle": {"r":254, "g": 229, "b": 217}, "end": {"r": 35, "g": 139, "b": 69}},
};
/**
* Default kineticjs objects references
* @name InCHlib#objects_ref
*/
_this.objects_ref = {
"tooltip_label": new Kinetic.Label({
opacity: 1,
listening: false,
}),
"tooltip_tag": new Kinetic.Tag({
fill: _this.settings.label_color,
pointerWidth: 10,
pointerHeight: 10,
lineJoin: 'round',
listening: false,
}),
"tooltip_text": new Kinetic.Text({
fontFamily: _this.settings.font,
fontSize: 12,
padding: 8,
fill: 'white',
fontStyle: "bold",
listening: false,
align: "center",
lineHeight: 1.2,
}),
"node": new Kinetic.Line({
stroke: "grey",
strokeWidth: 2,
lineCap: 'sqare',
lineJoin: 'round',
listening: false
}),
"node_rect" : new Kinetic.Rect({
fill: "white",
opacity: 0,
}),
"icon_overlay": new Kinetic.Rect({
width: 32,
height: 32,
opacity: 0,
}),
"heatmap_value": new Kinetic.Text({
fontFamily: _this.settings.font,
fill: _this.settings.heatmap_font_color,
fontStyle: "bold",
listening: false,
}),
"heatmap_line": new Kinetic.Line({
lineCap: 'butt',
value: false,
}),
"column_header": new Kinetic.Text({
fontFamily: _this.settings.font,
fontStyle: "bold",
fill: 'black',
}),
"count": new Kinetic.Text({
fontSize: 10,
fill: "#6d6b6a",
fontFamily: _this.settings.font,
fontStyle: 'bold',
listening: false,
}),
"cluster_overlay": new Kinetic.Rect({
fill: "white",
opacity: 0.5,
}),
"cluster_border": new Kinetic.Line({
stroke: "black",
strokeWidth: 1,
dash: [6,2]
}),
"icon": new Kinetic.Path({
fill: "grey",
}),
"rect_gradient": new Kinetic.Rect({
x: 0,
y: 80,
width: 100,
height: 20,
fillLinearGradientStartPoint: {x: 0, y: 80},
fillLinearGradientEndPoint: {x: 100, y: 80},
stroke: "#D2D2D2",
strokeWidth: "1px"
}),
};
_this.paths_ref = {
"zoom_icon": "M22.646,19.307c0.96-1.583,1.523-3.435,1.524-5.421C24.169,8.093,19.478,3.401,13.688,3.399C7.897,3.401,3.204,8.093,3.204,13.885c0,5.789,4.693,10.481,10.484,10.481c1.987,0,3.839-0.563,5.422-1.523l7.128,7.127l3.535-3.537L22.646,19.307zM13.688,20.369c-3.582-0.008-6.478-2.904-6.484-6.484c0.006-3.582,2.903-6.478,6.484-6.486c3.579,0.008,6.478,2.904,6.484,6.486C20.165,17.465,17.267,20.361,13.688,20.369zM15.687,9.051h-4v2.833H8.854v4.001h2.833v2.833h4v-2.834h2.832v-3.999h-2.833V9.051z",
"unzoom_icon": "M22.646,19.307c0.96-1.583,1.523-3.435,1.524-5.421C24.169,8.093,19.478,3.401,13.688,3.399C7.897,3.401,3.204,8.093,3.204,13.885c0,5.789,4.693,10.481,10.484,10.481c1.987,0,3.839-0.563,5.422-1.523l7.128,7.127l3.535-3.537L22.646,19.307zM13.688,20.369c-3.582-0.008-6.478-2.904-6.484-6.484c0.006-3.582,2.903-6.478,6.484-6.486c3.579,0.008,6.478,2.904,6.484,6.486C20.165,17.465,17.267,20.361,13.688,20.369zM8.854,11.884v4.001l9.665-0.001v-3.999L8.854,11.884z",
"lightbulb": "M15.5,2.833c-3.866,0-7,3.134-7,7c0,3.859,3.945,4.937,4.223,9.499h5.553c0.278-4.562,4.224-5.639,4.224-9.499C22.5,5.968,19.366,2.833,15.5,2.833zM15.5,28.166c1.894,0,2.483-1.027,2.667-1.666h-5.334C13.017,27.139,13.606,28.166,15.5,28.166zM12.75,25.498h5.5v-5.164h-5.5V25.498z"
};
}
InCHlib.prototype._update_user_settings = function(settings){
var updated_settings = {}, key;
for(var i = 0, keys=Object.keys(settings), len = keys.length; i < len; i++){
key = keys[i];
if(_this.user_settings[key] !== undefined && _this.user_settings[key] !== settings[key] && _this.user_settings[key] === true){
updated_settings[key] = false;
}
else if(_this.user_settings[key] === undefined){
updated_settings[key] = settings[key];
}
}
$.extend(_this.settings, updated_settings);
}
/**
* Read data from JSON variable.
*
* @param {object} [variable] Clustering in proper JSON format.
*/
InCHlib.prototype.read_data = function(json){
_this.json = json;
_this.data = _this.json.data;
var settings = {};
if(_this.json["metadata"] !== undefined){
_this.metadata = _this.json.metadata;
settings.metadata = true;
}
else{
settings.metadata = false;
}
if(_this.json["column_dendrogram"] !== undefined){
_this.column_dendrogram = _this.json.column_dendrogram;
settings.column_dendrogram = true;
}
else{
settings.column_dendrogram = false;
}
if(_this.json["column_metadata"] !== undefined){
_this.column_metadata = _this.json.column_metadata;
settings.column_metadata = true;
}
else{
settings.column_metadata = false;
}
_this._update_user_settings(settings);
_this._add_prefix();
}
/**
* Read data from JSON file.
*
* @param {string} [filename] Path to the JSON data file.
*
*/
InCHlib.prototype.read_data_from_file = function(json){
$.ajax({
type: 'GET',
url: json,
dataType: 'json',
success: function(json_file){
_this.read_data(json_file);
},
async: false
});
}
InCHlib.prototype._add_prefix = function(){
_this.data.nodes = _this._add_prefix_to_data(_this.data.nodes);
if(_this.settings.metadata){
var metadata = {};
for(var i = 0, keys = Object.keys(_this.metadata.nodes), len = keys.length; i < len; i++){
id = [_this.settings.target, keys[i]].join("#");
metadata[id] = _this.metadata.nodes[keys[i]];
}
_this.metadata.nodes = metadata;
}
if(_this.column_dendrogram){
_this.column_dendrogram.nodes = _this._add_prefix_to_data(_this.column_dendrogram.nodes);
}
}
InCHlib.prototype._add_prefix_to_data = function(data){
var id, prefixed_data = {};
for(var i = 0, keys = Object.keys(data), len = keys.length; i < len; i++){
id = [_this.settings.target, keys[i]].join("#");
prefixed_data[id] = data[keys[i]];
if(prefixed_data[id]["parent"] !== undefined){
prefixed_data[id].parent = [_this.settings.target, prefixed_data[id].parent].join("#");
}
if(prefixed_data[id]["count"] != 1){
prefixed_data[id].left_child = [_this.settings.target, prefixed_data[id].left_child].join("#");
prefixed_data[id].right_child = [_this.settings.target, prefixed_data[id].right_child].join("#");
}
}
return prefixed_data;
}
InCHlib.prototype._get_root_id = function(nodes){
var root_id;
for(var i = 0, keys = Object.keys(nodes), len = keys.length; i < len; i++){
if(nodes[keys[i]]["parent"] === undefined){
root_id = keys[i];
break;
}
}
return root_id;
}
InCHlib.prototype._get_dimensions = function(){
var dimensions = {"data": 0, "metadata": 0, "overall": 0}, key, keys, i;
for(i = 0, keys = Object.keys(_this.data.nodes), len = keys.length; i < len; i++){
key = keys[i];
if(_this.data.nodes[key].count == 1){
dimensions["data"] = _this.data.nodes[key].features.length;
break;
}
}
if(_this.settings.metadata){
key = Object.keys(_this.metadata.nodes)[0];
dimensions["metadata"] = _this.metadata.nodes[key].length;
}
dimensions["overall"] = dimensions["data"] + dimensions["metadata"];
return dimensions;
}
InCHlib.prototype._get_min_max_middle = function(data){
var i, len;
var min_max_middle = [];
var all = [];
for(i = 0, len = data.length; i<len; i++){
all = all.concat(data[i].filter(function(x){return x !== null}));
}
var len = all.length;
all.sort(function(a,b){return a - b});
min_max_middle.push((_this.settings.min_percentile > 0)?all[_this._hack_round(len*_this.settings.min_percentile/100)]:Math.min.apply(null, all));
min_max_middle.push((_this.settings.max_percentile < 100)?all[_this._hack_round(len*_this.settings.max_percentile/100)]:Math.max.apply(null, all));
min_max_middle.push((_this.settings.middle_percentile != 50)?all[_this._hack_round(len*_this.settings.middle_percentile/100)]:all[_this._hack_round((len-1)/2)]);
return min_max_middle;
}
InCHlib.prototype._get_data_min_max_middle = function(data, axis){
if(axis === undefined){
axis = "column";
}
var i, j, value, len, columns;
var data_length = data[0].length;
if(axis == "column"){
columns = [];
for(i = 0; i<data_length; i++){
columns.push([]);
}
for(i = 0; i<data.length; i++){
for(j = 0; j < data_length; j++){
value = data[i][j];
if(value !== null && value !== undefined){
columns[j].push(value);
}
}
}
}
else{
columns = data.slice(0);
}
var data2descs = {}
var data_min_max_middle = [], min, max, middle;
for(i = 0; i<columns.length; i++){
if(_this._is_number(columns[i][0])){
columns[i] = columns[i].map(parseFloat);
columns[i].sort(function(a,b){return a - b});
len = columns[i].length;
max = (_this.settings.max_percentile < 100)?columns[i][_this._hack_round(len*_this.settings.max_percentile/100)]:Math.max.apply(null, columns[i]);
min = (_this.settings.min_percentile > 0)?columns[i][_this._hack_round(len*_this.settings.min_percentile/100)]:Math.min.apply(null, columns[i]);
middle = (_this.settings.middle_percentile != 50)?columns[i][_this._hack_round(len*_this.settings.middle_percentile/100)]:columns[i][_this._hack_round((len-1)/2)];
data2descs[i] = {"min": min, "max": max, "middle": middle};
}
else{
var hash_object = _this._get_hash_object(columns[i]);
min = 0;
max = _this._hack_size(hash_object)-1;
middle = max/2;
data2descs[i] = {"min": min, "max": max, "middle": middle, "str2num": hash_object};
}
}
return data2descs;
}
InCHlib.prototype._get_hash_object = function(array){
var i, count=0, hash_object = {};
for(i = 0; i<array.length; i++){
if(hash_object[array[i]] === undefined){
hash_object[array[i]] = count;
count++;
}
}
return hash_object;
}
InCHlib.prototype._get_max_length = function(items){
var lengths = items.map(function(x){return (""+x).length});
var max = Math.max.apply(Math, lengths);
return max;
}
InCHlib.prototype._get_max_value_length = function(){
var nodes = _this.data.nodes;
var max_length = 0;
var node_data, key;
for(var i = 0, keys = Object.keys(nodes), len = keys.length; i < len; i++){
key = keys[i];
if(nodes[key].count == 1){
node_data = nodes[key].features;
for(var j = 0, len_2 = node_data.length; j < len_2; j++){
if((""+node_data[j]).length > max_length){
max_length = (""+node_data[j]).length;
}
}
}
}
if(_this.settings.metadata){
nodes = _this.metadata.nodes;
for(var i = 0, keys = Object.keys(nodes), len = keys.length; i < len; i++){
key = keys[i];
node_data = nodes[key];
for(var j = 0, len_2 = node_data.length; j < len_2; j++){
if((""+node_data[j]).length > max_length){
max_length = (""+node_data[j]).length;
}
}
}
}
return max_length;
}
InCHlib.prototype._preprocess_heatmap_data = function(){
var heatmap_array = [], i, j = 0, keys, key, len, data, node;
for(i = 0, keys = Object.keys(_this.data.nodes), len = keys.length; i < len; i++){
key = keys[i];
node = _this.data.nodes[key];
if(node.count == 1){
data = node.features;
heatmap_array.push([key]);
heatmap_array[j].push.apply(heatmap_array[j], data);
if(_this.settings.metadata){
heatmap_array[j].push.apply(heatmap_array[j], _this.metadata.nodes[key]);
}
j++;
}
}
return heatmap_array;
}
InCHlib.prototype._reorder_heatmap = function(column_index){
_this.leaves_y_coordinates = {};
column_index++;
if(_this.ordered_by_index == column_index){
_this.heatmap_array.reverse();
}
else{
if(_this._is_number(_this.heatmap_array[0][column_index])){
_this.heatmap_array.sort(function(a,b){return (a[column_index] == null)?-1:(b[column_index] == null)?1:a[column_index] - b[column_index]});
}
else{
_this.heatmap_array.sort(function(a,b){return (a[column_index] == null)?-1:(b[column_index] == null)?1:(a[column_index] > b[column_index])?1:(a[column_index] < b[column_index])?-1:0});
}
}
var y = _this.pixels_for_leaf/2 + _this.header_height;
for(var i = 0, len = _this.heatmap_array.length; i<len; i++){
_this.leaves_y_coordinates[_this.heatmap_array[i][0]] = y;
y += _this.pixels_for_leaf;
}
_this.ordered_by_index = column_index;
}
/**
* Draw already read data (from file/JSON variable).
*/
InCHlib.prototype.draw = function(){
_this.zoomed_clusters = {"row": [], "column": []};
_this.last_highlighted_cluster = null;
_this.current_object_ids = [];
_this.current_column_ids = [];
_this.heatmap_array = _this._preprocess_heatmap_data();
if(_this.settings.heatmap){
_this.last_column = null;
_this.on_features = {"data":[], "metadata":[]}
_this.dimensions = _this._get_dimensions();
_this.column_metadata_rows = (_this.settings.column_metadata)?_this.column_metadata.features.length:0;
_this.column_metadata_height = _this.column_metadata_rows * _this.settings.column_metadata_row_height;
_this._set_heatmap_settings();
_this._adjust_leaf_size(_this.heatmap_array.length);
}
else{
_this._adjust_horizontal_sizes();
_this.dimensions = {"data": 0, "metadata": 0, "overall": 0};
}
if(_this.settings.column_dendrogram && _this.heatmap_header){
_this.footer_height = 150;
}
_this.stage = new Kinetic.Stage({
container: _this.settings.target,
});
_this.settings.height = _this.heatmap_array.length*_this.pixels_for_leaf+_this.header_height+_this.footer_height;
_this.stage.setWidth(_this.settings.width);
_this.stage.setHeight(_this.settings.height);
_this._draw_stage_layer();
if(_this.settings.dendrogram){
_this.timer = 0;
_this._draw_dendrogram_layers();
_this.root_id = _this._get_root_id(_this.data.nodes);
_this._draw_row_dendrogram(_this.root_id);
if(_this.settings.column_dendrogram && _this.settings.dendrogram){
_this.column_root_id = _this._get_root_id(_this.column_dendrogram.nodes);
_this.nodes2columns = false;
_this.columns_start_index = 0;
_this._draw_column_dendrogram(_this.column_root_id);
}
}
else{
_this.settings.column_dendrogram = false;
_this._reorder_heatmap(0);
_this.ordered_by_index = 0;
}
_this._draw_heatmap();
_this._draw_heatmap_header();
_this._draw_navigation();
_this.highlight_rows(_this.settings.highlighted_rows);
}
InCHlib.prototype._draw_dendrogram_layers = function(){
_this.cluster_layer = new Kinetic.Layer();
_this.dendrogram_hover_layer = new Kinetic.Layer();
_this.stage.add(_this.cluster_layer, _this.dendrogram_hover_layer);
_this.cluster_layer.on("click", function(evt){
_this.unhighlight_cluster();
_this.unhighlight_column_cluster();
_this.trigger("empty_space_onclick", evt);
});
};
InCHlib.prototype._draw_row_dendrogram = function(node_id){
_this.dendrogram_layer = new Kinetic.Layer();
var node = _this.data.nodes[node_id];
var count = node.count;
_this.distance_step = _this.distance/node.distance;
_this.leaves_y_coordinates = {};
_this.objects2leaves = {};
_this._adjust_leaf_size(count);
_this.settings.height = count*_this.pixels_for_leaf+_this.header_height+_this.footer_height+_this.column_metadata_height;
_this.stage.setWidth(_this.settings.width);
_this.stage.setHeight(_this.settings.height);
var current_left_count = 0;
var current_right_count = 0;
var y = _this.header_height + _this.column_metadata_height + _this.pixels_for_leaf/2;
if(node.count > 1){
current_left_count = _this.data.nodes[node.left_child].count;
current_right_count = _this.data.nodes[node.right_child].count;
}
_this._draw_row_dendrogram_node(node_id, node, current_left_count, current_right_count, 0, y);
_this.middle_item_count = (_this.min_item_count+_this.max_item_count)/2;
_this._draw_distance_scale(node.distance);
_this.stage.add(_this.dendrogram_layer);
_this._bind_dendrogram_hover_events(_this.dendrogram_layer);
_this.dendrogram_layer.on("click", function(evt){
_this._dendrogram_layers_click(this, evt);
});
_this.dendrogram_layer.on("mousedown", function(evt){
_this._dendrogram_layers_mousedown(this, evt);
});
_this.dendrogram_layer.on("mouseup", function(evt){
_this._dendrogram_layers_mouseup(this, evt);
});
}
InCHlib.prototype._draw_row_dendrogram_node = function(node_id, node, current_left_count, current_right_count, x, y){
if(node.count != 1){
var node_neighbourhood = _this._get_node_neighbourhood(node, _this.data.nodes);
var right_child = _this.data.nodes[node.right_child];
var left_child = _this.data.nodes[node.left_child];
var y1 = _this._get_y1(node_neighbourhood, current_left_count, current_right_count);
var y2 = _this._get_y2(node_neighbourhood, current_left_count, current_right_count);
var x1 = _this._hack_round(_this.distance - _this.distance_step*node.distance);
x1 = (x1 == 0)? 2: x1;
var x2 = x1;
var left_distance = _this.distance - _this.distance_step*_this.data.nodes[node.left_child].distance;
var right_distance = _this.distance - _this.distance_step*_this.data.nodes[node.right_child].distance;
if(right_child.count == 1){
y2 = y2 + _this.pixels_for_leaf/2;
}
_this.dendrogram_layer.add(_this._draw_horizontal_path(node_id, x1, y1, x2, y2, left_distance, right_distance));
_this._draw_row_dendrogram_node(node.left_child, left_child, current_left_count - node_neighbourhood.left_node.right_count, current_right_count + node_neighbourhood.left_node.right_count, left_distance, y1);
_this._draw_row_dendrogram_node(node.right_child, right_child, current_left_count + node_neighbourhood.right_node.left_count, current_right_count - node_neighbourhood.right_node.left_count, right_distance, y2);
}
else{
var objects = node.objects;
_this.leaves_y_coordinates[node_id] = y;
for(var i = 0, len = objects.length; i<len; i++){
_this.objects2leaves[objects[i]] = node_id;
}
var count = node.objects.length;
if(count<_this.min_item_count){
_this.min_item_count = count;
}
if(count>_this.max_item_count){
_this.max_item_count = count;
}
}
}
InCHlib.prototype._draw_stage_layer = function(){
_this.stage_layer = new Kinetic.Layer();
var stage_rect = new Kinetic.Rect({
x: 0,
y: 0,
width: _this.settings.width,
height: _this.settings.height,
opacity: 0,
});
_this.stage_layer.add(stage_rect);
stage_rect.moveToBottom();
_this.stage.add(_this.stage_layer);
_this.stage_layer.on("click", function(evt){
_this.unhighlight_cluster();
_this.unhighlight_column_cluster();
_this.trigger("empty_space_onclick", evt);
});
}
InCHlib.prototype._draw_column_dendrogram = function(node_id){
_this.column_dendrogram_layer = new Kinetic.Layer();
_this.column_x_coordinates = {};
var node = _this.column_dendrogram.nodes[node_id];
_this.current_column_count = node.count;
_this.vertical_distance = _this.header_height;
_this.vertical_distance_step = _this.vertical_distance/node.distance;
_this.last_highlighted_column_cluster = null;
var current_left_count = _this.column_dendrogram.nodes[node.left_child].count;
var current_right_count = _this.column_dendrogram.nodes[node.right_child].count;
_this._draw_column_dendrogram_node(node_id, node, current_left_count, current_right_count, 0, 0);
_this.stage.add(_this.column_dendrogram_layer);
if(!_this.nodes2columns){
_this.nodes2columns = _this._get_nodes2columns();
}
_this._bind_dendrogram_hover_events(_this.column_dendrogram_layer);
_this.column_dendrogram_layer.on("click", function(evt){
_this._column_dendrogram_layers_click(this, evt);
});
_this.column_dendrogram_layer.on("mousedown", function(evt){
_this._column_dendrogram_layers_mousedown(this, evt);
});
_this.column_dendrogram_layer.on("mouseup", function(evt){
_this._dendrogram_layers_mouseup(this, evt);
});
}
InCHlib.prototype._get_nodes2columns = function(){
var coordinates = [];
var coordinates2nodes = {};
var nodes2columns = {};
var key, value, i;
for(i = 0, keys = Object.keys(_this.column_x_coordinates), len = keys.length; i < len; i++){
key = keys[i];
value = _this.column_x_coordinates[key];
coordinates2nodes[value] = key;
coordinates.push(value);
}
coordinates.sort(function(a,b){return a - b});
for(i = 0, len = coordinates.length; i<len; i++){
nodes2columns[coordinates2nodes[coordinates[i]]] = i;
}
return nodes2columns;
}
InCHlib.prototype._bind_dendrogram_hover_events = function(layer){
layer.on("mouseover", function(evt){
_this._dendrogram_layers_mouseover(this, evt);
});
layer.on("mouseout", function(evt){
_this._dendrogram_layers_mouseout(this, evt);
});
}
InCHlib.prototype._delete_layers = function(to_destroy, to_remove_children){
for(var i = 0, len = to_destroy.length; i < len; i++){
if(to_destroy[i] !== undefined){
to_destroy[i].destroy();
}
}
if(to_remove_children !== undefined){
for(var i = 0, len = to_remove_children.length; i < len; i++){
to_remove_children[i].removeChildren();
to_remove_children[i].draw();
}
}
}
InCHlib.prototype._delete_all_layers = function(){
_this.stage.destroyChildren();
}
InCHlib.prototype._adjust_leaf_size = function(leaves){
_this.pixels_for_leaf = (_this.settings.max_height-_this.header_height-_this.footer_height-_this.column_metadata_height-5)/leaves;
if(_this.pixels_for_leaf > 2*_this.pixels_for_dimension){
_this.pixels_for_leaf = 2*_this.pixels_for_dimension;
}
if(_this.pixels_for_leaf > _this.settings.max_row_height){
_this.pixels_for_leaf = _this.settings.max_row_height;
}
if(_this.settings.min_row_height > _this.pixels_for_leaf){
_this.pixels_for_leaf = _this.settings.min_row_height;
}
}
InCHlib.prototype._adjust_horizontal_sizes = function(dimensions){
if(dimensions === undefined){
dimensions = _this._get_visible_count();
}
_this.right_margin = 100;
if(_this.settings.dendrogram){
_this.heatmap_width = (_this.settings.width - _this.right_margin - _this.dendrogram_heatmap_distance)*_this.settings.heatmap_part_width;
_this.distance = _this.settings.width - _this.heatmap_width - _this.right_margin;
_this.heatmap_distance = _this.distance + _this.dendrogram_heatmap_distance;
}
else{
_this.heatmap_width = _this.settings.width - _this.right_margin;
_this.distance = _this.right_margin/2;
_this.heatmap_distance = _this.distance;
}
_this.pixels_for_dimension = dimensions?_this.heatmap_width/dimensions:0;
if(_this.settings.max_column_width && _this.settings.max_column_width < _this.pixels_for_dimension){
_this.pixels_for_dimension = _this.settings.max_column_width;
_this.heatmap_width = dimensions*_this.pixels_for_dimension;
if(_this.settings.dendrogram){
_this.distance = _this.settings.width - _this.heatmap_width - _this.right_margin - _this.dendrogram_heatmap_distance;
_this.heatmap_distance = _this.distance + _this.dendrogram_heatmap_distance;
}
else{
_this.distance = _this._hack_round((_this.settings.width - _this.heatmap_width)/2);
_this.right_margin = _this.distance;
_this.heatmap_distance = _this.distance;
}
}
}
InCHlib.prototype._set_color_settings = function(){
var data = [];
for(i = 0, keys = Object.keys(_this.data.nodes), len = keys.length; i < len; i++){
node = _this.data.nodes[keys[i]];
if(node.count == 1){
data.push(node.features);
};
}
_this.data_descs = {};
if(_this.settings.independent_columns){
_this.data_descs = _this._get_data_min_max_middle(data);
}
else{
var min_max_middle = _this._get_min_max_middle(data);
for(i = 0; i < _this.dimensions["data"]; i++){
_this.data_descs[i] = {"min": min_max_middle[0], "max": min_max_middle[1], "middle": min_max_middle[2]};
}
}
if(_this.settings.metadata){
var metadata = [];
for(i = 0, keys = Object.keys(_this.metadata.nodes), len = keys.length; i < len; i++){
metadata.push(_this.metadata.nodes[keys[i]]);
}
_this.metadata_descs = _this._get_data_min_max_middle(metadata);
}
}
InCHlib.prototype._set_heatmap_settings = function(){
var i, keys, key, len, node;
_this.header = [];
for(i = 0; i<_this.dimensions["overall"]; i++){
_this.header.push("");
}
_this.heatmap_header = false;
_this.metadata_header = false;
_this.current_label = null;
_this._set_color_settings();
if(_this.data.feature_names !== undefined){
_this.heatmap_header = _this.data.feature_names;
for(i=0; i<_this.dimensions["data"]; i++){
_this.header[i] = _this.heatmap_header[i];
}
}
if(_this.settings.metadata){
if(_this.metadata.feature_names){
_this.metadata_header = _this.metadata.feature_names;
for(i=0; i<_this.dimensions["metadata"]; i++){
_this.header[_this.dimensions["data"]+i] = _this.metadata_header[i];
}
}
}
if(_this.settings.column_metadata){
if(_this.column_metadata.feature_names !== undefined){
_this.column_metadata_header = _this.column_metadata.feature_names;
}
}
if(_this.settings.count_column){
_this.max_item_count = 1;
_this.min_item_count = 1;
_this.dimensions["overall"]++;
_this.header.push("Count");
}
_this.features = {};
for(i=0; i<_this.dimensions["overall"]; i++){
_this.features[i] = true;
}
_this._set_on_features();
_this._adjust_horizontal_sizes();
_this.top_heatmap_distance = _this.header_height + _this.column_metadata_height + _this.settings.column_metadata_row_height/2;
}
InCHlib.prototype._set_on_features = function(features){
var key;
if(features === undefined){
var features = [];
for(var i = 0, keys = Object.keys(_this.features), len = keys.length; i < len; i++){
key = keys[i];
if(_this.features[key]){
features.push(key);
}
}
}
_this.on_features = {"data":[], "metadata":[], "count_column":[]}
for(var i = 0, len = features.length; i < len; i++){
key = features[i];
if(key < _this.dimensions["data"]){
_this.on_features["data"].push(key);
}
else if(key <= _this.dimensions["data"] + _this.dimensions["metadata"] - 1){
_this.on_features["metadata"].push(key-_this.dimensions["data"]);
}
else{
_this.on_features["count_column"].push(0);
}
}
}
InCHlib.prototype._draw_heatmap = function(){
if(!_this.settings.heatmap || _this.dimensions["overall"]==0){
return;
}
var heatmap_row, row_id, col_number, col_label, row_values, y;
_this.heatmap_layer = new Kinetic.Layer();
_this.heatmap_overlay = new Kinetic.Layer();
_this.highlighted_rows_y = [];
_this.current_draw_values = true;
_this.max_value_length = _this._get_max_value_length();
_this.value_font_size = _this._get_font_size(_this.max_value_length, _this.pixels_for_dimension, _this.pixels_for_leaf, 12);
if(_this.value_font_size < 4){
_this.current_draw_values = false;
}
var x1 = _this.heatmap_distance;
var current_leaves_y = [];
for(var i = 0, keys = Object.keys(_this.leaves_y_coordinates), len = keys.length; i < len; i++){
key = keys[i];
y = _this.leaves_y_coordinates[key];
heatmap_row = _this._draw_heatmap_row(key, x1, y);
_this.heatmap_layer.add(heatmap_row);
current_leaves_y.push([key, y]);
_this._bind_row_events(heatmap_row);
}
if(_this.settings.column_metadata){
_this.column_metadata_descs = _this._get_data_min_max_middle(_this.column_metadata.features, "row");
y1 = _this.header_height + 0.5*_this.settings.column_metadata_row_height;
for(var i = 0, len = _this.column_metadata.features.length; i < len; i++){
heatmap_row = _this._draw_column_metadata_row(_this.column_metadata.features[i], i, x1, y1);
_this.heatmap_layer.add(heatmap_row);
_this._bind_row_events(heatmap_row);
y1 = y1 + _this.settings.column_metadata_row_height;
}
}
if(_this.settings.draw_row_ids){
_this._draw_row_ids(current_leaves_y);
}
_this.highlighted_rows_layer = new Kinetic.Layer();
_this.stage.add(_this.heatmap_layer, _this.heatmap_overlay, _this.highlighted_rows_layer);
_this.highlighted_rows_layer.moveToTop();
_this.row_overlay = _this.objects_ref.heatmap_line.clone();
_this.column_overlay = _this.objects_ref.heatmap_line.clone();
_this.heatmap_layer.on("mouseleave", function(evt){
_this.last_header = null;
_this.heatmap_overlay.destroyChildren();
_this.heatmap_overlay.draw();
_this.trigger("heatmap_onmouseout", evt);
});
}
InCHlib.prototype._draw_heatmap_row = function(node_id, x1, y1){
var node = _this.data.nodes[node_id];
var row = new Kinetic.Group({id:node_id});
var x2, y2, color, line, value, text, text_value, col_index;
for (var i = 0, len = _this.on_features["data"].length; i < len; i++){
col_index = _this.on_features["data"][i];
x2 = x1 + _this.pixels_for_dimension;
y2 = y1;
value = node.features[col_index];
if(value !== null){
color = _this._get_color_for_value(value, _this.data_descs[col_index]["min"], _this.data_descs[col_index]["max"], _this.data_descs[col_index]["middle"], _this.settings.heatmap_colors);
line = _this.objects_ref.heatmap_line.clone({
stroke: color,
points: [x1, y1, x2, y2],
value: value,
column: ["d", col_index].join("_"),
strokeWidth: _this.pixels_for_leaf,
});
row.add(line);
if(_this.current_draw_values){
text = _this.objects_ref.heatmap_value.clone({
x: _this._hack_round((x1 + x2)/2-(""+value).length*(_this.value_font_size/4)),
y: _this._hack_round(y1-_this.value_font_size/2),
fontSize: _this.value_font_size,
text: value,
});
row.add(text);
}
}
x1 = x2;
}
if(_this.settings.metadata){
var metadata = _this.metadata.nodes[node_id];
if(metadata !== undefined){
for (var i = 0, len = _this.on_features["metadata"].length; i < len; i++){
col_index = _this.on_features["metadata"][i];
value = metadata[col_index];
x2 = x1 + _this.pixels_for_dimension;
y2 = y1;
if(value !== null && value !== undefined){
text_value = value;
if(_this.metadata_descs[col_index]["str2num"] !== undefined){
value = _this.metadata_descs[col_index]["str2num"][value];
}
color = _this._get_color_for_value(value, _this.metadata_descs[col_index]["min"], _this.metadata_descs[col_index]["max"], _this.metadata_descs[col_index]["middle"], _this.settings.metadata_colors);
line = _this.objects_ref.heatmap_line.clone({
stroke: color,
points: [x1, y1, x2, y2],
value: text_value,
column: ["m", col_index].join("_"),
strokeWidth: _this.pixels_for_leaf,
});
row.add(line);
if(_this.current_draw_values){
text = _this.objects_ref.heatmap_value.clone({
text: text_value,
fontSize: _this.value_font_size,
});
width = text.getWidth();
x = _this._hack_round((x1+x2)/2-width/2);
y = _this._hack_round(y1-_this.value_font_size/2);
text.position({x:x, y:y});
row.add(text);
}
}
x1 = x2;
}
}
}
if(_this.settings.count_column && _this.features[_this.dimensions["overall"]-1]){
x2 = x1 + _this.pixels_for_dimension;
var count = node.objects.length;
color = _this._get_color_for_value(count, _this.min_item_count, _this.max_item_count, _this.middle_item_count, _this.settings.count_column_colors);
line = _this.objects_ref.heatmap_line.clone({
stroke: color,
points: [x1, y1, x2, y2],
value: count,
column: "Count",
strokeWidth: _this.pixels_for_leaf,
});
row.add(line);
if(_this.current_draw_values){
text = _this.objects_ref.heatmap_value.clone({
text: count,
});
width = text.getWidth();
x = _this._hack_round((x1+x2)/2-width/2);
y = _this._hack_round(y1-_this.value_font_size/2);
text.position({x:x, y:y});
row.add(text);
}
}
return row;
}
InCHlib.prototype._draw_column_metadata_row = function(data, row_index, x1, y1){
var row = new Kinetic.Group({"class": "column_metadata"});
var x2, y2, color, line, value, text, text_value, width, col_index;
var str2num = (_this.column_metadata_descs[row_index]["str2num"] === undefined)?false:true;
for (var i = 0, len = _this.on_features["data"].length; i < len; i++){
col_index = _this.on_features["data"][i];
value = data[col_index];
text_value = value;
if(str2num){
value = _this.column_metadata_descs[row_index]["str2num"][value];
}
color = _this._get_color_for_value(value, _this.column_metadata_descs[row_index]["min"], _this.column_metadata_descs[row_index]["max"], _this.column_metadata_descs[row_index]["middle"], _this.settings.column_metadata_colors);
x2 = x1 + _this.pixels_for_dimension;
y2 = y1;
line = _this.objects_ref.heatmap_line.clone({
strokeWidth: _this.settings.column_metadata_row_height,
stroke: color,
value: text_value,
points: [x1, y1, x2, y2],
column: ["cm", row_index].join("_"),
});
row.add(line);
x1 = x2;
}
return row;
}
InCHlib.prototype._bind_row_events = function(row){
row.on("mouseenter", function(evt){
_this._row_mouseenter(evt);
});
row.on("mouseleave", function(evt){
_this._row_mouseleave(evt);
});
row.on("mouseover", function(evt){
_this._draw_col_label(evt);
});
row.on("mouseout", function(evt){
_this.heatmap_overlay.find("#col_label")[0].destroy();
});
row.on("click", function(evt){
var row_id = evt.target.parent.attrs.id;
if(evt.target.parent.attrs.class !== "column_metadata"){
var items = _this.data.nodes[row_id].objects;
var item_ids = [];
for(i = 0; i < items.length; i++){
item_ids.push(items[i]);
}
_this.trigger("row_onclick", item_ids, evt);
}
});
}
InCHlib.prototype._draw_row