|
<!DOCTYPE html> |
|
|
|
<html> |
|
<head> |
|
<title>Progress Tree</title> |
|
<meta charset="utf-8"> |
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> |
|
</head> |
|
<style type="text/css"> |
|
#content { |
|
padding-left: 50px; |
|
padding-top: 100px; |
|
} |
|
#svgArea { |
|
display: block; |
|
} |
|
|
|
div.tooltip { |
|
position: absolute; |
|
text-align: center; |
|
padding: 5px; |
|
color: #fff; |
|
font: 12px sans-serif; |
|
/*font-weight: bold; */ |
|
background: rgba(0, 0, 0, 0.9);; |
|
border: 2px; |
|
border-radius: 10px; |
|
pointer-events: none; |
|
margin: 20px |
|
} |
|
|
|
div.tooltip p{ |
|
text-align: left; |
|
} |
|
div.tooltip h1{ |
|
text-align: left; |
|
} |
|
|
|
</style> |
|
|
|
<body> |
|
|
|
<div class="row"> |
|
<div class="col-md-8" id ="svgArea"> |
|
<svg |
|
xmlns:dc="http://purl.org/dc/elements/1.1/" |
|
xmlns:cc="http://creativecommons.org/ns#" |
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
|
xmlns:svg="http://www.w3.org/2000/svg" |
|
xmlns="http://www.w3.org/2000/svg" |
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |
|
width="186.32976" |
|
height="170.42111" |
|
id="svg1901" |
|
sodipodi:version="0.32" |
|
inkscape:version="0.46+pre4" |
|
sodipodi:docname="leaf.svg" |
|
sodipodi:docbase="C:\Important\svg" |
|
inkscape:output_extension="org.inkscape.output.svg.inkscape" |
|
version="1.0"> |
|
<defs> |
|
<g |
|
inkscape:label="Taso 1" |
|
inkscape:groupmode="layer" |
|
id="layer1" |
|
transform="scale(0.1) rotate(132 355 210)"> |
|
<path |
|
style="fill-rule:evenodd;stroke-width:3.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" |
|
d="M 422.27292,632.34394 C 432.85918,616.61295 448.34076,612.0402 448.34076,612.0402 C 448.34076,612.0402 442.54386,606.55398 439.22989,607.84521 C 426.2285,617.56064 419.44945,629.60334 419.44945,629.60334 C 266.27427,623.18356 285.91787,686.20064 265.011,767.7044 C 381.6304,796.72098 433.42735,737.61667 422.27292,632.34394 z" |
|
id="path2159" |
|
sodipodi:nodetypes="cccccc" /> |
|
</g> |
|
</defs> |
|
<sodipodi:namedview |
|
id="base" |
|
pagecolor="#ffffff" |
|
bordercolor="#666666" |
|
borderopacity="1.0" |
|
inkscape:pageopacity="0.0" |
|
inkscape:pageshadow="2" |
|
inkscape:zoom="0.7" |
|
inkscape:cx="205.81395" |
|
inkscape:cy="-68.376196" |
|
inkscape:document-units="px" |
|
inkscape:current-layer="layer1" |
|
gridtolerance="10000" |
|
inkscape:window-width="1280" |
|
inkscape:window-height="938" |
|
inkscape:window-x="-4" |
|
inkscape:window-y="-4" |
|
showguides="true" |
|
inkscape:guide-bbox="true" |
|
showgrid="false" /> |
|
<metadata |
|
id="metadata1906"> |
|
<rdf:RDF> |
|
<cc:Work |
|
rdf:about=""> |
|
<dc:format>image/svg+xml</dc:format> |
|
<dc:type |
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> |
|
</cc:Work> |
|
</rdf:RDF> |
|
</metadata> |
|
</svg> |
|
</div> |
|
<div class="col-md-4" id="content"> |
|
<h1>Make a change</h1> |
|
<p>Save the Children has been working to fight hunger, prevent malnutrition and improve the lives of boys and girls in Africa since 1963. Whether we are working with orphaned children in sub-Saharan Africa or emergency relief for refugees in North Africa, Save the Children strives to meet the needs to vulnerable children and their families with lifesaving and life-changing programs. Charities can make a difference, with a hand-up, not a handout.</p> |
|
|
|
Please enter your name: <input type="text" class="form-control" id="field-name" name="name"><br> |
|
Amount you wish to donate: <input type="text" class="form-control" id="field-amount" name="amount"><br> |
|
|
|
<button class="btn btn-primary" id='donate' type="submit">Donate</button> |
|
<button class="btn btn-success" id='animate' type="submit">Animate</button> |
|
<button class="btn btn-danger" id='atonce' type="submit">At Once</button> |
|
<button class="btn btn-info" id='increment' type='submit'>Increment</button> |
|
</div> |
|
</div> |
|
|
|
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> |
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> |
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> |
|
|
|
<script> |
|
|
|
var branches = []; |
|
var leaves = []; |
|
var seedWidth = 75; |
|
var maxDepth = 9; |
|
|
|
function degToRad(degree) { |
|
return degree*Math.PI/180; |
|
} |
|
|
|
function radToDeg(radian) { |
|
return radian*180/Math.PI |
|
} |
|
|
|
// transform angle with y axis as a basis. so 0 degree means vertical |
|
function normalizeAngle(rad) { |
|
return degToRad(90)+rad; |
|
} |
|
|
|
// transform angle with x axis as basis like normal polar coordinate |
|
function denormalizeAngle(rad) { |
|
return rad - degToRad(90); |
|
} |
|
|
|
// Tree creation functions |
|
function branch(b) { |
|
var end = endPt(b), daR, newB, da, dl, aRand, node, id; |
|
|
|
branches.push(b); |
|
|
|
// first branch has 0.5 scaling factor while the other branch 0.8 |
|
if (b.depth >= 0 && b.depth < 1) { |
|
dl = 0.5; |
|
} else { |
|
dl = 0.75; |
|
} |
|
|
|
// Randomized length of branch |
|
lRand = 0.15; |
|
lRand = Math.random() * (lRand); |
|
|
|
// change the branch angle depending on the depth of the branch |
|
if (b.depth < 3) { |
|
da = degToRad(30); |
|
aRand = degToRad(10); |
|
} else if (b.depth < 6) { |
|
da = degToRad(20); |
|
aRand = degToRad(10); |
|
} else { |
|
da = degToRad(20); |
|
aRand = degToRad(5); |
|
} |
|
|
|
// stop making new branch on maximum depth and make the last branches indicator for the leaves |
|
if (b.depth === maxDepth) { |
|
b.visible = false; |
|
b.leafId = leaves.length; |
|
leaves.push(b); |
|
return; |
|
} |
|
|
|
|
|
// create node array, contains id of branch that has been traversed by the branch |
|
node = []; |
|
node.push(b.i); |
|
node = node.concat(b.node); |
|
|
|
// Left branch |
|
daR = Math.random() * (aRand + aRand) - aRand; //random angle between -aRand and aRand |
|
newB = { |
|
i: branches.length, |
|
x: end.x, |
|
y: end.y, |
|
angle: b.angle + da + daR, |
|
length: b.length * (dl + lRand), |
|
depth: b.depth + 1, |
|
parent: b.i, |
|
node: node, |
|
dl: dl, |
|
da: radToDeg(da), |
|
aRand: aRand |
|
}; |
|
branch(newB); |
|
|
|
// Right branch |
|
daR = Math.random() * (aRand + aRand) - aRand; //random angle between -aRand and aRand |
|
newB = { |
|
i: branches.length, |
|
x: end.x, |
|
y: end.y, |
|
angle: b.angle - da + daR, |
|
length: b.length * (dl + lRand), |
|
depth: b.depth + 1, |
|
parent: b.i, |
|
node: node, |
|
dl: dl, |
|
da: radToDeg(da), |
|
aRand: aRand |
|
}; |
|
branch(newB); |
|
} |
|
|
|
// compute end point of branch (x2,y2) |
|
function endPt(b) { |
|
// Return endpoint of branch |
|
var x = b.x + b.length * Math.cos( normalizeAngle(b.angle) ); |
|
var y = b.y - b.length * Math.sin( normalizeAngle(b.angle) ); |
|
return {x: x, y: y}; |
|
} |
|
|
|
// define svg for placeholder |
|
var width = 800; |
|
var height = 800; |
|
var svg = d3.select("svg") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.attr('viewBox', '0 0 ' + width + ' ' + height); |
|
|
|
// First branch configuration |
|
var seed = {i: 0, x: 10, y: 20, angle: 0, length: 1, depth: 0, node: [], dl:0.5}; |
|
|
|
// return scaled up of (x1,y1) and (x2,y2) of a branch for svg rendering |
|
function x1(d) {return xScale(d.x);} |
|
function y1(d) {return yScale(d.y);} |
|
function x2(d) {return xScale(endPt(d).x);} |
|
function y2(d) {return yScale(endPt(d).y);} |
|
|
|
// show tooltip when mouseover the branch and leaf |
|
function showToolTip(d) |
|
{ |
|
var branchID = d.i; |
|
var totalAmount = 0; |
|
|
|
//for all leaves => find leaves with node array containing id == d.id |
|
leaves.forEach(function(d){ |
|
var index = d.node.indexOf(branchID); |
|
|
|
if (index != -1) |
|
{ |
|
if (typeof d.amount != 'undefined') |
|
{ |
|
totalAmount += +d.amount; |
|
} |
|
} |
|
}) |
|
|
|
tooltip.transition().duration(200).style("opacity", 1); |
|
|
|
tooltip.html("<h4>branch name:"+ d.i + "</h4>" + "<h4>amount: "+ totalAmount + "</h4>") |
|
.style("left", (d3.event.pageX) + "px") |
|
.style("top", (d3.event.pageY - 28) + "px"); |
|
|
|
highlightParents(d); |
|
} |
|
|
|
// highlight branches on mouseover and mouseout |
|
function highlightParents(d) { |
|
var colour = d3.event.type === 'mouseover' ? '#CDBA96' : '#470000'; |
|
var depth = d.depth; |
|
for(var i = 0; i <= depth; i++) { |
|
d3.select('#id-'+parseInt(d.i)).style('stroke', colour); |
|
d = branches[d.parent]; |
|
} |
|
} |
|
|
|
// hightlight leaf to green on mouseover while keep the other leaf greyed out |
|
function highlightLeaf(d,i) { |
|
svg.selectAll('use') |
|
.style('fill', 'grey'); |
|
|
|
svg.select('use#id-'+ i) |
|
.style('fill', 'green'); |
|
} |
|
|
|
// the process of making branches. Will stop at maxDepth |
|
branch(seed); |
|
|
|
// scaling depend on the width and height of svg |
|
var xMax = Math.max.apply(null, branches.map(function(element){return element.x})); |
|
var xMin = Math.min.apply(null, branches.map(function(element){return element.x})); |
|
var yMax = Math.max.apply(null, branches.map(function(element){return element.y})); |
|
var yMin = Math.min.apply(null, branches.map(function(element){return element.y})); |
|
|
|
// an ugly hack to leave room for leave to grow inside the svg container |
|
// so that the leaf is not cut out of the svg |
|
var leafWidth = 0.8; |
|
|
|
var xScale = d3.scale.linear() |
|
.domain([xMin-leafWidth, xMax+leafWidth]) |
|
.range([0, width]); |
|
|
|
var yScale = d3.scale.linear() |
|
.domain([yMin-leafWidth, yMax+leafWidth]) |
|
.range([0, height]); |
|
|
|
// svg rendering of the branches |
|
svg.selectAll('line') |
|
.data(branches) |
|
.enter() |
|
.append('line') |
|
.attr('x1', x1) |
|
.attr('y1', y1) |
|
.attr('x2', x2) |
|
.attr('y2', y2) |
|
.attr('stroke', '#470000') |
|
.style('stroke-width', function(d){ |
|
var x2 = xScale(endPt(d).x); |
|
var y2 = yScale(endPt(d).y); |
|
var x1 = xScale(d.x); |
|
var y1 = yScale(d.y); |
|
var length = Math.pow(Math.pow((x2-x1),2)+Math.pow((y2-y1),2),0.5); |
|
var width = treeWidth(length,d.depth); |
|
return width < 1 ? parseInt(1) + 'px' : parseInt(width) + 'px'; |
|
}) |
|
.style('stroke-linecap', 'round') |
|
.attr('id', function(d) {return 'id-'+d.i;}) |
|
.on('mouseover', showToolTip) |
|
.on("mouseout", function(d) { |
|
tooltip.transition() |
|
.duration(500) |
|
.style("opacity", 0); |
|
highlightParents(d); |
|
}); |
|
|
|
// change this number to change the size of the leaf |
|
leafScale(1); |
|
|
|
// add tooltip to page |
|
var tooltip = d3.select("body").append("div") |
|
.attr("class", "tooltip") |
|
.style("opacity", 0); |
|
|
|
// svg rendering of leaf |
|
svg.selectAll('use') |
|
.data(leaves) |
|
.enter() |
|
.append('use') |
|
.attr('x', x2) |
|
.attr('y', y2) |
|
.attr('id', function(d,i){return 'id-' + i}) |
|
.attr('transform', function(d){ return 'rotate(' + radToDeg(-d.angle) + ' ' + xScale(endPt(d).x) + ' ' + yScale(endPt(d).y) + ')';}) |
|
.attr("xlink:href", "#layer1") |
|
.attr('style', 'fill:green;stroke:black') |
|
.attr('visibility', function(d){ |
|
return d.visible ? 'visible' : 'hidden' |
|
}) |
|
.on("mouseover", function(d,i) { |
|
tooltip.transition().duration(200).style("opacity", 1); |
|
tooltip.html("<h4>name: "+ d.name + "</h4>" + "<h4>amount: "+ d.amount + "</h4>") |
|
.style("left", (d3.event.pageX) + "px") |
|
.style("top", (d3.event.pageY - 28) + "px"); |
|
highlightLeaf(d,i); |
|
}) |
|
.on("mouseout", function(d) { |
|
tooltip.transition() |
|
.duration(500) |
|
.style("opacity", 0); |
|
svg.selectAll('use') |
|
.style('fill', 'green'); |
|
}); |
|
|
|
// another hack to hide rounded line of first branch (the trunk) |
|
var trunk = branches[0]; |
|
svg.append('rect') |
|
.attr('height', getLength(trunk)*0.3) |
|
.attr('width', width) |
|
.attr('x', 0) |
|
.attr('y', yScale(trunk.y)) |
|
.attr('fill', 'white'); |
|
|
|
// handle click event of buttons |
|
d3.select('#donate').on('click', update); |
|
d3.select('#animate').on('click', animate); |
|
d3.select('#atonce').on('click', atonce); |
|
d3.select('#increment').on('click', incrementalFill); |
|
|
|
function update() { |
|
|
|
console.log('btn clicked') |
|
var name = $('#field-name').val(); |
|
var amount = $('#field-amount').val(); |
|
|
|
var leavesCopy = leaves.map(function(d){return d.leafId}); |
|
console.log(leavesCopy) |
|
|
|
var randIndex = Math.random() * leavesCopy.length; |
|
randIndex = Math.floor(randIndex); |
|
|
|
var removed = leavesCopy.splice(randIndex, 1)[0]; |
|
|
|
leaves[removed].visible = true; |
|
|
|
leaves[removed].name = name; |
|
leaves[removed].amount = amount; |
|
|
|
svg |
|
.select('use#id-' + removed) |
|
//.data(leaves) |
|
.transition() |
|
.delay(200) |
|
.attr('visibility', function(d){return d.visible ? 'visible' : 'hidden'}); |
|
} |
|
|
|
|
|
function randomFill() |
|
{ |
|
var halfFull = leaves.length / 4; |
|
|
|
for (i = 0; i < halfFull; i++) |
|
{ |
|
var randIndex = Math.random() * leaves.length; |
|
randIndex = Math.round(randIndex); |
|
|
|
leaves[randIndex].visible = true; |
|
|
|
leaves[randIndex].name = "name"; |
|
leaves[randIndex].amount = "amount"; |
|
console.log(leaves[numOfClick]); |
|
|
|
svg |
|
.select('use') |
|
//.data(leaves) |
|
.transition() |
|
.attr('visibility', function(d){return d.visible ? 'visible' : 'hidden'}); |
|
} |
|
} |
|
|
|
var increment = 0; |
|
var leavesCopy = leaves.map(function(d){return d.leafId}); |
|
var quarter = leavesCopy.length*0.25; |
|
|
|
function incrementalFill() { |
|
increment++; |
|
|
|
console.log(quarter); |
|
console.log(leavesCopy.length); |
|
|
|
for (i=0;i<quarter;i++) { |
|
var randIndex = Math.random() * leavesCopy.length; |
|
randIndex = Math.floor(randIndex); |
|
var removed = leavesCopy.splice(randIndex, 1)[0]; |
|
leaves[removed].visible = true; |
|
} |
|
|
|
svg.selectAll('use') |
|
.transition() |
|
.attr('visibility', function(d){return d.visible ? 'visible' : 'hidden'}); |
|
|
|
|
|
} |
|
|
|
function animate() { |
|
for (i = 0; i < leaves.length; i++) { |
|
setTimeout(function(i) { |
|
leaves[i].visible = true; |
|
svg |
|
.selectAll('use#id-'+i) |
|
.data(leaves) |
|
.transition() |
|
.attr('visibility', function(d){return d.visible ? 'visible' : 'hidden'}); |
|
}, 100, i); |
|
|
|
|
|
} |
|
} |
|
|
|
function atonce() { |
|
leaves.forEach(function(d){ |
|
d.visible = true; |
|
}) |
|
svg |
|
.selectAll('use') |
|
.data(leaves) |
|
.transition() |
|
.attr('visibility', function(d){return d.visible ? 'visible' : 'hidden'}); |
|
} |
|
|
|
function leafScale(scale) { |
|
d3.select('g#layer1') |
|
.attr('transform', 'scale(' + scale*0.1 + ') rotate(132 355 210)'); |
|
} |
|
|
|
function treeWidth(length,depth) { |
|
return depth === 0 ? (0.4059*length-8.1419)*0.73 : 0.4059*length-8.1419; |
|
} |
|
|
|
function getLength(branch) { |
|
var x2 = xScale(endPt(branch).x); |
|
var y2 = yScale(endPt(branch).y); |
|
var x1 = xScale(branch.x); |
|
var y1 = yScale(branch.y); |
|
return Math.pow(Math.pow((x2-x1),2)+Math.pow((y2-y1),2),0.5); |
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |