Last active
July 12, 2019 12:42
-
-
Save vkuchinov/64a994d2f6ae5ea7d435b1687d313185 to your computer and use it in GitHub Desktop.
D3.JS Bubble Stacks Mockup
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
/* | |
WEEKLY TICKET STACKS Ф | |
[based on two points prespective built with Ф golden ratio] | |
zl: { x: -11,070, y: 11,993 } | |
zm: { x: 0.0, y: 14.392 } | |
zr: { x: 12.749, y: 12.466 } | |
tp: { x: 0.0, y: 0.0 } | |
rp: { x: 12.749, y: -5.044 } | |
bp: { x: 2.887, y: -14.904 } | |
lp: { x: -11.070, y: -6.277 } | |
zl_lp: 18.270 | |
zm_tp: 14.392 | |
zr_rp: 17.507 | |
lp_bp: 16,408 | |
bp_rp: 13.948 | |
rp_tp: 13.710 | |
REFERENCES: | |
http://corpuschristijobsonline.com/wp-content/uploads/2019/01/3d-stacked-bar-chart-plot-in-r-stack-overflow.png | |
@author Vladimir V KUCHINOV | |
@email helloworld@vkuchinov.co.uk | |
*/ | |
var svg, chart, background, g, defs, div, fulldata, data, w = 800, h = 600, dist, offset = 64, vratio = 1.0, radius = 18, zAxis, zValues, ratio = 0.3814, stacksData = [], filter = false, filtered = [true, true, true]; | |
var offset = 64, scaleDown = 9; | |
var days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; //could be German or whatever | |
var states = ["Red Zone", "Yellow Zone", "Red Zone"]; | |
var maxStack = Number.NEGATIVE_INFINITY; | |
var colors = ["#F0F0F0", "#FF652F", "#FFE400", "#14A76C"]; | |
var scale = 20.0; | |
var zl = { x: -11.070 * scale, y: -11.993 * scale }, | |
zm = { x: 0.0 * scale, y: -14.392 * scale }, | |
zr = { x: 12.749 * scale, y: -12.466 * scale }, | |
tp = { x: 0.0, y: 0.0 }, | |
rp = { x: 12.749 * scale, y: 5.044 * scale }, | |
bp = { x: 2.887 * scale, y: 14.904 * scale }, | |
lp = { x: -11.070 * scale, y: 6.277 * scale }; | |
d3.legend = function(){ | |
function legend(selection_){ | |
selection_.each(function(d_, i_) { | |
var g = d3.select(this).attr("transform", "translate(" + d_.x + "," + d_.y + ")") | |
var back = g.append("rect") | |
.attr("width", d_.w) | |
.attr("height", d_.h) | |
.attr("fill", "#DEDEDE") | |
.attr("opacity", 0.5); | |
var legendlabels = g.selectAll(".legendlabel") | |
.data(d_.states) | |
.enter() | |
.append("text") | |
.attr("class", function(d2_, j_){ return "legendText_" + j_; }) | |
.attr("dx", 40) | |
.attr("dy", function(d2_, j_){ return 25 + j_ * 25; }) | |
.text(function(d_){ return d_; }) | |
var boxes = g.selectAll(".box") | |
.data(d_.states) | |
.enter() | |
.append("rect") | |
.attr("class", function(d2_, j_){ return "legendRect_" + j_; }) | |
.attr("x", 20) | |
.attr("y", function(d2_, j_){ return 16 + j_ * 25; }) | |
.attr("width", 12) | |
.attr("height", 12) | |
.attr("fill", function(d2_, j_){ return colors[j_ + 1]; }); | |
var placeholder = g.selectAll(".box") | |
.data(d_.states) | |
.enter() | |
.append("rect") | |
.attr("x", 10) | |
.attr("y", function(d2_, j_){ return 11 + j_ * 25; }) | |
.attr("width", 108) | |
.attr("height", 22) | |
.attr("fill", "transparent") | |
.on("click", function(d2_, j_){ | |
filtered[j_] = !filtered[j_]; | |
if(filtered[j_]){ | |
d3.select(".legendText_" + j_).attr("fill", "#000000"); | |
d3.select(".legendRect_" + j_).attr("fill", colors[j_ + 1]); | |
drawBubbleStacks(); | |
}else{ | |
if(atLeastOneLeft()){ | |
d3.select(".legendText_" + j_).attr("fill", "#808080"); | |
d3.select(".legendRect_" + j_).attr("fill", "#808080"); | |
drawBubbleStacks(); | |
} | |
} | |
}) | |
}); | |
} | |
return legend; | |
} | |
d3.bubbleStack = function(){ | |
function bubbleStack(selection_){ | |
selection_.each(function(d_, i_) { | |
var g = d3.select(this).attr("transform", "translate(" + (128 + (800 - 128)/ 10 * d_.y) + "," + ( 64 + (600 - 64) / days.length * d_.x) + ")") | |
var total = d_.stack.reduce((a_, b_) => a_ + b_, 0) / scaleDown; | |
var pack = d3.pack() | |
.size([total, total]) | |
.padding(0); | |
this.root = d3.hierarchy(d_.hierarchy) | |
.sum(function(d2_) { return d2_.size; }) | |
//.sort(function(a_, b_) { return b_.value - a_.value; }); | |
this.nodes = pack(this.root).descendants(); | |
var circle = g.selectAll("circle") | |
.data(this.nodes) | |
.enter().append("circle") | |
.attr("class", function(d2_) { return d2_.parent ? d2_.children ? "node" : "node node--leaf" : "node node--root"; }) | |
.attr("cx", function(d2_) { return d2_.x - total/2 }) | |
.attr("cy", function(d2_) { return d2_.y - total/2 }) | |
.attr("r", function(d2_) { return d2_.r }) | |
.attr("fill", function(d2_, k_){ if(k_ != 0){ return d2_.data.color; } else { return colors[0]; } }) | |
.on("mouseover", function(d2_, k_){ | |
if(k_ != 0){ | |
var str = "" + d_.stack[d2_.data.group] + " " + states[d2_.data.group] + " tickets " + " on " + d_.hierarchy.name; | |
div.transition().duration(500).style("opacity", 0.9); | |
div.html(str).style("left", (d3.event.pageX) + "px").style("top", (d3.event.pageY - 28) + "px"); | |
}else{ | |
var str = "There are " + d_.total + " " + states.map(function(s_, m_) { if(filtered[m_]) return s_ + "" }) + " tickets " + " on " + d_.hierarchy.name; | |
str = str.replace(" ,", " ").replace(",,", ","); | |
div.transition().duration(500).style("opacity", 0.9); | |
div.html(str).style("left", (d3.event.pageX) + "px").style("top", (d3.event.pageY - 28) + "px"); | |
} | |
}) | |
.on("mouseout", function(d2_){ | |
div.transition().duration(500).style("opacity", 0); | |
}); | |
}); | |
} | |
return bubbleStack; | |
} | |
d3.json("mockup.json", function(error_, data_) { | |
if (error_) throw error_; | |
fulldata = data_; | |
inits(); | |
}); | |
function inits(){ | |
console.log("%cWeekly Ticket Bubble Grid β demo", "color: #494949; font-size: 18px; font-family: sans-serif;"); | |
console.log("%cby Vladimir V KUCHINOV", "color: #494949; font-size: 12px; font-style: italic;font-family: sans-serif;"); | |
svg = d3.select("body").append("svg") | |
.attr("preserveAspectRatio", "xMinYMin meet") | |
.attr("viewBox", "0 0 " + w + " " + h) | |
.classed("svg-content", true); | |
background = svg.append("rect").attr("width", w).attr("height", h).attr("fill", "transparent").on("click", function(){ filter = false, d3.selectAll(".cylinder").attr("opacity", 1.0); }); | |
defs = svg.append("defs"); | |
div = d3.select("#tooltip"); | |
var weekdays = svg.selectAll(".weekday") | |
.data(days) | |
.enter() | |
.append("text") | |
.attr("dx", 48) | |
.attr("dy", function(d_, i_) { return 64 + (600 - 64) / days.length * i_; }) | |
.attr("text-ancho", "end") | |
.text(function(d_) { return d_; }); | |
var ages = svg.selectAll(".age") | |
.data(new Array(10).fill(0)) | |
.enter() | |
.append("text") | |
.attr("dx", function(d_, i_) { return 128 + (800 - 128)/ 10 * i_; }) | |
.attr("dy", 24) | |
.attr("text-ancho", "end") | |
.text(function(d_, i_) { return i_ + 1; }) | |
drawBubbleStacks(); | |
var legend = svg.selectAll(".legendBox") | |
.data([{states: states, x: w - 38 - 128, y: 38, w: 128, h: 92 }]) | |
.enter().append("g") | |
.attr("class", "legendBox") | |
.call(d3.legend()); | |
} | |
function atLeastOneLeft(){ | |
var T = 0; | |
filtered.forEach(function(f_){ if(f_) { T++; } }); | |
return T > 0 ? true : false; | |
} | |
function drawBubbleStacks(){ | |
parseData(fulldata); | |
d3.selectAll("#chart").remove(); | |
var g = svg.append("g").attr("id", "chart"); | |
var stacks = g.selectAll(".stack") | |
.data(stacksData) | |
.enter() | |
.append("g") | |
.call(d3.bubbleStack()) | |
} | |
function parseData(data_){ | |
var EPS = 5; | |
var days = Object.keys(data_); | |
days.forEach(function(d_, i_){ | |
data_[d_].forEach(function(a_, j_){ | |
var total = 0.0; | |
data = { name: d_ + ", " + a_.age + " days old", children: [] }; | |
a_.stack.forEach(function(s_, k_){ | |
if(filtered[k_]){ data.children.push({ group: k_, name : states[k_], color: colors[k_ + 1], size: s_ / 2}); total += s_; } | |
}) | |
stacksData.push({ x: i_ , y: j_, d: d_, a: a_.age, stack: a_.stack, hierarchy : data, total: total }) | |
maxStack = Math.max(maxStack, a_.stack.reduce((a, b) => a + b, 0)); | |
}) | |
}); | |
} | |
function average1D(v0_, v1_){ return (Number(v0_) + Number(v1_)) / 2.0; } | |
function remapFloat(v_, min0_, max0_, min1_, max1_) { | |
return min1_ + (v_ - min0_) / (max0_ - min0_) * (max1_ - min1_); | |
} | |
function distance(x0_, y0_, x1_, y1_){ | |
return Math.sqrt(Math.pow(x1_ - x0_, 2) + Math.pow(y1_ - y0_, 2)); | |
} | |
function distance2D(v0_, v1_){ | |
return Math.sqrt(Math.pow(v1_.x - v0_.x, 2) + Math.pow(v1_.y - v0_.y, 2)); | |
} | |
function lerp2D(v0_, v1_, t_){ | |
return { x: v0_.x * t_ + (1.0 - t_) * v1_.x, y: v0_.y * t_ + (1.0 - t_) * v1_.y }; | |
} | |
function lerp1D(f0_, f1_, t_){ return f0_ * t_ + (1.0 - t_) * f1_; } |
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Weekly Ticket Bubble Stack φ demo</title> | |
<script src="http://d3js.org/d3.v4.min.js"></script> | |
<style> | |
@import url('https://fonts.googleapis.com/css?family=Karla&display=swap'); | |
body{ margin: 0; font-family: 'Karla', sans-serif; font-size: 12px; } | |
#d3placeholder { | |
margin: 0; | |
width: 100%; | |
height: 100%; | |
} | |
#tooltip { | |
position: absolute; | |
text-align: center; | |
padding: 8px; | |
font: 12px sans-serif; | |
background-color: #DEDEDE; | |
border: 0px; | |
border-radius: 8px; | |
pointer-events: none; | |
opacity: 0.0; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="d3placeholder"></div> | |
<script src="app.js"></script> | |
<div id="tooltip"></div> | |
</body> | |
</html> |
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
{ | |
"Mon":[ | |
{ | |
"age":1, | |
"stack":[ | |
23, | |
26, | |
32 | |
] | |
}, | |
{ | |
"age":2, | |
"stack":[ | |
15, | |
24, | |
44 | |
] | |
}, | |
{ | |
"age":3, | |
"stack":[ | |
124, | |
109, | |
200 | |
] | |
}, | |
{ | |
"age":4, | |
"stack":[ | |
151, | |
99, | |
151 | |
] | |
}, | |
{ | |
"age":5, | |
"stack":[ | |
197, | |
141, | |
279 | |
] | |
}, | |
{ | |
"age":6, | |
"stack":[ | |
105, | |
111, | |
240 | |
] | |
}, | |
{ | |
"age":7, | |
"stack":[ | |
163, | |
101, | |
199 | |
] | |
} | |
], | |
"Tue":[ | |
{ | |
"age":1, | |
"stack":[ | |
204, | |
131, | |
249 | |
] | |
}, | |
{ | |
"age":2, | |
"stack":[ | |
22, | |
21, | |
32 | |
] | |
}, | |
{ | |
"age":3, | |
"stack":[ | |
24, | |
24, | |
63 | |
] | |
}, | |
{ | |
"age":4, | |
"stack":[ | |
154, | |
100, | |
202 | |
] | |
}, | |
{ | |
"age":5, | |
"stack":[ | |
110, | |
100, | |
200 | |
] | |
}, | |
{ | |
"age":6, | |
"stack":[ | |
142, | |
132, | |
237 | |
] | |
}, | |
{ | |
"age":7, | |
"stack":[ | |
97, | |
68, | |
120 | |
] | |
}, | |
{ | |
"age":8, | |
"stack":[ | |
85, | |
55, | |
108 | |
] | |
} | |
], | |
"Wed":[ | |
{ | |
"age":1, | |
"stack":[ | |
137, | |
109, | |
269 | |
] | |
}, | |
{ | |
"age":2, | |
"stack":[ | |
140, | |
132, | |
255 | |
] | |
}, | |
{ | |
"age":3, | |
"stack":[ | |
22, | |
21, | |
40 | |
] | |
}, | |
{ | |
"age":4, | |
"stack":[ | |
69, | |
24, | |
82 | |
] | |
}, | |
{ | |
"age":5, | |
"stack":[ | |
107, | |
114, | |
250 | |
] | |
}, | |
{ | |
"age":6, | |
"stack":[ | |
132, | |
98, | |
208 | |
] | |
}, | |
{ | |
"age":7, | |
"stack":[ | |
121, | |
122, | |
223 | |
] | |
}, | |
{ | |
"age":8, | |
"stack":[ | |
75, | |
60, | |
135 | |
] | |
}, | |
{ | |
"age":9, | |
"stack":[ | |
75, | |
50, | |
90 | |
] | |
} | |
], | |
"Thu":[ | |
{ | |
"age":1, | |
"stack":[ | |
125, | |
120, | |
250 | |
] | |
}, | |
{ | |
"age":2, | |
"stack":[ | |
130, | |
105, | |
236 | |
] | |
}, | |
{ | |
"age":3, | |
"stack":[ | |
143, | |
102, | |
241 | |
] | |
}, | |
{ | |
"age":4, | |
"stack":[ | |
20, | |
20, | |
31 | |
] | |
}, | |
{ | |
"age":5, | |
"stack":[ | |
28, | |
22, | |
40 | |
] | |
}, | |
{ | |
"age":6, | |
"stack":[ | |
112, | |
65, | |
90 | |
] | |
}, | |
{ | |
"age":7, | |
"stack":[ | |
40, | |
50, | |
120 | |
] | |
}, | |
{ | |
"age":8, | |
"stack":[ | |
75, | |
75, | |
100 | |
] | |
}, | |
{ | |
"age":9, | |
"stack":[ | |
60, | |
40, | |
70 | |
] | |
}, | |
{ | |
"age":10, | |
"stack":[ | |
50, | |
35, | |
75 | |
] | |
} | |
], | |
"Fri":[ | |
{ | |
"age":1, | |
"stack":[ | |
128, | |
160, | |
200 | |
] | |
}, | |
{ | |
"age":2, | |
"stack":[ | |
100, | |
120, | |
225 | |
] | |
}, | |
{ | |
"age":3, | |
"stack":[ | |
130, | |
100, | |
225 | |
] | |
}, | |
{ | |
"age":4, | |
"stack":[ | |
175, | |
100, | |
200 | |
] | |
}, | |
{ | |
"age":5, | |
"stack":[ | |
20, | |
20, | |
40 | |
] | |
}, | |
{ | |
"age":6, | |
"stack":[ | |
25, | |
20, | |
25 | |
] | |
}, | |
{ | |
"age":7, | |
"stack":[ | |
100, | |
75, | |
100 | |
] | |
}, | |
{ | |
"age":8, | |
"stack":[ | |
80, | |
50, | |
120 | |
] | |
}, | |
{ | |
"age":9, | |
"stack":[ | |
71, | |
78, | |
90 | |
] | |
}, | |
{ | |
"age":10, | |
"stack":[ | |
55, | |
40, | |
65 | |
] | |
} | |
], | |
"Sat":[ | |
{ | |
"age":1, | |
"stack":[ | |
130, | |
100, | |
190 | |
] | |
}, | |
{ | |
"age":2, | |
"stack":[ | |
130, | |
115, | |
234 | |
] | |
}, | |
{ | |
"age":3, | |
"stack":[ | |
125, | |
115, | |
240 | |
] | |
}, | |
{ | |
"age":4, | |
"stack":[ | |
130, | |
100, | |
225 | |
] | |
}, | |
{ | |
"age":5, | |
"stack":[ | |
175, | |
100, | |
250 | |
] | |
}, | |
{ | |
"age":6, | |
"stack":[ | |
20, | |
15, | |
30 | |
] | |
}, | |
{ | |
"age":7, | |
"stack":[ | |
20, | |
10, | |
20 | |
] | |
}, | |
{ | |
"age":8, | |
"stack":[ | |
80, | |
50, | |
80 | |
] | |
}, | |
{ | |
"age":9, | |
"stack":[ | |
76, | |
50, | |
111 | |
] | |
}, | |
{ | |
"age":10, | |
"stack":[ | |
65, | |
64, | |
83 | |
] | |
} | |
], | |
"Sun":[ | |
{ | |
"age":1, | |
"stack":[ | |
59, | |
30, | |
55 | |
] | |
}, | |
{ | |
"age":2, | |
"stack":[ | |
135, | |
90, | |
166 | |
] | |
}, | |
{ | |
"age":3, | |
"stack":[ | |
130, | |
115, | |
240 | |
] | |
}, | |
{ | |
"age":4, | |
"stack":[ | |
120, | |
115, | |
230 | |
] | |
}, | |
{ | |
"age":5, | |
"stack":[ | |
130, | |
112, | |
222 | |
] | |
}, | |
{ | |
"age":6, | |
"stack":[ | |
175, | |
113, | |
220 | |
] | |
}, | |
{ | |
"age":7, | |
"stack":[ | |
20, | |
20, | |
32 | |
] | |
}, | |
{ | |
"age":8, | |
"stack":[ | |
10, | |
15, | |
20 | |
] | |
}, | |
{ | |
"age":9, | |
"stack":[ | |
78, | |
50, | |
80 | |
] | |
}, | |
{ | |
"age":10, | |
"stack":[ | |
80, | |
49, | |
100 | |
] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment