Last active
January 10, 2018 18:42
-
-
Save SumNeuron/d4744240642294c1e529947192fa8b32 to your computer and use it in GitHub Desktop.
StackOverflow Help Request: Heatmap with Lasso and Dendogram
This file contains hidden or 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
/* | |
NOTE: This code has the assumption that either the chart being produced with have | |
a dendrogram and labels (e.g. next to the cells in a heat map) OR that the chart | |
will have visible axes and labels along the axes. Thus in the config | |
if | |
config.percentElementsTake.axes.x != 0 | |
then | |
config.percentElementsTake.dendogram.x = 0 | |
and vis versa | |
*/ | |
/* | |
A prototype function to get the absolute position of an element | |
in the svg regardless of its relative window. This is used for the lasso tool. | |
*/ | |
d3.selection.prototype.absolutePosition = function() { | |
var el = this.node(); | |
var elPos = el.getBoundingClientRect(); | |
var vpPos = getVpPos(el); | |
function getVpPos(el) { | |
if(el.parentElement.tagName === 'svg') { | |
return el.parentElement.getBoundingClientRect(); | |
} | |
return getVpPos(el.parentElement); | |
} | |
return { | |
top: elPos.top - vpPos.top, | |
left: elPos.left - vpPos.left, | |
width: elPos.width, | |
bottom: elPos.bottom - vpPos.top, | |
height: elPos.height, | |
right: elPos.right - vpPos.left | |
}; | |
}; | |
/* | |
A hard-coded self-made lasso icon to stand place as a temporary improvement over | |
an empty square. | |
*/ | |
lassoIconSvg = '<g class="lassoIconGroup">\ | |
<g transform="matrix(1.131,-0.30305,0.256192,0.956123,-7.16519,1.94122)">\ | |
<circle cx="12.739" cy="10.052" r="7.237" style="fill:none;stroke:rgb(35,31,32);stroke-width:0.38px;"/>\ | |
</g>\ | |
<g transform="matrix(1,0,0,1,-3.9849,-0.558624)">\ | |
<g transform="matrix(0.229548,0,0,0.229548,4.97755,13.524)">\ | |
<circle cx="12.739" cy="10.052" r="7.237" style="fill:none;stroke:rgb(35,31,32);stroke-width:1.82px;"/>\ | |
</g>\ | |
<g transform="matrix(1,0,0,1,0.10204,0)">\ | |
<path d="M6.641,17.018C6.641,17.018 9.458,19.952 5.388,19.952" style="fill:none;stroke:rgb(35,31,32);stroke-width:0.42px;"/>\ | |
</g>\ | |
</g>\ | |
<g transform="matrix(1.11933,-0.646247,0.646247,1.11933,-13.8193,6.95008)">\ | |
<path d="M15.504,9.879L17.966,17.243L13.041,17.243L15.504,9.879Z" style="stroke:black;stroke-width:0.32px;"/>\ | |
</g>\ | |
<g transform="matrix(1.49555,-0.863456,0.236964,0.410433,-12.0893,23.4167)">\ | |
<path d="M15.504,9.879L17.966,17.243L13.041,17.243L15.504,9.879Z" style="fill:white;stroke:white;stroke-width:0.33px;"/>\ | |
</g>\ | |
</g>' | |
// For matter of clarity we need to establish a naming convention for what labels | |
// are horizontally oriented but are vertically stacked (labels on the y axis) | |
// and which are vertically (or slanted) oriented but are horizontally spaced (x axis). | |
// Let the former be y axis labels and the latter x axis labels | |
barConfig = { | |
svg: { | |
id: "barchartSVG", | |
width: 500, | |
height: 500 | |
}, | |
percentElementsTake : { | |
axes: { | |
x: 0.1, | |
y: 0.1 | |
}, | |
buttons: 0.0, | |
dendogram: { | |
x: 0.0, | |
y: 0.0 | |
}, | |
labels: { | |
x: 0.1, | |
y: 0.1 | |
}, | |
legend: 0.0, | |
title: 0.05, | |
toolbar: 0.0, | |
tooltip: 0.12, | |
spaceBetween: { | |
x: 0.02, | |
y: 0.02 | |
} | |
} | |
} | |
// This is the config for a single heatmap | |
config = { | |
svg: { | |
id: "heatmapSVG", | |
width: 500, | |
height: 500 | |
}, | |
percentElementsTake : { | |
axes: { | |
x: 0.0, | |
y: 0.0 | |
}, | |
buttons: 0.0, | |
dendogram: { | |
x: 0.1, | |
y: 0.1 | |
}, | |
labels: { | |
x: 0.1, | |
y: 0.1 | |
}, | |
legend: 0.1, | |
title: 0.05, | |
toolbar: 0.05, | |
tooltip: 0.12, | |
spaceBetween: { | |
x: 0.02, | |
y: 0.02 | |
} | |
}, | |
linkedSVGS: [ | |
function( subdata ) { | |
makeLinkedBarChart(barConfig, subdata, 'meta', "sex") | |
} | |
] | |
} | |
// global variables of some use | |
DEBUG = true; | |
LASSO = false; | |
function configurateHeatmap(config) { | |
// select svg | |
svg = d3.select("svg#"+config.svg.id) | |
if (svg.empty()) { | |
if (DEBUG) { | |
console.log("ERROR\n\tconfigurateHeatmap(config): svg selection is empty") | |
} | |
} | |
svg.style("width", config.svg.width) | |
svg.style("height", config.svg.height) | |
} | |
function configurateSVG(config) { | |
// select svg | |
svg = d3.select("svg#"+config.svg.id) | |
if (svg.empty()) { | |
if (DEBUG) { | |
console.log("ERROR\n\tconfigurateHeatmap(config): svg selection is empty") | |
} | |
} | |
svg.style("width", config.svg.width) | |
svg.style("height", config.svg.height) | |
} | |
// get the heatmap labels acording to the keys in which they are stored | |
function getHeatmapLabels(data, xAxisKey, yAxisKey) { | |
// store unique labels for each access | |
xAxisLabels = [] | |
yAxisLabels = [] | |
data.map( | |
function( element ) | |
{ | |
curYKey = element[yAxisKey] | |
curXKey = element[xAxisKey] | |
if ( yAxisLabels.indexOf(curYKey) == -1 ) { | |
yAxisLabels.push( curYKey ) | |
} | |
if ( xAxisLabels.indexOf(curXKey) == -1 ) { | |
xAxisLabels.push( curXKey ) | |
} | |
} | |
) | |
return {"xAxis": xAxisLabels, "yAxis": yAxisLabels} | |
} | |
function getUnique(array) { | |
// store unique labels for each access | |
unique = [] | |
array.map( | |
function( element ){ | |
if ( unique.indexOf(element) == -1 ) { | |
unique.push( element ) | |
} | |
}) | |
return unique | |
} | |
// number of cells wide x number of cells high | |
function getheatmapCellDimensions(xAxisLabels, yAxisLabels) { | |
return {x: xAxisLabels.length, y: yAxisLabels.length} | |
} | |
function getMetaData(data, metaKey, metaSubkey) { | |
metaData = [] | |
for (var i = 0; i < data.length; i++) { | |
metaData.push(data[i][metaKey][metaSubkey]) | |
} | |
return metaData | |
} | |
function tally( array ) { | |
tallies = {} | |
array.map( function ( element ) { | |
if ( d3.keys(tallies).indexOf(element) == -1 ) { | |
tallies[element] = 1 | |
} else { | |
tallies[element] += 1 | |
} | |
}) | |
return tallies | |
} | |
//-------------------------------------------------------------------// | |
// // | |
// MAKE AXES // | |
// // | |
//-------------------------------------------------------------------// | |
// Makes axes / updates axes if they already exist | |
function makeBarChartAxes(svg, xAxesLabels, yMax, pixelsRequiredByOthers, drawingSpace) { | |
// y-axis | |
var yAxisScale = d3.scaleLinear().domain([0, yMax]).range([drawingSpace.y, 0]) | |
var yAxis = d3.axisLeft().scale(yAxisScale).tickSize(pixelsRequiredByOthers.axes.x / 4).ticks(5) | |
// x-axis | |
var xAxisScale = d3.scaleBand() | |
.domain(xAxesLabels.map(function(d) { | |
// if (d[hyperParameters.data.x].length > hyperParameters.fonts.axes.maxCharacters.x) { | |
// return d[hyperParameters.data.x].slice(0, hyperParameters.fonts.axes.maxCharacters.x - 3) + "..." | |
// } // for truncating long text | |
return d.slice(0, 12) | |
})) | |
.range([0, drawingSpace.x]) | |
.align([0.5]) | |
var xAxis = d3.axisBottom().scale(xAxisScale) | |
// y axis | |
svg.select(".yAxisContainer").transition().duration(500).call(yAxis) | |
svg.selectAll(".yAxisContainer").transition().duration(500).attr("transform", function(d,i){ | |
x = pixelsRequiredByOthers.spaceBetween.y * 3 + pixelsRequiredByOthers.dendogram.y + pixelsRequiredByOthers.labels.y + pixelsRequiredByOthers.axes.y | |
y = pixelsRequiredByOthers.spaceBetween.x * 4 + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.title + pixelsRequiredByOthers.buttons | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
// x axis | |
svg.select(".xAxisContainer").transition().duration(500).call(xAxis) | |
svg.selectAll(".xAxisContainer").transition().duration(500).attr("transform", function(d,i){ | |
x = pixelsRequiredByOthers.spaceBetween.y * 4 + pixelsRequiredByOthers.dendogram.y + pixelsRequiredByOthers.labels.y + pixelsRequiredByOthers.axes.y | |
y = pixelsRequiredByOthers.spaceBetween.x * 5 + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.title + pixelsRequiredByOthers.buttons + drawingSpace.y | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
// Adjust text to angle, fontsize and fontfamily | |
xFontSize = pixelsRequiredByOthers.labels.x / 4 | |
yFontSize = pixelsRequiredByOthers.labels.y / 4 | |
svg.selectAll(".xAxisContainer") | |
.selectAll('text') | |
.attr("text-anchor", "end") | |
.attr("font-size", xFontSize) | |
.attr("transform", "rotate(-45)").transition().duration(500) | |
.attr("x", -xFontSize) | |
.attr("y", xFontSize) | |
svg.selectAll(".yAxisContainer") | |
.selectAll('text') | |
.attr("font-size", yFontSize) | |
} | |
//-------------------------------------------------------------------// | |
// // | |
// MAKE LASSO // | |
// // | |
//-------------------------------------------------------------------// | |
/* | |
Lasso function call heirarchy: | |
setUpLasso - calls and organizes the following | |
| setUpLassoIcon (0) | |
| | |
| lassoCells (1) | |
| | | |
| |---> lassoTick (2) | |
| |-----| ---> checkWhichCellsInLasso(svg, lassoPointData) (3) | |
| |-----| ---> applyToCellsInLasso(svg) (4) | |
function 0 sets up the toggle indecator and global boolean toggle to the lasso icon | |
function 1 creates the container for the the lasso polygon which the user sees | |
function 2 is called by function 1 and it adds the current mouse position | |
to lasso and then calls functions 3 and 4 | |
function 3 marks which cells of the heatmap are inside the lasso with the class | |
inLasso | |
function 4 finally inacts what should happen to cells in the selection | |
Currently, changes selected cell styles and then calls linked svg | |
functions with the subset data | |
Also, it resets non-select cells to default styles. | |
*/ | |
function setUpLasso(data, config, svg, chartContainer, toolbarContainer, pixelsRequiredByOthers) { | |
setUpLassoIcon(data, svg, toolbarContainer, pixelsRequiredByOthers) | |
// stores the mouse movements | |
var lassoPointData = []; | |
svg.on("mousedown", function(d, i){ | |
lassoCells(svg, chartContainer, config) | |
}) | |
svg.on("mouseup", function(d, i) { | |
// remove the polygon | |
svg.select(".lassoContainer").remove(); | |
// stop tracking mouse movements | |
svg.on("mousemove", null); | |
}) | |
} | |
function setUpLassoIcon(data, svg, toolbarContainer, pixelsRequiredByOthers) { | |
/* | |
This will make the lassoIcon and add the event listener for | |
when the lassoIcon is clicked | |
*/ | |
if (toolbarContainer.select(".lassoIconContainer").empty()) { | |
lassoIconContainer = toolbarContainer.append("g").attr("class","lassoIconContainer") // move lasso over | |
} else { | |
lassoIconContainer = toolbarContainer.select("g.lassoIconContainer") | |
} | |
// put the hardcoded image in there | |
if (lassoIconContainer.select("svg").empty()) { | |
lassoIconSVGContainer = lassoIconContainer.append("svg") | |
} | |
lassoIconSVGContainer = lassoIconContainer.select("svg") | |
lassoIconSVGContainer.node().innerHTML = lassoIconSvg | |
// add an invisible rectangle as otherwise mousedown doesnt register | |
if (lassoIconContainer.select("rect.hiddenBox").empty()) { | |
lassoIconContainer.append("rect").attr("class", 'hiddenBox') | |
} | |
lassoIconContainer.select("rect.hiddenBox") | |
.attr("width", pixelsRequiredByOthers.toolbar) | |
.attr("height", pixelsRequiredByOthers.toolbar) | |
.attr("fill", "white").style("opacity",0) | |
.on("mousedown", lassoIconMousedown) | |
function lassoIconMousedown(d, i) { | |
if (LASSO) { | |
LASSO = false // toggle lasso ability on and off | |
lassoIconSVGContainer.selectAll("circle") | |
.style("fill", "white") // inside of lasso is white | |
// when it goes off, reset all cells to default style | |
svg.selectAll(".cellContainer").selectAll("rect") | |
.attr("stroke-width", 1) | |
.style("opacity", 1) | |
// remove lasso class from all cells | |
svg.selectAll(".cellContainer").classed("inLasso", false); | |
for (var i = 0; i < config.linkedSVGS.length; i++) { | |
config.linkedSVGS[i](data) | |
} | |
} else { | |
LASSO = true | |
// color the icon to give user notification | |
lassoIconSVGContainer.selectAll("circle") | |
.style("fill", "cyan") | |
} | |
} | |
} | |
// track the cells in the lasso | |
function lassoCells(svg, chartContainer, config) { | |
if (!LASSO) {return null} // only proceed if Lasso is turned on | |
// store the points where the mouse was at | |
lassoPointData = []; | |
// function for making the polygon | |
var lassoLine = d3.line().x(function(d, i) { return d[0]; }).y(function(d, i) { return d[1]; }); | |
// container for the lasso | |
lassoContainer = chartContainer.append("g").attr("class", "lassoContainer") | |
// stylize the lasso | |
lasso = lassoContainer.append("path") // dont worry about the append because | |
.data([lassoPointData]) // the container will be removed on mouse up | |
.attr("class", "line") | |
.attr("d", lassoLine) | |
.attr("fill", "blue") | |
.style("opacity", 0.3) | |
.attr("stroke", "blue") | |
.attr("stroke-width", 3) | |
// on move, append another point | |
svg.on("mousemove", function() { | |
var pt = d3.mouse(this); | |
lassoTick(pt, lassoPointData, lasso, lassoLine, svg, config); | |
}); | |
} // end lassoCells | |
function checkWhichCellsInLasso(svg, lassoPointData) { | |
// for each cell of the heatmap | |
svg.selectAll(".cellContainer").each(function(d, i) { | |
// get current cell of the heatmap | |
currentCell = d3.select(this) | |
// get absolute position of this cell in the svg | |
currentBox = currentCell.absolutePosition() | |
// get the four corners of the bounding box | |
pts = [ | |
[currentBox.left, currentBox.top], | |
[currentBox.right, currentBox.top], | |
[currentBox.left, currentBox.bottom], | |
[currentBox.right, currentBox.bottom] | |
] | |
// helper function to see if all the points are in the lasso | |
function allPointsInLassoHullQ(element, index, array) { | |
return d3.polygonContains(lassoPointData, element) | |
} | |
// boolean using above function | |
allPointsInLasso = pts.every(allPointsInLassoHullQ) | |
// if the entire cell is inside the lasso, change the class and style | |
if (allPointsInLasso) { | |
currentCell.classed("inLasso", true); | |
} else { | |
currentCell.classed("inLasso", false); | |
} | |
}) // end each | |
} | |
function applyToCellsInLasso(svg, config) { | |
subsetData = [] | |
svg.selectAll(".cellContainer").each(function(){ | |
currentCell = d3.select(this) | |
if (currentCell.classed("inLasso")) { | |
currentCell.select("rect").attr("stroke-width", 3) | |
currentCell.raise() | |
subsetData.push(currentCell.data()[0]) | |
} else { | |
currentCell.select("rect") | |
.attr("stroke-width", 1) | |
.style("opacity", 1) | |
currentCell.classed("inLasso", false); | |
} | |
}) | |
for (var i = 0; i < config.linkedSVGS.length; i++) { | |
config.linkedSVGS[i](subsetData) | |
} | |
} | |
// when a new point is pushed | |
function lassoTick(pt, lassoPointData, lasso, lassoLine, svg, config) { | |
// push a new data point onto the back | |
lassoPointData.push(pt); | |
// Redraw the path: | |
lasso.attr("d", function(d) { return lassoLine(d);}) | |
// make the hull of the lasso and test to see what is inside if more than | |
// 3 points | |
if (lassoPointData.length < 3) { return null } | |
checkWhichCellsInLasso(svg, lassoPointData) | |
applyToCellsInLasso(svg, config) | |
} // end lassoTick | |
function getChartGroups(svg) { | |
if (svg.selectAll("g").empty()) { | |
/****************************************************************** | |
* * | |
* set up groups * | |
* * | |
******************************************************************/ | |
// contains all chart elements, e.g. labels, dendogram, buttons, etc | |
var chartContainer = svg.append("g").attr("class", "chartContainer") | |
// contains the cells for the heat map | |
var plotContainer = chartContainer.append("g").attr("class", "plotContainer") | |
// container for the labels along the axes | |
var axesLabelContainer = chartContainer.append("g").attr("class", "axesLabelContainer") | |
var xAxisLabelContainer = axesLabelContainer.append("g").attr("class", "xAxisLabelContainer") | |
var yAxisLabelContainer = axesLabelContainer.append("g").attr("class", "yAxisLabelContainer") | |
// container for the axes themselves | |
var axesContainer = chartContainer.append("g").attr("class", "axesContainer") | |
var xAxisContainer = axesContainer.append("g").attr("class", "xAxisContainer") | |
var yAxisContainer = axesContainer.append("g").attr("class", "yAxisContainer") | |
// Title container | |
var titleContainer = chartContainer.append("g").attr("class", "titleContainer") | |
// svg button container (for toggling between data) | |
var buttonContainer = chartContainer.append("g").attr("class", "buttonContainer") | |
// svg button container (for toggling between data) | |
var toolbarContainer = chartContainer.append("g").attr("class", "toolbarContainer") | |
// color legend | |
var legendContainer = chartContainer.append("g").attr("class", "legendContainer") | |
// container for the dendograms along the axes | |
var dendogramContainer = chartContainer.append("g").attr("class", "dendogramContainer") | |
var xAxisdendogramContainer = dendogramContainer.append("g").attr("class", "xAxisdendogramContainer") | |
var yAxisdendogramContainer = dendogramContainer.append("g").attr("class", "yAxisdendogramContainer") | |
} else { | |
/****************************************************************** | |
* * | |
* select groups * | |
* * | |
******************************************************************/ | |
// contains all chart elements, e.g. labels, dendogram, buttons, etc | |
var chartContainer = svg.select("g.chartContainer") | |
// contains the cells for the heat map | |
var plotContainer = chartContainer.select("g.plotContainer") | |
// container for the labels along the axes | |
var axesLabelContainer = chartContainer.select("g.axesLabelContainer") | |
var xAxisLabelContainer = axesLabelContainer.select("g.xAxisLabelContainer") | |
var yAxisLabelContainer = axesLabelContainer.select("g.yAxisLabelContainer") | |
// container for the axes themselves | |
var axesContainer = chartContainer.select("g.axesContainer") | |
var xAxisContainer = axesContainer.select("g.xAxisContainer") | |
var yAxisContainer = axesContainer.select("g.yAxisContainer") | |
// Title container | |
var titleContainer = chartContainer.select("g.titleContainer") | |
// svg button container (for toggling between data) | |
var buttonContainer = chartContainer.select("g.buttonContainer") | |
// svg button container (for toggling between data) | |
var toolbarContainer = chartContainer.select("g.toolbarContainer") | |
// color legend | |
var legendContainer = chartContainer.select("g.legendContainer") | |
// container for the dendograms along the axes | |
var dendogramContainer = chartContainer.select("g.dendogramContainer") | |
var xAxisdendogramContainer = dendogramContainer.select("g.xAxisdendogramContainer") | |
var yAxisdendogramContainer = dendogramContainer.select("g.yAxisdendogramContainer") | |
} | |
// return the selections | |
return [ | |
chartContainer, plotContainer, | |
axesLabelContainer, xAxisContainer, yAxisContainer, | |
axesLabelContainer, xAxisLabelContainer, yAxisLabelContainer, | |
titleContainer, toolbarContainer, buttonContainer, legendContainer, | |
dendogramContainer, xAxisdendogramContainer, yAxisdendogramContainer | |
] | |
} | |
function moveChartGroups(config, drawingSpace, pixelsRequiredByOthers, | |
chartContainer, plotContainer, | |
axesLabelContainer, xAxisContainer, yAxisContainer, | |
axesLabelContainer, xAxisLabelContainer, yAxisLabelContainer, | |
titleContainer, toolbarContainer, buttonContainer, legendContainer, | |
dendogramContainer, xAxisdendogramContainer, yAxisdendogramContainer | |
) { | |
plotContainer.attr("transform",function(d, i) { | |
x = (pixelsRequiredByOthers.dendogram.y + pixelsRequiredByOthers.axes.y + pixelsRequiredByOthers.labels.y + pixelsRequiredByOthers.spaceBetween.y * 4) | |
// pixelsRequiredByOthers.spaceBetween.y * 3 + pixelsRequiredByOthers.dendogram.y + pixelsRequiredByOthers.labels.y + pixelsRequiredByOthers.axes.y | |
y = (pixelsRequiredByOthers.title + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.buttons + pixelsRequiredByOthers.spaceBetween.x * 4) | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
titleContainer.attr("transform", function(d, i) { | |
x = parseFloat(svg.style("width")) / 2 | |
y = (pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.spaceBetween.y * 2) | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
legendContainer.attr("transform", function(d, i) { | |
x = (pixelsRequiredByOthers.spaceBetween.x * 1) | |
y = (pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.title + pixelsRequiredByOthers.spaceBetween.y * 3) | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) // move it over | |
yAxisdendogramContainer.attr("transform",function(){ | |
x = (pixelsRequiredByOthers.dendogram.x + pixelsRequiredByOthers.labels.x + pixelsRequiredByOthers.spaceBetween.x * 3) | |
y = (pixelsRequiredByOthers.title + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.buttons + pixelsRequiredByOthers.labels.y + pixelsRequiredByOthers.spaceBetween.y * 6 + drawingSpace.y + pixelsRequiredByOthers.dendogram.y) | |
xCent = (x + drawingSpace.x / 2) | |
yCent = (y + pixelsRequiredByOthers.dendogram.y / 2) | |
trans = "translate("+(x)+","+(y)+")" | |
// rot = "rotate(90 "+(xCent)+" "+(yCent)+")"; | |
return trans | |
}) | |
xAxisdendogramContainer.attr("transform",function(){ | |
x = (pixelsRequiredByOthers.spaceBetween.x * 1) | |
y = (pixelsRequiredByOthers.title + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.buttons + pixelsRequiredByOthers.spaceBetween.y * 4) | |
xCent = (x + drawingSpace.x / 2) | |
yCent = (y + pixelsRequiredByOthers.dendogram.y / 2) | |
trans = "translate("+(x)+","+(y)+")" | |
// rot = "rotate(90 "+(xCent)+" "+(yCent)+")"; | |
return trans | |
}) | |
legendContainer.attr("transform", function(d, i) { | |
x = (pixelsRequiredByOthers.dendogram.y + pixelsRequiredByOthers.labels.y + drawingSpace.x + pixelsRequiredByOthers.spaceBetween.y * 5) | |
y = (pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.title + pixelsRequiredByOthers.spaceBetween.y * 4) | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
toolbarContainer.attr("transform", function(d, i) { | |
x = (pixelsRequiredByOthers.spaceBetween.x * 1) | |
y = (pixelsRequiredByOthers.spaceBetween.y * 1) | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
} | |
function makeLinkedBarChart(config, data, metaKey, metaSubkey) { | |
configurateSVG(config) | |
// select the svg | |
var svg = d3.select("svg#"+config.svg.id) | |
// extract the relevant meta data | |
var metaData = getMetaData(data, metaKey, metaSubkey) | |
var tallies = tally(metaData) | |
var labels = d3.keys(tallies) | |
// temp title element for demo purposes | |
var title = "My Linked Barchart" | |
// calculate space needed for all elements | |
var pixelsRequiredByOthers = calculatePixelsNeeded(config.svg.id, config.percentElementsTake) | |
var drawingSpace = getDrawingSpace(config.svg.id, pixelsRequiredByOthers) | |
// get min / max of data | |
var dataExtent = { | |
min: d3.min(labels.map(function(d){return tallies[d]})), | |
max: d3.max(labels.map(function(d){return tallies[d]})) | |
} | |
/****************************************************************** | |
* get main groups * | |
******************************************************************/ | |
var [ | |
chartContainer, plotContainer, | |
axesLabelContainer, xAxisContainer, yAxisContainer, | |
axesLabelContainer, xAxisLabelContainer, yAxisLabelContainer, | |
titleContainer, toolbarContainer, buttonContainer, legendContainer, | |
dendogramContainer, xAxisdendogramContainer, yAxisdendogramContainer | |
] = getChartGroups(svg) // makes / gets groups | |
// move groups to their respective positions within the chart | |
moveChartGroups(config, drawingSpace, pixelsRequiredByOthers, | |
chartContainer, plotContainer, | |
axesLabelContainer, xAxisContainer, yAxisContainer, | |
axesLabelContainer, xAxisLabelContainer, yAxisLabelContainer, | |
titleContainer, toolbarContainer, buttonContainer, legendContainer, | |
dendogramContainer, xAxisdendogramContainer, yAxisdendogramContainer | |
) | |
// parameters for the bars in this bar chart | |
var bar = { | |
width: drawingSpace.x / (labels.length + 1), | |
// one bar's width will be used for the spacers between the bars | |
spacer: (drawingSpace.x / (labels.length + 1)) / (labels.length + 1), | |
// in total there will be (numberOfBars + 1) spacers | |
scale: d3.scaleLinear() | |
.domain([0, dataExtent.max]) | |
.range([0,drawingSpace.y]), | |
color: d3.scaleLinear() | |
.domain([dataExtent.min, dataExtent.max]) | |
.range(["cyan", "blue"]) | |
} | |
/****************************************************************** | |
* * | |
* set up barchart proper * | |
* * | |
******************************************************************/ | |
// current number of bars | |
numberOfBars = plotContainer.selectAll(".barContainer").size() | |
// number of bars needed | |
numberOfBarsNeeded = labels.length | |
if (numberOfBarsNeeded > numberOfBars) { // need more bars | |
plotContainer.selectAll(".barContainer") | |
.data(labels) // bind data | |
.enter() // will only produce (numberOfBarsNeeded - numberOfBars) # of bars | |
.append("g").attr("class", "barContainer") | |
.append("rect") | |
.attr("stroke", "black") // all bars will have the following styles | |
.attr("stroke-width", "1px") // as they will originally be created here | |
.attr("rx", "10px") | |
.attr("ry", "10px") | |
} else { // remove excess bars | |
plotContainer.selectAll(".barContainer") | |
.data(labels) // bind data | |
.exit().remove() // remove excess | |
} | |
// adjust things for pre-existing bars | |
plotContainer.selectAll(".barContainer") | |
.attr("transform", function(d, i) { | |
x = i * bar.width + bar.spacer * (i+1) // + 1 for the leading spacer | |
y = drawingSpace.y - bar.scale(tallies[d]) | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) // move bars to respective position | |
.select("rect") | |
.attr("width", bar.width) | |
.attr("height", function (d, i) { | |
return bar.scale(tallies[d]) | |
}) // reset the height of all bars | |
.attr("fill", function (d, i) { | |
return bar.color(tallies[d]) | |
}) // bar cell | |
.on("mousemove", mousemoveBar) | |
.on("mouseout", mouseoutBar) | |
/****************************************************************** | |
* set up title * | |
******************************************************************/ | |
makeTitle(svg, titleContainer, pixelsRequiredByOthers, title) | |
makeBarChartAxes(svg, labels, dataExtent.max, pixelsRequiredByOthers, drawingSpace) | |
function mousemoveBar(d, i) { // simple tooltip for demo purposes | |
var tooltip = svg.select("g.tooltip") | |
if (tooltip.empty()) { | |
tooltip = svg.append("g").attr("class","tooltip") | |
} | |
// move tooltip to mouse location | |
tooltip.attr("transform", function(d, i) { | |
x = d3.event.pageX - document.getElementById(config.svg.id).getBoundingClientRect().x + 10 | |
y = d3.event.pageY - document.getElementById(config.svg.id).getBoundingClientRect().y + 10 | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
// add a background for the tooltip rather than use an external div | |
tooltipRect = tooltip.append("rect").attr("fill", "white") | |
// add the text | |
tooltipText = tooltip.append("g").attr("class","tooltipText") | |
tooltipText.append("text").text(tallies[d]) | |
.attr("text-anchor","middle") | |
tooltipText.attr("transform","translate("+(tooltipText.node().getBBox().width)+","+(tooltipText.node().getBBox().height)+")") | |
// stylize the rectangle | |
tooltipRect.attr("fill", "white") | |
.attr("width", tooltipText.node().getBBox().width * 2) | |
.attr("height", tooltipText.node().getBBox().height * 2) | |
.attr("rx", 10) | |
.attr("ry", 10) | |
.attr("stroke","black") | |
tooltip.raise() | |
} | |
function mouseoutBar(d, i) { | |
svg.selectAll("g.tooltip").remove() | |
} | |
} | |
function makeTitle(svg, titleContainer, pixelsRequiredByOthers, title) { | |
if ( titleContainer.select("text").empty() ) { | |
titleContainer.append("text") | |
} | |
titleContainer.select("text").text(title) // move the title to position | |
.attr('text-anchor',"middle") // lazy centering | |
.attr("font-size", pixelsRequiredByOthers.title + "px") | |
.style("user-select", "none") // disable user select as it interfers with lasso | |
.style("pointer-events", "none") | |
} | |
// A sloppy temp for the make heatmap function | |
function makeHeatmap(config, data, labelKeys) { | |
// select the svg | |
var svg = d3.select("svg#"+config.svg.id) | |
// start to fill the heatmap | |
var labels = getHeatmapLabels(data, labelKeys.x, labelKeys.y) | |
// a temp title title to see spacing | |
var title = "My Heatmap" | |
// The pixel space used up by all other elements besides the actual cells of | |
// the heatmap | |
var pixelsRequiredByOthers = calculatePixelsNeeded(config.svg.id, config.percentElementsTake) | |
// the pixel space required by the actual heatmap | |
var drawingSpace = getDrawingSpace(config.svg.id, pixelsRequiredByOthers) | |
// min / max values of the cells | |
var dataExtent = { | |
min: d3.min(data.map(function(d){return d.val;})), | |
max: d3.max(data.map(function(d){return d.val;})) | |
} | |
// number of cells wide x number of cells high | |
cellDimensions = getheatmapCellDimensions(labels.xAxis, labels.yAxis) | |
// the size of a single cell in the heatmap | |
cellSize = {x: drawingSpace.x / cellDimensions.x, y: drawingSpace.y / cellDimensions.y} | |
// the color interpolation function | |
cellColors = d3.scaleSequential(d3.interpolateGnBu).domain([dataExtent.min, dataExtent.max]) | |
/****************************************************************** | |
* get main groups * | |
******************************************************************/ | |
var [ | |
chartContainer, plotContainer, | |
axesLabelContainer, xAxisContainer, yAxisContainer, | |
axesLabelContainer, xAxisLabelContainer, yAxisLabelContainer, | |
titleContainer, toolbarContainer, buttonContainer, legendContainer, | |
dendogramContainer, xAxisdendogramContainer, yAxisdendogramContainer | |
] = getChartGroups(svg) // makes / gets groups | |
// move groups to their respective positions within the chart | |
moveChartGroups(config, drawingSpace, pixelsRequiredByOthers, | |
chartContainer, plotContainer, | |
axesLabelContainer, xAxisContainer, yAxisContainer, | |
axesLabelContainer, xAxisLabelContainer, yAxisLabelContainer, | |
titleContainer, toolbarContainer, buttonContainer, legendContainer, | |
dendogramContainer, xAxisdendogramContainer, yAxisdendogramContainer | |
) | |
/****************************************************************** | |
* * | |
* set up heatmap proper * | |
* * | |
******************************************************************/ | |
// current number of cells | |
numberOfCells = plotContainer.selectAll(".cellContainer").size() | |
// number of cells needed | |
numberOfCellsNeeded = cellDimensions.x * cellDimensions.y | |
if (numberOfBarsNeeded > numberOfCells) { | |
plotContainer.selectAll(".cellContainer") | |
.data(data) // bind data | |
.enter() | |
.append("g").attr("class", "cellContainer") | |
.append("rect") | |
.attr("stroke", "black") | |
.attr("stroke-width", "1px") | |
.attr("rx", "10px") | |
.attr("ry", "10px") | |
} else { | |
plotContainer.selectAll(".cellContainer") | |
.data(data) // bind data | |
.exit().remove() // remove extra containers | |
} | |
plotContainer.selectAll(".cellContainer") | |
.attr("transform", function(d, i) { | |
xAxislabelIndex = labels.xAxis.indexOf(d[labelKeys.x]); | |
yAxislabelIndex = labels.yAxis.indexOf(d[labelKeys.y]); | |
x = cellSize.x * xAxislabelIndex; | |
y = cellSize.y * yAxislabelIndex; | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) // move cells to respective position | |
.select("rect") | |
.attr("width", cellSize.x) | |
.attr("height", cellSize.y) | |
.attr("fill", function(d, i) {return cellColors(d.val)}) // color cell | |
.on("mousemove", mousemoveCell) // add tooltip | |
.on("mouseout", mouseoutCell) // remove tooltip | |
function mousemoveCell(d, i) { // simple tooltip for demo purposes | |
tooltip = d3.select("g.tooltip") | |
if (tooltip.empty()) { | |
tooltip = svg.append("g").attr("class","tooltip") | |
} | |
// move tooltip to mose location | |
tooltip.attr("transform", function(d, i) { | |
x = d3.event.pageX - document.getElementById(config.svg.id).getBoundingClientRect().x + 10 | |
y = d3.event.pageY - document.getElementById(config.svg.id).getBoundingClientRect().y + 10 | |
trans = "translate("+(x)+","+(y)+")" | |
return trans | |
}) | |
// add a background for the tooltip rather than use an external div | |
tooltipRect = tooltip.append("rect").attr("fill", "white") | |
// add the text | |
tooltipText = tooltip.append("g").attr("class",".tooltipText") | |
tooltipText.append("text").text(d.val.toFixed(4)) | |
.attr("text-anchor","middle") | |
tooltipText.attr("transform","translate("+(tooltipText.node().getBBox().width)+","+(tooltipText.node().getBBox().height)+")") | |
.style("user-select", "none") | |
.style("pointer-events", "none") | |
// stylize the rectangle | |
tooltipRect.attr("fill", "white") | |
.attr("width", tooltipText.node().getBBox().width * 2) | |
.attr("height", tooltipText.node().getBBox().height * 2) | |
.attr("rx", 10) | |
.attr("ry", 10) | |
.attr("stroke","black") | |
} | |
function mouseoutCell(d, i) { | |
svg.selectAll("g.tooltip").remove() | |
} | |
/****************************************************************** | |
* set up title * | |
******************************************************************/ | |
makeTitle(svg, titleContainer, pixelsRequiredByOthers, title) | |
/****************************************************************** | |
* set up LASSO * | |
******************************************************************/ | |
setUpLasso(data, config, svg, chartContainer, toolbarContainer, pixelsRequiredByOthers) | |
/****************************************************************** | |
* * | |
* set up colored legend * | |
* * | |
******************************************************************/ | |
addGnBuVerticalGradient(svg) | |
if ( legendContainer.select("rect.colorRect") ) { | |
colorLegendRect = legendContainer.append("rect").attr("class", "colorRect") | |
} | |
if ( legendContainer.select("text.legendMin") ) { | |
legendContainer.append("text").attr("class", "legendMin") | |
} | |
if ( legendContainer.select("text.legendMax") ) { | |
legendContainer.append("text").attr("class", "legendMax") | |
} | |
// the colored rectangle | |
colorLegendRect = legendContainer.select("rect") | |
.attr("width", pixelsRequiredByOthers.legend) | |
.attr("height", drawingSpace.y * 0.8) | |
.attr("rx", 10) | |
.attr("ry", 10) | |
.attr("transform", "translate(0,"+(drawingSpace.y * 0.1)+")") | |
.style("fill", "url(#GnBuVerticalGradient)") | |
.attr("stroke","black") | |
// text for min | |
legendContainer.select("text.legendMin") | |
.attr("transform", "translate("+(pixelsRequiredByOthers.legend * 0.5)+","+(drawingSpace.y * 0.1 - 6)+")") | |
.attr("class","legendText").text(dataExtent.min.toFixed(4)) | |
.attr("font-size", 12) | |
.attr("text-anchor", "middle") | |
.style("user-select", "none") // no user interaction with text | |
.style("pointer-events", "none") | |
// text for max | |
legendContainer.select("text.legendMax") | |
.attr("transform", "translate("+(pixelsRequiredByOthers.legend * 0.5)+","+(drawingSpace.y * .9 + 12)+")") | |
.attr("class","legendText").text(dataExtent.max.toFixed(4)) | |
.attr("font-size", 12) | |
.attr("text-anchor", "middle") | |
.style("user-select", "none") | |
.style("pointer-events", "none") | |
/****************************************************************** | |
* * | |
* set up labels * | |
* * | |
******************************************************************/ | |
xLabels = xAxisLabelContainer.selectAll(".xLabel").data(labels.xAxis).enter() | |
.append("g").attr("class","xLabel") | |
.attr("transform", function(e, i) { | |
x = (pixelsRequiredByOthers.dendogram.x + pixelsRequiredByOthers.labels.x + pixelsRequiredByOthers.spaceBetween.x * 3) | |
y = (pixelsRequiredByOthers.title + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.buttons + pixelsRequiredByOthers.spaceBetween.y * 5) | |
x += cellSize.x * i + cellSize.x / 2; | |
y += cellSize.y * cellDimensions.y + pixelsRequiredByOthers.labels.y / 2 ; | |
trans = "translate("+(x)+","+(y)+")"; | |
return trans | |
}) // move the labels over | |
xLabels.append("text").text(function(d){return d}) | |
.style("user-select", "none") | |
.style("pointer-events", "none") // no user interaction with the text | |
yLabels = xAxisLabelContainer.selectAll(".yLabel").data(labels.yAxis).enter() | |
.append("g").attr("class","yLabel") | |
.attr("transform", function(e, i) { | |
x = (pixelsRequiredByOthers.dendogram.x + pixelsRequiredByOthers.labels.x + pixelsRequiredByOthers.spaceBetween.x * 2) | |
y = (pixelsRequiredByOthers.title + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.buttons + pixelsRequiredByOthers.spaceBetween.y * 4) | |
x += -pixelsRequiredByOthers.labels.x / 2 | |
y += cellSize.y * i + cellSize.y / 2; | |
trans = "translate("+(x)+","+(y)+")"; | |
return trans | |
}) // move the labels over | |
yLabels.append("text").text(function(d){return d}) | |
.style("user-select", "none") | |
.style("pointer-events", "none") // no user interaction with the text | |
/****************************************************************** | |
* * | |
* set up dendogram * | |
* * | |
******************************************************************/ | |
var yRoot = d3.stratify() | |
.id(function(d) { return d.name; }) | |
.parentId(function(d) { return d.parent; }) | |
(yDend); // make the data heirarchical | |
// make the tree | |
var yDendoTree = d3.tree().size([drawingSpace.x, pixelsRequiredByOthers.dendogram.y]); | |
// make the links | |
var yLinks = yAxisdendogramContainer.selectAll(".link") | |
.data(yDendoTree(yRoot).links()) | |
.enter().append("path") | |
.attr("class", "link") | |
.attr("d", d3.linkVertical() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return -d.y; })) | |
.attr("fill", "none") | |
.attr("stroke", "black") | |
// move the tree | |
yAxisdendogramContainer.attr("transform",function(){ | |
x = (pixelsRequiredByOthers.dendogram.x + pixelsRequiredByOthers.labels.x + pixelsRequiredByOthers.spaceBetween.x * 3) | |
y = (pixelsRequiredByOthers.title + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.buttons + pixelsRequiredByOthers.labels.y + pixelsRequiredByOthers.spaceBetween.y * 6 + drawingSpace.y + pixelsRequiredByOthers.dendogram.y) | |
xCent = (x + drawingSpace.x / 2) | |
yCent = (y + pixelsRequiredByOthers.dendogram.y / 2) | |
trans = "translate("+(x)+","+(y)+")" | |
// rot = "rotate(90 "+(xCent)+" "+(yCent)+")"; | |
return trans | |
}) | |
// hierarchical | |
var xRoot = d3.stratify() | |
.id(function(d) { return d.name; }) | |
.parentId(function(d) { return d.parent; }) | |
(xDend); | |
// make the tree | |
var xDendoTree = d3.tree().size([drawingSpace.y,pixelsRequiredByOthers.dendogram.x]); | |
// make the links | |
var xLinks = xAxisdendogramContainer.selectAll(".link") | |
.data(xDendoTree(xRoot).links()) | |
.enter().append("path") | |
.attr("class", "link") | |
.attr("d", d3.linkHorizontal() | |
.x(function(d) { return d.y; }) | |
.y(function(d) { return d.x; })) | |
.attr("fill", "none") | |
.attr("stroke", "black") | |
// move the tree | |
xAxisdendogramContainer.attr("transform",function(){ | |
x = (pixelsRequiredByOthers.spaceBetween.x * 1) | |
y = (pixelsRequiredByOthers.title + pixelsRequiredByOthers.toolbar + pixelsRequiredByOthers.buttons + pixelsRequiredByOthers.spaceBetween.y * 4) | |
xCent = (x + drawingSpace.x / 2) | |
yCent = (y + pixelsRequiredByOthers.dendogram.y / 2) | |
trans = "translate("+(x)+","+(y)+")" | |
// rot = "rotate(90 "+(xCent)+" "+(yCent)+")"; | |
return trans | |
}) | |
} | |
function calculatePixelsNeeded(svgID, percentElementsTake) { | |
/* | |
this turns the percentages of the elements around the HeatMap | |
(title, labels, dendogram, legend, etc) into pixel values the function | |
getDrawingSpace calculates how much space is left over for the heatmap proper | |
*/ | |
var svg = d3.select("svg#"+svgID) | |
var w = parseFloat(svg.style("width")) | |
var h = parseFloat(svg.style("height")) | |
var pixelsRequired = { | |
axes: { | |
x: percentElementsTake.axes.x * w, | |
y: percentElementsTake.axes.y * h | |
}, | |
dendogram: { | |
x: percentElementsTake.dendogram.x * w, | |
y: percentElementsTake.dendogram.y * h | |
}, | |
labels: { | |
x: percentElementsTake.labels.x * w, | |
y: percentElementsTake.labels.y * h, | |
}, | |
title: percentElementsTake.title * h, | |
toolbar: percentElementsTake.toolbar * h, | |
buttons: percentElementsTake.buttons * h, | |
legend: percentElementsTake.legend * w, | |
spaceBetween: { | |
x: percentElementsTake.spaceBetween.x * w, | |
y: percentElementsTake.spaceBetween.x * h | |
} | |
} | |
return pixelsRequired | |
} | |
function getDrawingSpace(svgID, pixelsRequiredByOtherElements) { | |
/* | |
this uses the pixel values of other elements in the chart (e.g. title, labels) | |
and calculates how much space is left over for the heatmap proper | |
*/ | |
var svg = d3.select("svg#"+svgID) | |
var w = parseFloat(svg.style("width")) | |
var h = parseFloat(svg.style("height")) | |
// for convience | |
var margins = pixelsRequiredByOtherElements | |
var drawingSpace = { | |
x: w - (margins.axes.x + margins.dendogram.x + margins.labels.x + margins.legend + margins.spaceBetween.y * 6), | |
y: h - (margins.axes.y + margins.dendogram.y + margins.labels.y + margins.title + margins.toolbar + margins.buttons + margins.spaceBetween.x * 8) | |
} | |
return drawingSpace | |
} | |
function addGnBuVerticalGradient(svg) { | |
//Append a defs (for definition) element to your SVG | |
if ( svg.select("defs").empty() ) { | |
var defs = svg.append("defs"); | |
} | |
var defs = svg.select("defs"); | |
//Append a linearGradient element to the defs and give it a unique id | |
if (defs.select("#legendLinearGradient").empty()) { | |
var legendLinearGradient = defs.append("linearGradient") | |
.attr("id", "GnBuVerticalGradient"); | |
} | |
var legendLinearGradient = defs.select("linearGradient") | |
// vertical gradient | |
legendLinearGradient.attr("x1", "0%") | |
.attr("y1", "0%") | |
.attr("x2", "0%") | |
.attr("y2", "100%"); | |
// A color scale (same colors as interpolateGnBu) | |
var legendColorScale = d3.scaleLinear().range(["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"]); | |
//Append multiple color stops by binding data | |
legendLinearGradient.selectAll("stop") | |
.data( legendColorScale.range() ) | |
.enter().append("stop") | |
.attr("offset", function(d,i) { return i/(legendColorScale.range().length-1); }) | |
.attr("stop-color", function(d) { return d; }); | |
} | |
configurateHeatmap(config) | |
makeLinkedBarChart(barConfig, data, 'meta', "sex") | |
makeHeatmap(config, data, {x:"xName",y:"yName"}) |
This file contains hidden or 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>HeatMap</title> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<!-- for the interpolated colors --> | |
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> | |
<!-- for the polygon hull --> | |
<script src="https://d3js.org/d3-polygon.v1.min.js"></script> | |
<script type="text/javascript"> | |
data = [ | |
{"xName":"a", "yName":"a", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"a", "yName":"b", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"a", "yName":"c", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"a", "yName":"d", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"a", "yName":"e", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"a", "yName":"f", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"b", "yName":"a", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"b", "yName":"b", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"b", "yName":"c", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"b", "yName":"d", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"b", "yName":"e", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"b", "yName":"f", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"c", "yName":"a", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"c", "yName":"b", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"c", "yName":"c", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"c", "yName":"d", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}}, | |
{"xName":"c", "yName":"e", "val": Math.random(), "meta":{"sex":"male", "age": Math.random() * 10}}, | |
{"xName":"c", "yName":"f", "val": Math.random(), "meta":{"sex":"female", "age": Math.random() * 10}} | |
] | |
yDend = [ | |
{"name": "b", "parent": ""}, | |
{"name": "a", "parent": "b"}, | |
{"name": "c", "parent": "b"} | |
] | |
xDend = [ | |
{"name": "a", "parent": ""}, | |
{"name": "c", "parent": "a"}, | |
{"name": "b", "parent": "a"}, | |
{"name": "e", "parent": "d"}, | |
{"name": "d", "parent": "c"}, | |
{"name": "f", "parent": "d"} | |
] | |
</script> | |
</head> | |
<body> | |
<svg id="heatmapSVG" style="border: 1px solid black;"></svg> | |
<svg id="barchartSVG" style="border: 1px solid black;"></svg> | |
</body> | |
<script type="text/javascript" src="./heatmap.js"></script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment