|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> |
|
<!-- <link rel="stylesheet" type="text/css" href="index.css"/> --> |
|
<style> |
|
html,body,.content{ |
|
height:100%; |
|
width:100%; |
|
margin:0; |
|
|
|
} |
|
svg{ |
|
/* To fit better in a block */ |
|
margin-top: -3em; |
|
} |
|
/* from https://bl.ocks.org/mbostock/raw/3887235/e8f21e130ddc82f9f47d37d0b9d6345049dfd0f2/ */ |
|
text { |
|
transition:all 0.5s linear; |
|
font-family: 'Roboto', sans-serif; |
|
font-size:3em; |
|
text-anchor: middle; |
|
} |
|
.arc text { |
|
font-size: 10px |
|
} |
|
|
|
.arc path { |
|
/* animation: * ease-in-out; */ |
|
transition:fill-opacity 0.5s linear, stroke-width 0.5s linear; |
|
/* ease-in-out; */ |
|
stroke: #fff; |
|
stroke-width: 5px; |
|
} |
|
|
|
.arc.happen path{ |
|
fill: rgb(36, 36, 36); |
|
} |
|
|
|
.arc.not-happen path{ |
|
fill-opacity:1; |
|
fill:rgb(224, 224, 224); |
|
} |
|
|
|
|
|
.together .arc.not-happen path { |
|
fill-opacity:0; |
|
} |
|
|
|
.together .arc path { |
|
stroke-width:2px; |
|
|
|
} |
|
|
|
.sum-line { |
|
stroke-width: 10px; |
|
stroke: #d5d5d5; |
|
stroke-linecap: round; |
|
} |
|
|
|
.sum-line.button { |
|
transition:fill 0.5s ease-in-out; |
|
fill: #d5d5d5; |
|
stroke: white; |
|
stroke-width:5px; |
|
} |
|
|
|
.together .sum-line.button { |
|
fill: black; |
|
} |
|
|
|
.together .pie-label text { |
|
font-size:2em; |
|
} |
|
.together-pie-label-add { |
|
font-size:12px; |
|
fill:white; |
|
} |
|
</style> |
|
<!-- Start by following this example --> |
|
<!-- https://bl.ocks.org/mbostock/3887235 --> |
|
<!-- Lets not worry about require and etc now --> |
|
|
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
</head> |
|
<body> |
|
<svg width="960" height="500"></svg> |
|
|
|
<script > |
|
// Just to satisfy JSHint |
|
var d3 = window.d3 || {}; |
|
|
|
var svg = d3.select("svg"), |
|
width = +svg.attr("width"), |
|
height = +svg.attr("height"), |
|
radius = Math.min(width, height) / 4; |
|
|
|
var defs = svg.append('defs'); |
|
|
|
var together_pie_group_label_clip_id = 'together_pie_group_label_clip'; |
|
|
|
var together_pie_group_label_clip = defs |
|
.append('clipPath') |
|
.attr('id', 'together_pie_group_label_clip'); |
|
|
|
|
|
var pie = d3.pie() |
|
.sort(null) |
|
.value(function (d) { return d.prob; }); |
|
// https://developer.mozilla.org/en-US/docs/Web/Events/wheel |
|
// https://github.com/d3/d3-selection/blob/master/README.md#selection_on |
|
//One scroll twirl is about 10 events, each which do 100 |
|
//So ten scrolls to get to a full wheel: |
|
// 10000 was too much |
|
var scroll_range = 10000 / 4; |
|
|
|
|
|
var arc_path = d3.arc() |
|
.outerRadius(radius - 10) |
|
.innerRadius(0); |
|
|
|
|
|
var get_prob_data = function (prob_options, non_prob_options) { |
|
|
|
non_prob_options = non_prob_options || {}; |
|
|
|
//Returns two slicse of pie |
|
return [ |
|
{ |
|
prob: prob_options.prob, |
|
event: prob_options.event, |
|
is_happen: false |
|
}, |
|
{ |
|
prob: (1 - prob_options.prob), |
|
event: non_prob_options.event || 'not ' + prob_options.event, |
|
/* THe second slice, which will dominate the horizontal left of the pie |
|
should represent the first of the 2 pies, which is on the left; |
|
so that when the pie group labels come together, the labels |
|
position relative to each other: 36% + 64% |
|
correspond to the way the pie slices fit together, 36% arc on the *left* of the circle/pie; and 64% on the *right* |
|
*/ |
|
is_happen: true |
|
}] |
|
}; |
|
|
|
var y_point = scroll_range / 2; |
|
|
|
/* LINES BEFORE BUTTONS*/ |
|
|
|
var sum_line_label_line = svg |
|
.append('line') |
|
.attr('class', 'sum-line label'); |
|
|
|
var sum_line_pie_line = svg |
|
.append('line') |
|
.attr('class', 'sum-line pie') |
|
.attr('y1', height / 2 + radius) |
|
.attr('y2', height / 2 + radius); |
|
|
|
|
|
|
|
var button_radius = 20; |
|
|
|
var sum_line_pie_button = svg |
|
.append('circle') |
|
.attr('class', 'sum-line pie button') |
|
.attr('cx', width / 2) |
|
.attr('cy', height / 2 + radius) |
|
.attr('r', 20); |
|
|
|
|
|
var sum_line_label_button = svg |
|
.append('circle') |
|
.attr('class', 'sum-line label button') |
|
.attr('cx', width / 2) |
|
.attr('r', button_radius); |
|
/* |
|
var sum_line_label_close_button = svg |
|
.append('circle') |
|
.attr('class', 'sum-line pie close button') |
|
.attr('cx', width / 2) |
|
.attr('cy', height / 2 - 100) |
|
.attr('r', button_radius); |
|
*/ |
|
|
|
// /Not expected to be passed, so I won't call it func |
|
var original_sum_lines = function () { |
|
|
|
sum_line_label_button |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('cy', height / 2 - 40); |
|
|
|
sum_line_label_line |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('y1', height / 2 - 40) //radius*0.5 ) |
|
.attr('y2', height / 2 - 40)//radius*0.5); |
|
.attr('x1', width / 4 + 60) //*1.1) |
|
.attr('x2', 3 * width / 4 - 60) // *1.1) |
|
|
|
sum_line_pie_line |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
|
|
.attr('x1', width / 4 + radius) //*1.1) |
|
.attr('x2', 3 * width / 4 - radius) // *1.1) |
|
|
|
} |
|
|
|
original_sum_lines(); |
|
// /Not expected to be passed, so I won't call it func |
|
var together_sum_lines = function () { |
|
|
|
sum_line_label_button |
|
.transition() |
|
// .delay(500) |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('cy', height / 2 - radius * 0.85); |
|
|
|
sum_line_label_line |
|
|
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
// .delay(500) |
|
.duration(750)//radius*0.5); |
|
.attr('y1', height / 2 - 100) //radius*0.5 ) |
|
.attr('y2', height / 2 - 100) |
|
.attr('x1', width / 2) //*1.1) |
|
.attr('x2', width / 2) // *1.1); |
|
|
|
sum_line_pie_line |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750)//radius*0.5); |
|
.attr('x1', width / 2) //*1.1) |
|
.attr('x2', width / 2) // *1.1) |
|
|
|
} |
|
|
|
/* |
|
// d3.select(sum_line_pie_button) |
|
|
|
var button_size_func = function (button_Selection, new_radius) { |
|
return function () { |
|
button_Selection |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('r', new_radius); |
|
} |
|
} |
|
|
|
|
|
sum_line_label_button |
|
.on('mouseover', button_size_func(sum_line_label_button, button_radius * 1.5)) |
|
.on('mouseout', button_size_func(sum_line_label_button, button_radius)) |
|
|
|
|
|
sum_line_pie_button |
|
.on('mouseover', button_size_func(sum_line_pie_button, button_radius * 1.5)) |
|
.on('mouseout', button_size_func(sum_line_pie_button, button_radius)) |
|
*/ |
|
|
|
var original_pie_translate_func = function (d, i) { |
|
return "translate(" + (2 * i + 1) * width / 4 + "," + 3 * height / 4 + ")"; |
|
} |
|
|
|
var together_pie_translate_func = function (d, i) { |
|
//index doesnt matter, both pies move to the same point in the |
|
//horizontal center point between the circles' centers |
|
return "translate(" + width / 2 + "," + 3 * height / 4 + ")"; |
|
}; |
|
|
|
var are_together = false; |
|
|
|
var pie_translate_func = function (transform_func) { |
|
|
|
return function () { |
|
return svg |
|
.classed('together', are_together) |
|
.selectAll('.prob-pie') |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr("transform", transform_func); |
|
} |
|
|
|
}; |
|
|
|
// var prob_text_groups = pie_groups.selectAll('.text'); |
|
|
|
// var prob_text_groups = prob_text_groups |
|
// .enter() |
|
var pie_text_format = d3.format('.0%'); |
|
|
|
var text_adjust = function () { |
|
|
|
/* TODO: Both numbers tween to 100%*/ |
|
d3.selectAll('.pie-label text') |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
/* |
|
.tween('text', function (d) { |
|
var from = are_together ? pie_text_val(d) : 1; |
|
var to = are_together ? 1 : pie_text_val(d); |
|
|
|
var i = d3.interpolate(from, to); |
|
var node = this; |
|
|
|
return function (t) { |
|
node.textContent = pie_text_format(i(t)); |
|
}; |
|
}); |
|
*/ |
|
|
|
} |
|
|
|
var toggle_together_func = function (d, i) { |
|
if (are_together) { |
|
are_together = false; |
|
draw_pie(); |
|
text_adjust(); |
|
original_sum_lines(); |
|
original_pie_group_labels(); |
|
return pie_translate_func(original_pie_translate_func)();//(d, i); |
|
} else { |
|
|
|
are_together = true; |
|
text_adjust(); |
|
together_sum_lines(); |
|
together_pie_group_labels(); |
|
return pie_translate_func(together_pie_translate_func)();//(d, i) |
|
} |
|
// .on('end', function(){ |
|
|
|
// }); |
|
}; |
|
/* |
|
sum_line_label_close_button |
|
.on('click', function (d, i) { |
|
if (!are_together) { |
|
return; |
|
} |
|
|
|
}); |
|
*/ |
|
|
|
// .attr({ |
|
// x1: function(){ return 20; }, |
|
// y1: "100", |
|
// x2: "100", |
|
// y2: "20", |
|
|
|
|
|
// }); |
|
|
|
|
|
svg.on('wheel', function (data, index) { |
|
|
|
d3.event.preventDefault(); |
|
// console.log(index); |
|
// WheelEvent {isTrusted: true, deltaX: -0, deltaY: 100, deltaZ: 0, deltaMode: 0, …} |
|
// console.log(d3.event); |
|
y_point += d3.event.deltaY; |
|
y_point = Math.min(y_point, scroll_range); |
|
y_point = Math.max(y_point, 0); |
|
|
|
draw_pie(); |
|
}); |
|
|
|
var get_scroll_data = function () { |
|
|
|
return get_prob_data({ prob: y_point / scroll_range, event: 'heads' }, { event: 'tails' }); |
|
} |
|
|
|
|
|
|
|
var tween_each_arc = function (pie_data) { |
|
return function (d, i) { |
|
|
|
var new_slice = pie_data[i]; |
|
|
|
var endAngle_interpolate = d3.interpolate(d.endAngle, new_slice.endAngle); |
|
var startAngle_interpolate = d3.interpolate(d.startAngle, new_slice.startAngle); |
|
|
|
return function (t) { |
|
|
|
d.endAngle = endAngle_interpolate(t); |
|
d.startAngle = startAngle_interpolate(t); |
|
|
|
return arc_path(d); |
|
}; |
|
} |
|
}; |
|
|
|
var get_inverse_prob_data = function (prob_data) { |
|
|
|
return prob_data.map(function (prob_options) { |
|
return { |
|
prob: prob_options.prob, |
|
event: prob_options.event, |
|
is_happen: !prob_options.is_happen |
|
}; |
|
}); |
|
} |
|
|
|
var get_scroll_pie_data = function () { |
|
|
|
//DATA: |
|
//========== |
|
var data = get_scroll_data(); |
|
//inverse the second based on first |
|
var inverse_data = get_inverse_prob_data(data); |
|
|
|
//PIES: |
|
//========== |
|
//apply pie to both |
|
var pie_data = pie(data); |
|
var pie_inverse_data = pie(inverse_data); |
|
|
|
//Return the happens case, then the inverse case: |
|
return [pie_data, pie_inverse_data]; |
|
|
|
} |
|
|
|
var pie_groups = svg.selectAll('g.prob-pie') |
|
.data(get_scroll_pie_data()); |
|
|
|
|
|
pie_groups = pie_groups |
|
.enter() |
|
.append('g') |
|
.attr('class', 'prob-pie') |
|
.attr("transform", original_pie_translate_func); |
|
|
|
var pie_text_val = function (d) { |
|
|
|
var is_happening_slices = d.filter(function (prob_options) { |
|
// https://github.com/d3/d3-format |
|
// "for human consumption" |
|
return prob_options.data.is_happen; |
|
}); |
|
|
|
//there are two slices; take only the one which is happening; |
|
// that is the best way to represent a binary probability pie |
|
var is_happening_slice = is_happening_slices[0]; |
|
|
|
return is_happening_slice.data.prob; |
|
|
|
} |
|
var pie_text_func = function (d) { |
|
|
|
return pie_text_format(pie_text_val(d)); |
|
}; |
|
|
|
var pie_group_labels = pie_groups |
|
.append("g") |
|
.attr("class", 'pie-label') |
|
.append('text') |
|
.text(pie_text_func); |
|
|
|
var raised_label_height_offset = radius * 1.75; |
|
|
|
var together_pie_group_label = svg |
|
.append("g") |
|
.attr('transform', together_pie_translate_func) |
|
.attr("class", 'together-pie-label') |
|
.append('text') |
|
.attr('y', -radius * 1.2) |
|
.text('=100%'); |
|
|
|
var together_pie_group_label_add = svg |
|
.append("g") |
|
.attr('transform', together_pie_translate_func) |
|
.attr("class", 'together-pie-label-add') |
|
.append('text') |
|
// .attr('fill', 'white') |
|
// .attr('y', -radius * 1.7) |
|
//this is same as the labels |
|
// .attr('y', -radius * 1.2) |
|
.attr('y', -raised_label_height_offset) |
|
.text('+'); |
|
|
|
together_pie_group_label_add.on('click', toggle_together_func); |
|
sum_line_label_button.on('click', toggle_together_func); |
|
sum_line_pie_button.on('click', toggle_together_func); |
|
|
|
var together_pie_group_label_clip_width = 200; |
|
|
|
var together_pie_group_label_clip_rect = together_pie_group_label_clip |
|
.append('rect') |
|
//No need to transform it |
|
// .attr('transform', overlapping_pie_translate_func) |
|
.attr('x', -together_pie_group_label_clip_width / 2) |
|
.attr('width', together_pie_group_label_clip_width) |
|
.attr('height', 40); |
|
|
|
together_pie_group_label |
|
.style('clip-path', 'url(#' + together_pie_group_label_clip_id + ')'); |
|
|
|
var normal_label_height_offset = radius * 1.2; |
|
|
|
var original_pie_group_labels = function () { |
|
together_pie_group_label_clip_rect |
|
.transition() |
|
// No delay on removal |
|
// .delay(750) |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('y', -normal_label_height_offset + 10) |
|
|
|
pie_group_labels |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('y', -normal_label_height_offset) |
|
.attr('x', 0); |
|
} |
|
|
|
original_pie_group_labels(); |
|
|
|
var together_pie_group_labels = function () { |
|
together_pie_group_label_clip_rect |
|
.transition() |
|
// .delay(500) |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('y', -radius * 1.2 - 40) |
|
|
|
pie_group_labels |
|
.transition() |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
.attr('y', - raised_label_height_offset) |
|
.attr('x', function (d, i) { return (i ? 1 : -1) * 60; }); |
|
} |
|
|
|
// g2 = svg.append("g").attr("transform", "translate(" + 3 * width / 2 + "," + height / 2 + ")"); |
|
|
|
/* the data cannot be bound before attrTween, |
|
data binding updates the current value with a new value |
|
and once data binding does that; attrTween can only see the new value */ |
|
var arc_groups = pie_groups.selectAll(".arc") |
|
.data(function (d) { return d; }); |
|
|
|
var arc_groups = arc_groups |
|
.enter() |
|
.append("g") |
|
.attr("class", function (d, i) { |
|
return 'arc ' + (d.data.is_happen ? 'happen' : 'not-happen'); |
|
}); |
|
|
|
arc_groups |
|
.append('path') |
|
.attr('d', arc_path); |
|
|
|
var draw_pie = function (data) { |
|
//TODO: Use webAnimationRequestFrame or whatever it's called |
|
|
|
var scroll_pie_data = get_scroll_pie_data(); |
|
|
|
//Necessary to bind at least the outer layer of data |
|
//for the sake of the text function |
|
|
|
svg.selectAll('g.prob-pie') |
|
.data(scroll_pie_data); |
|
|
|
//https://bl.ocks.org/mbostock/3808218 |
|
//https://stackoverflow.com/questions/47066905/d3-merge-function |
|
svg |
|
.selectAll('g.prob-pie') |
|
.each(function (prob_pie, pie_i) { |
|
|
|
var current_pie_data = scroll_pie_data[pie_i]; |
|
|
|
d3.select(this) |
|
.selectAll('.arc') |
|
.select('path') |
|
.transition() |
|
// https://github.com/d3/d3-ease |
|
.ease(d3.easeCircleOut) |
|
.duration(750) |
|
// https://github.com/d3/d3-transition#transition_attrTween |
|
// http://javascript.tutorialhorizon.com/2015/03/05/creating-an-animated-ring-or-pie-chart-in-d3js/ |
|
.attrTween('d', tween_each_arc(current_pie_data)) |
|
|
|
d3.select(this) |
|
.select('.pie-label text') |
|
.text(pie_text_func); |
|
// .text(are_together ? pie_text_format(1) : pie_text_func); |
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
draw_pie(); |
|
</script> |
|
</body> |
|
</html> |