Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c2265a4
commit 1810bf0
Showing
2 changed files
with
395 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* Treemap Squared 0.5 - Treemap Charting library | ||
* | ||
* http://treemapsquared.github.com | ||
* | ||
* Copyright (c) 2012 Imran Ghory (imranghory@gmail.com) | ||
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. | ||
*/ | ||
|
||
(function() { | ||
Treemap.draw = function(){ | ||
function isArray(arr) { | ||
return arr && arr.constructor === Array; | ||
} | ||
|
||
function isFunction(func) { | ||
return func && func.constructor === Function; | ||
} | ||
|
||
function drawTreemap(paper, treemapNodes, labels, styles, index) { | ||
var i,j, newindex; | ||
console.log("drawing: ") | ||
console.log(arguments) | ||
|
||
if(isArray(treemapNodes[0][0])) { | ||
for(i=0; i<treemapNodes.length; i++) { | ||
newIndex = index.slice(); | ||
newIndex.push(i); | ||
console.log(newIndex); | ||
drawTreemap(paper, treemapNodes[i],labels, styles, newIndex); | ||
} | ||
} else { | ||
for(i=0; i<treemapNodes.length; i++) { | ||
newindex = index.slice(); | ||
newindex.push(i); | ||
|
||
// figure out the matching label using the index | ||
label = labels; | ||
for(j=0; j<newindex.length; j++){ | ||
label = label[newindex[j]]; | ||
} | ||
|
||
styles['draw'](treemapNodes[i], label, newindex); | ||
} | ||
} | ||
} | ||
|
||
|
||
function draw(element, width, height, data, labels, styles) { | ||
var paper, background, data, nodes, labelFormatter, boxDrawer; | ||
styles = (typeof styles === "undefined") ? [] : styles; | ||
|
||
/* create some default style functions */ | ||
|
||
// This label formatter calculates a font-size based upon | ||
// average label length and the size of the box the label is | ||
// going into. The maximum font size is set to 20px. | ||
labelFormatter = function () { | ||
averageLabelSize = totalLabelLength(labels) / countLabels(labels); | ||
|
||
// total length of labels (i.e [["Italy"],["Spain", "Greece"]] -> 16) | ||
function totalLabelLength(arr) { | ||
var i, total = 0; | ||
if(isArray(arr[0])) { | ||
for(i=0; i<arr.length; i++) { | ||
total += totalLabelLength(arr[i]) | ||
} | ||
} else { | ||
for (i = 0; i<arr.length; i++){ | ||
total += arr[i].length; | ||
} | ||
} | ||
return total; | ||
} | ||
|
||
// count of labels (i.e [["Italy"],["Spain", "Greece"]] -> 3) | ||
function countLabels(arr) { | ||
var i, total = 0; | ||
if(isArray(arr[0])) { | ||
for(i=0; i<arr.length; i++) { | ||
total += countLabels(arr[i]) | ||
} | ||
} else { | ||
for (i = 0; i<arr.length; i++){ | ||
total += 1; | ||
} | ||
} | ||
return total; | ||
} | ||
|
||
function fontSize(width) { | ||
return Math.min( width / (averageLabelSize), 20); | ||
} | ||
|
||
function style(coordinates, index) { | ||
return { "fill" : "#FCFCFC", "font-size": fontSize(coordinates[2] - coordinates[0])} | ||
} | ||
|
||
return style; | ||
}(); | ||
|
||
|
||
// default box & label drawing routine - in most cases this default one in combination with the other style | ||
// will suffice. Only if you're doing something complex and want to rewrite how the treemap gets drawn | ||
// would you replace this. | ||
boxDrawer = function () { | ||
function drawbox(coordinates, label, newindex) { | ||
var x1=coordinates[0], y1=coordinates[1], x2=coordinates[2], y2=coordinates[3]; | ||
|
||
// draw box | ||
box = paper.rect(x1, y1, x2 - x1, y2 - y1); | ||
|
||
if (isFunction(styles['box'])) { | ||
box.attr(styles['box'](coordinates, newindex)); | ||
} else { | ||
box.attr(styles['box']); | ||
} | ||
|
||
// draw labels | ||
var text = paper.text((x1 + x2) / 2, (y1 + y2) / 2, label); | ||
|
||
if (isFunction(styles['label'])) { | ||
text.attr(styles['label'](coordinates, newindex)); | ||
} else { | ||
text.attr(styles['label']); | ||
} | ||
} | ||
return drawbox; | ||
}(); | ||
|
||
// default style for boxes | ||
boxFormatter = { "stroke": "#FEFEFE", "fill" : "green", "fill-opacity" : "50%"}; | ||
|
||
styles['background'] = (typeof styles['background'] === "undefined") ? {fill: "green"} : styles['background']; | ||
styles['label'] = (typeof styles['label'] === "undefined") ? labelFormatter : styles['label']; | ||
styles['box'] = (typeof styles['box'] === "undefined") ? boxFormatter : styles['box']; | ||
styles['draw'] = (typeof styles['draw'] === "undefined") ? boxDrawer : styles['draw']; | ||
|
||
// create our canvas and style the background | ||
paper = new Raphael(element, width, height); | ||
background = paper.rect(0, 0, width, height); | ||
background.attr(styles['background']); | ||
|
||
// generate our treemap from our data | ||
nodes = Treemap.generate(data, width, height); | ||
|
||
// draw the generated treemap | ||
drawTreemap(paper, nodes, labels, styles, []); | ||
} | ||
|
||
return draw; | ||
}(); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
/* | ||
* treemap-squarify.js - open source implementation of squarified treemaps | ||
* | ||
* Treemap Squared 0.5 - Treemap Charting library | ||
* | ||
* http://treemapsquared.github.com | ||
* | ||
* Copyright (c) 2012 Imran Ghory (imranghory@gmail.com) | ||
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. | ||
* | ||
* | ||
* Implementation of the squarify treemap algorithm described in: | ||
* | ||
* Bruls, Mark; Huizing, Kees; van Wijk, Jarke J. (2000), "Squarified treemaps" | ||
* in de Leeuw, W.; van Liere, R., Data Visualization 2000: | ||
* Proc. Joint Eurographics and IEEE TCVG Symp. on Visualization, Springer-Verlag, pp. 33–42. | ||
* | ||
* Paper is available online at: http://www.win.tue.nl/~vanwijk/stm.pdf | ||
* | ||
* The code in this file is completeley decoupled from the drawing code so it should be trivial | ||
* to port it to any other vector drawing library. Given an array of datapoints this library returns | ||
* an array of cartesian coordinates that represent the rectangles that make up the treemap. | ||
* | ||
* The library also supports multidimensional data (nested treemaps) and performs normalization on the data. | ||
* | ||
* See the README file for more details. | ||
*/ | ||
|
||
var Treemap = {}; | ||
|
||
(function() { | ||
Treemap.generate = function(){ | ||
|
||
function Container(xoffset, yoffset, width, height) { | ||
this.xoffset = xoffset; // offset from the the top left hand corner | ||
this.yoffset = yoffset; // ditto | ||
this.height = height; | ||
this.width = width; | ||
|
||
this.shortestEdge = function () { | ||
return Math.min(this.height, this.width); | ||
}; | ||
|
||
// getCoordinates - for a row of boxes which we've placed | ||
// return an array of their cartesian coordinates | ||
this.getCoordinates = function (row) { | ||
var coordinates = []; | ||
var subxoffset = this.xoffset, subyoffset = this.yoffset; //our offset within the container | ||
var areawidth = sumArray(row) / this.height; | ||
var areaheight = sumArray(row) / this.width; | ||
var i; | ||
|
||
if (this.width >= this.height) { | ||
for (i = 0; i < row.length; i++) { | ||
coordinates.push([subxoffset, subyoffset, subxoffset + areawidth, subyoffset + row[i] / areawidth]); | ||
subyoffset = subyoffset + row[i] / areawidth; | ||
} | ||
} else { | ||
for (i = 0; i < row.length; i++) { | ||
coordinates.push([subxoffset, subyoffset, subxoffset + row[i] / areaheight, subyoffset + areaheight]); | ||
subxoffset = subxoffset + row[i] / areaheight; | ||
} | ||
} | ||
return coordinates; | ||
}; | ||
|
||
// cutArea - once we've placed some boxes into an row we then need to identify the remaining area, | ||
// this function takes the area of the boxes we've placed and calculates the location and | ||
// dimensions of the remaining space and returns a container box defined by the remaining area | ||
this.cutArea = function (area) { | ||
var newcontainer; | ||
|
||
if (this.width >= this.height) { | ||
var areawidth = area / this.height; | ||
var newwidth = this.width - areawidth; | ||
newcontainer = new Container(this.xoffset + areawidth, this.yoffset, newwidth, this.height); | ||
} else { | ||
var areaheight = area / this.width; | ||
var newheight = this.height - areaheight; | ||
newcontainer = new Container(this.xoffset, this.yoffset + areaheight, this.width, newheight); | ||
} | ||
return newcontainer; | ||
}; | ||
} | ||
|
||
|
||
|
||
// normalize - the Bruls algorithm assumes we're passing in areas that nicely fit into our | ||
// container box, this method takes our raw data and normalizes the data values into | ||
// area values so that this assumption is valid. | ||
function normalize(data, area) { | ||
var normalizeddata = []; | ||
var sum = sumArray(data); | ||
var multiplier = area / sum; | ||
var i; | ||
|
||
for (i = 0; i < data.length; i++) { | ||
normalizeddata[i] = data[i] * multiplier; | ||
} | ||
return normalizeddata; | ||
} | ||
|
||
// treemapMultidimensional - takes multidimensional data (aka [[23,11],[11,32]] - nested array) | ||
// and recursively calls itself using treemapSingledimensional | ||
// to create a patchwork of treemaps and merge them | ||
function treemapMultidimensional(data, width, height, xoffset, yoffset) { | ||
xoffset = (typeof xoffset === "undefined") ? 0 : xoffset; | ||
yoffset = (typeof yoffset === "undefined") ? 0 : yoffset; | ||
|
||
var mergeddata = []; | ||
var mergedtreemap; | ||
var results = []; | ||
|
||
if(isArray(data[0])) { // if we've got more dimensions of depth | ||
for(i=0; i<data.length; i++) { | ||
mergeddata[i] = sumMultidimensionalArray(data[i]); | ||
} | ||
mergedtreemap = treemapSingledimensional(mergeddata, width, height, xoffset, yoffset) | ||
|
||
for(i=0; i<data.length; i++) { | ||
results.push(treemapMultidimensional(data[i], mergedtreemap[i][2] - mergedtreemap[i][0], mergedtreemap[i][3] - mergedtreemap[i][1], mergedtreemap[i][0], mergedtreemap[i][1])); | ||
} | ||
} else { | ||
results = treemapSingledimensional(data,width,height, xoffset, yoffset); | ||
} | ||
return results; | ||
} | ||
|
||
|
||
|
||
// treemapSingledimensional - simple wrapper around squarify | ||
function treemapSingledimensional(data, width, height, xoffset, yoffset) { | ||
xoffset = (typeof xoffset === "undefined") ? 0 : xoffset; | ||
yoffset = (typeof yoffset === "undefined") ? 0 : yoffset; | ||
|
||
var rawtreemap = squarify(normalize(data, width * height), [], new Container(xoffset, yoffset, width, height), []); | ||
return flattenTreemap(rawtreemap) | ||
} | ||
|
||
// flattenTreemap - squarify implementation returns an array of arrays of coordinates | ||
// because we have a new array everytime we switch to building a new row | ||
// this converts it into an array of coordinates. | ||
function flattenTreemap(rawtreemap) { | ||
var flattreemap = []; | ||
var i, j; | ||
|
||
for (i = 0; i < rawtreemap.length; i++) { | ||
for (j = 0; j < rawtreemap[i].length; j++) { | ||
flattreemap.push(rawtreemap[i][j]); | ||
} | ||
} | ||
return flattreemap; | ||
} | ||
|
||
// squarify - as per the Bruls paper | ||
// plus coordinates stack and containers so we get | ||
// usable data out of it | ||
function squarify(data, currentrow, container, stack) { | ||
var length; | ||
var nextdata; | ||
|
||
if (data.length === 0) { | ||
stack.push(container.getCoordinates(currentrow)); | ||
return; | ||
} | ||
|
||
length = container.shortestEdge(); | ||
nextdatapoint = data[0]; | ||
|
||
if (improvesRatio(currentrow, nextdatapoint, length)) { | ||
currentrow.push(nextdatapoint); | ||
squarify(data.slice(1), currentrow, container, stack); | ||
} else { | ||
newcontainer = container.cutArea(sumArray(currentrow), stack); | ||
stack.push(container.getCoordinates(currentrow)); | ||
squarify(data, [], newcontainer, stack); | ||
} | ||
return stack; | ||
} | ||
|
||
// improveRatio - implements the worse calculation and comparision as given in Bruls | ||
// (note the error in the original paper; fixed here) | ||
function improvesRatio(currentrow, nextnode, length) { | ||
var newrow; | ||
|
||
if (currentrow.length === 0) { | ||
return true; | ||
} | ||
|
||
newrow = currentrow.slice(); | ||
newrow.push(nextnode); | ||
|
||
var currentratio = calculateRatio(currentrow, length); | ||
var newratio = calculateRatio(newrow, length); | ||
|
||
// the pseudocode in the Bruls paper has the direction of the comparission | ||
// wrong, this is the correct one. | ||
return currentratio >= newratio; | ||
} | ||
|
||
// calculateRatio - calculates the maximum width to height ratio of the | ||
// boxes in this row | ||
function calculateRatio(row, length) { | ||
var min = Math.min.apply(Math, row); | ||
var max = Math.max.apply(Math, row); | ||
var sum = sumArray(row); | ||
return Math.max(Math.pow(length, 2) * max / Math.pow(sum, 2), Math.pow(sum, 2) / (Math.pow(length, 2) * min)); | ||
} | ||
|
||
// isArray - checks if arr is an array | ||
function isArray(arr) { | ||
return arr && arr.constructor === Array; | ||
} | ||
|
||
// sumArray - sums a single dimensional array | ||
function sumArray(arr) { | ||
var sum = 0; | ||
var i; | ||
|
||
for (i = 0; i < arr.length; i++) { | ||
sum += arr[i]; | ||
} | ||
return sum; | ||
} | ||
|
||
// sumMultidimensionalArray - sums the values in a nested array (aka [[0,1],[[2,3]]]) | ||
function sumMultidimensionalArray(arr) { | ||
var i, total = 0; | ||
|
||
if(isArray(arr[0])) { | ||
for(i=0; i<arr.length; i++) { | ||
total += sumMultidimensionalArray(arr[i]) | ||
} | ||
} else { | ||
total = sumArray(arr) | ||
} | ||
return total; | ||
} | ||
|
||
return treemapMultidimensional; | ||
}(); | ||
})(); |