File: /home/alloesb/www/escapeometre.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Donut Chart - Flipped Labels</title>
<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<!-- Month names -->
<link href='http://fonts.googleapis.com/css?family=Pacifico' rel='stylesheet' type='text/css'>
<style>
body {
font-family: 'Josefin Sans', sans-serif;
fill: #333333;
font-weight: 500;
font-size: 16px;
text-align: center;
letter-spacing: -0.08em;
}
#jauge-immersion {
position: relative;
top: 47px;
}
#donut {
position: absolute;
top: 0;
left: 50%;
margin-left: -150px;
transition: transform 0.5s 0s;
border: 1px solid #eee;
}
#line1, #line2 {
position: fixed;
height: 100%;
box-sizing: border-box;
border-left: 1px solid red;
}
#line1 {
left: 50%;
margin-left: -105px;
}
#line2 {
right: 50%;
margin-right: -105px;
}
svg * {
overflow: visible!important;
}
</style>
</head>
<body>
<div id="log" style="display: none; position: fixed; top: 100px; left: 30px; background: #fff; padding: 20px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.23); z-index: 99999;"></div>
<!-- <div id="line1"></div>
<div id="line2"></div> -->
<div id="jauge-immersion"><svg id="fillgauge2" height="190" onclick=""></svg></div>
<div id="donut"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
jQuery(document).ready(function($) {
// Debug : Affiche son paramètre sur le front
function log(log) {
$('#log').show()
$('#log').html(log)
}
$('#donut').click(function(){
$(this).animate({
duration: 2000,
step: function(){
$elem.css({
transform: 'rotate(90deg)'
});
}
})
});
});
</script>
<script>
////////////////////////////////////////////////////////////
//////////////////////// Set-up ////////////////////////////
////////////////////////////////////////////////////////////
var screenWidth = window.innerWidth;
var margin = {left: 20, top: 20, right: 20, bottom: 20},
width = Math.min(screenWidth, 300) - margin.left - margin.right,
height = Math.min(screenWidth, 300) - margin.top - margin.bottom;
var svg = d3.select("#donut").append("svg")
.attr("width", (width + margin.left + margin.right))
.attr("height", (height + margin.top + margin.bottom))
.append("g").attr("class", "wrapper")
.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");
//////////////////////////////////////////////////////////////
///////////////////// Data & Scales /////////////////////////
//////////////////////////////////////////////////////////////
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
value1 = getRandomInt(2, 11) * 10; // 60
value2 = getRandomInt(2, 11) * 10; // 80
value3 = getRandomInt(2, 11) * 10; // 60
value4 = getRandomInt(2, 11) * 10; // 70
//Some random data
var donutData = [
{name: "Fouille", value: value1 },
{name: "Manipulation", value: value2 },
{name: "Énigmes", value: value3 },
];
console.log('Fouille : ' + value1)
console.log('Manipulation : ' + value2)
console.log('Énigmes : ' + value3)
console.log('Immersion : ' + value4)
//Create a color scale
var colorScale = d3.scale.linear()
.domain([0,1,2])
.range(["#ed709e", "#23b2a4", "#6cc8e2"])
.interpolate(d3.interpolateHcl);
//Create an arc function
var arc = d3.svg.arc()
.innerRadius(width*0.75/2 -7)
.outerRadius(width*0.75/2 + 5);
//Turn the pie donut 90 degrees counter clockwise, so it starts at the left
var pie = d3.layout.pie()
.startAngle(-90 * Math.PI/180)
.endAngle(-90 * Math.PI/180 + 2*Math.PI)
.value(function(d) { return d.value; })
.padAngle(.01)
.sort(null);
//////////////////////////////////////////////////////////////
//////////////////// Create Donut Chart //////////////////////
//////////////////////////////////////////////////////////////
//Create the donut slices and also the invisible arcs for the text
svg.selectAll(".donutArcs")
.data(pie(donutData))
.enter().append("path")
.attr("class", "donutArcs")
.attr("d", arc)
.style("fill", function(d,i) {
if(i === 7) return "#CCCCCC"; //Other
else return colorScale(i);
})
.each(function(d,i) {
//Search pattern for everything between the start and the first capital L
var firstArcSection = /(^.+?)L/;
//Grab everything up to the first Line statement
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
//Replace all the comma's so that IE can handle it
newArc = newArc.replace(/,/g , " ");
//If the end angle lies beyond a quarter of a circle (90 degrees or pi/2)
//flip the end and start position
if (d.endAngle > 360 * Math.PI/180) {
var startLoc = /M(.*?)A/, //Everything between the first capital M and first capital A
middleLoc = /A(.*?)0 0 1/, //Everything between the first capital A and 0 0 1
endLoc = /0 0 1 (.*?)$/; //Everything between the first 0 0 1 and the end of the string (denoted by $)
//Flip the direction of the arc by switching the start en end point (and sweep flag)
//of those elements that are below the horizontal line
var newStart = endLoc.exec( newArc )[1];
var newEnd = startLoc.exec( newArc )[1];
var middleSec = middleLoc.exec( newArc )[1];
//Build up the new arc notation, set the sweep-flag to 0
newArc = "M" + newStart + "A" + middleSec + "0 0 0 " + newEnd;
}//if
//Create a new invisible arc that the text can flow along
svg.append("path")
.attr("class", "hiddenDonutArcs")
.attr("id", "donutArc"+i)
.attr("d", newArc)
.style("fill", "none");
});
//Append the label names on the outside
svg.selectAll(".donutText")
.data(pie(donutData))
.enter().append("text")
//.enter().append("value")
.attr("class", "donutText")
//Move the labels below the arcs for those slices with an end angle greater than 90 degrees
.attr("dy", function(d,i) { return (d.endAngle > 360 * Math.PI/180 ? 18 : -8); })
.append("textPath")
.attr("startOffset","50%")
.style({
"text-anchor" : "middle"
})
.attr("xlink:href",function(d,i){return "#donutArc"+i;})
.text(function(d){return d.data.name;});
</script>
<script>
var config_immersion = liquidFillGaugeDefaultSettings();
config_immersion.circleColor = "#fff"
config_immersion.textColor = "#6f9fa3"
config_immersion.waveTextColor = "#6fc3bb"
config_immersion.waveColor = "#a4f1d6"
config_immersion.waveCount = 1.8;
config_immersion.waveRiseTime = 10;
config_immersion.waveAnimateTime = 5000;
config_immersion.displayPercent = true;
config_immersion.valueCountUp = false;
config_immersion.textSize = 0.4;
var gauge2 = loadLiquidFillGauge("fillgauge2", value4, config_immersion);
/*!
* @license Open source under BSD 2-clause (http://choosealicense.com/licenses/bsd-2-clause/)
* Copyright (c) 2015, Curtis Bratton
* All rights reserved.
*
* Liquid Fill Gauge v1.1
*/
function liquidFillGaugeDefaultSettings() {
return {
minValue: 0, // The gauge minimum value.
maxValue: 100, // The gauge maximum value.
circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
circleColor: "#178BCA", // The color of the outer circle.
waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
waveCount: 1, // The number of full waves per width of the wave circle.
waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle.
waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
waveAnimate: true, // Controls if the wave scrolls or is static.
waveColor: "#178BCA", // The color of the fill wave.
waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
displayPercent: false, // If true, a % symbol is displayed after the value.
textColor: "#045681", // The color of the value text when the wave does not overlap it.
waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
};
}
function loadLiquidFillGauge(elementId, value, config) {
if (config == null) config = liquidFillGaugeDefaultSettings();
var gauge = d3.select("#" + elementId);
var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height"))) / 2;
var locationX = parseInt(gauge.style("width")) / 2 - radius;
var locationY = parseInt(gauge.style("height")) / 2 - radius;
var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value)) / config.maxValue;
var waveHeightScale;
if (config.waveHeightScaling) {
waveHeightScale = d3.scale.linear()
.range([0, config.waveHeight, 0])
.domain([0, 50, 100]);
} else {
waveHeightScale = d3.scale.linear()
.range([config.waveHeight, config.waveHeight])
.domain([0, 100]);
}
var textPixels = (config.textSize * radius / 2);
var textFinalValue = parseFloat(value).toFixed(2);
var textStartValue = config.valueCountUp ? config.minValue : textFinalValue;
var percentText = config.displayPercent ? "%" : "";
var circleThickness = config.circleThickness * radius;
var circleFillGap = config.circleFillGap * radius;
var fillCircleMargin = circleThickness + circleFillGap;
var fillCircleRadius = radius - fillCircleMargin;
var waveHeight = fillCircleRadius * waveHeightScale(fillPercent * 100);
var waveLength = fillCircleRadius * 2 / config.waveCount;
var waveClipCount = 1 + config.waveCount;
var waveClipWidth = waveLength * waveClipCount;
// Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
var textRounder = function(value) {
return Math.round(value);
};
if (parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))) {
textRounder = function(value) {
return parseFloat(value).toFixed(1);
};
}
if (parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))) {
textRounder = function(value) {
return parseFloat(value).toFixed(2);
};
}
// Data for building the clip wave area.
var data = [];
for (var i = 0; i <= 40 * waveClipCount; i++) {
data.push({
x: i / (40 * waveClipCount),
y: (i / (40))
});
}
// Scales for drawing the outer circle.
var gaugeCircleX = d3.scale.linear().range([0, 2 * Math.PI]).domain([0, 1]);
var gaugeCircleY = d3.scale.linear().range([0, radius]).domain([0, radius]);
// Scales for controlling the size of the clipping path.
var waveScaleX = d3.scale.linear().range([0, waveClipWidth]).domain([0, 1]);
var waveScaleY = d3.scale.linear().range([0, waveHeight]).domain([0, 1]);
// Scales for controlling the position of the clipping path.
var waveRiseScale = d3.scale.linear()
// The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
// such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
// circle at 100%.
.range([(fillCircleMargin + fillCircleRadius * 2 + waveHeight), (fillCircleMargin - waveHeight)])
.domain([0, 1]);
var waveAnimateScale = d3.scale.linear()
.range([0, waveClipWidth - fillCircleRadius * 2]) // Push the clip area one full wave then snap back.
.domain([0, 1]);
// Scale for controlling the position of the text within the gauge.
var textRiseScaleY = d3.scale.linear()
.range([fillCircleMargin + fillCircleRadius * 2, (fillCircleMargin + textPixels * 0.7)])
.domain([0, 1]);
// Center the gauge within the parent SVG.
var gaugeGroup = gauge.append("g")
.attr('transform', 'translate(' + locationX + ',' + locationY + ')');
// Draw the outer circle.
var gaugeCircleArc = d3.svg.arc()
.startAngle(gaugeCircleX(0))
.endAngle(gaugeCircleX(1))
.outerRadius(gaugeCircleY(radius))
.innerRadius(gaugeCircleY(radius - circleThickness));
gaugeGroup.append("path")
.attr("d", gaugeCircleArc)
.style("fill", config.circleColor)
.attr('transform', 'translate(' + radius + ',' + radius + ')');
// Text where the wave does not overlap.
var text1 = gaugeGroup.append("text")
.text("Immersion")
.attr("dy", "0em")
//.append("text")
.text("Immersion : " + textRounder(textStartValue) + percentText)
.attr("class", "liquidFillGaugeText")
//.attr("dy", "1em")
.attr("text-anchor", "middle")
.attr("font-size", textPixels + "px")
.style("fill", config.textColor)
.attr('transform', 'translate(' + radius + ',' + textRiseScaleY(config.textVertPosition) + ')');
// The clipping wave area.
var clipArea = d3.svg.area()
.x(function(d) {
return waveScaleX(d.x);
})
.y0(function(d) {
return waveScaleY(Math.sin(Math.PI * 2 * config.waveOffset * -1 + Math.PI * 2 * (1 - config.waveCount) + d.y * 2 * Math.PI));
})
.y1(function(d) {
return (fillCircleRadius * 2 + waveHeight);
});
var waveGroup = gaugeGroup.append("defs")
.append("clipPath")
.attr("id", "clipWave" + elementId);
var wave = waveGroup.append("path")
.datum(data)
.attr("d", clipArea)
.attr("T", 0);
// The inner circle with the clipping wave attached.
var fillCircleGroup = gaugeGroup.append("g")
.attr("clip-path", "url(#clipWave" + elementId + ")");
fillCircleGroup.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", fillCircleRadius)
.style("fill", config.waveColor);
// Text where the wave does overlap.
var text2 = fillCircleGroup.append("text")
.text("Immersion")
.attr("dy", "0em")
//.append("text")
.text("Immersion : " + textRounder(textStartValue) + percentText)
.attr("class", "liquidFillGaugeText")
//.attr("dy", "1em")
.attr("text-anchor", "middle")
.attr("font-size", textPixels + "px")
.style("fill", config.waveTextColor)
.attr('transform', 'translate(' + radius + ',' + textRiseScaleY(config.textVertPosition) + ')');
// Make the value count up.
if (config.valueCountUp) {
var textTween = function() {
var i = d3.interpolate(this.textContent, textFinalValue);
return function(t) {
this.textContent = textRounder(i(t)) + percentText;
}
};
text1.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
text2.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
}
// Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
var waveGroupXPosition = fillCircleMargin + fillCircleRadius * 2 - waveClipWidth;
if (config.waveRise) {
waveGroup.attr('transform', 'translate(' + waveGroupXPosition + ',' + waveRiseScale(0) + ')')
.transition()
.duration(config.waveRiseTime)
.attr('transform', 'translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')')
.each("start", function() {
wave.attr('transform', 'translate(1,0)');
}); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
} else {
waveGroup.attr('transform', 'translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')');
}
if (config.waveAnimate) animateWave();
function animateWave() {
wave.attr('transform', 'translate(' + waveAnimateScale(wave.attr('T')) + ',0)');
wave.transition()
.duration(config.waveAnimateTime * (1 - wave.attr('T')))
.ease('linear')
.attr('transform', 'translate(' + waveAnimateScale(1) + ',0)')
.attr('T', 1)
.each('end', function() {
wave.attr('T', 0);
animateWave(config.waveAnimateTime);
});
}
function GaugeUpdater() {
this.update = function(value) {
var newFinalValue = parseFloat(value).toFixed(2);
var textRounderUpdater = function(value) {
return Math.round(value);
};
if (parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))) {
textRounderUpdater = function(value) {
return parseFloat(value).toFixed(1);
};
}
if (parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))) {
textRounderUpdater = function(value) {
return parseFloat(value).toFixed(2);
};
}
var textTween = function() {
var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
return function(t) {
this.textContent = textRounderUpdater(i(t)) + percentText;
}
};
text1.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
text2.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value)) / config.maxValue;
var waveHeight = fillCircleRadius * waveHeightScale(fillPercent * 100);
var waveRiseScale = d3.scale.linear()
// The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
// such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
// circle at 100%.
.range([(fillCircleMargin + fillCircleRadius * 2 + waveHeight), (fillCircleMargin - waveHeight)])
.domain([0, 1]);
var newHeight = waveRiseScale(fillPercent);
var waveScaleX = d3.scale.linear().range([0, waveClipWidth]).domain([0, 1]);
var waveScaleY = d3.scale.linear().range([0, waveHeight]).domain([0, 1]);
var newClipArea;
if (config.waveHeightScaling) {
newClipArea = d3.svg.area()
.x(function(d) {
return waveScaleX(d.x);
})
.y0(function(d) {
return waveScaleY(Math.sin(Math.PI * 2 * config.waveOffset * -1 + Math.PI * 2 * (1 - config.waveCount) + d.y * 2 * Math.PI));
})
.y1(function(d) {
return (fillCircleRadius * 2 + waveHeight);
});
} else {
newClipArea = clipArea;
}
var newWavePosition = config.waveAnimate ? waveAnimateScale(1) : 0;
wave.transition()
.duration(0)
.transition()
.duration(config.waveAnimate ? (config.waveAnimateTime * (1 - wave.attr('T'))) : (config.waveRiseTime))
.ease('linear')
.attr('d', newClipArea)
.attr('transform', 'translate(' + newWavePosition + ',0)')
.attr('T', '1')
.each("end", function() {
if (config.waveAnimate) {
wave.attr('transform', 'translate(' + waveAnimateScale(0) + ',0)');
animateWave(config.waveAnimateTime);
}
});
waveGroup.transition()
.duration(config.waveRiseTime)
.attr('transform', 'translate(' + waveGroupXPosition + ',' + newHeight + ')')
}
}
var insertLinebreaks = function (d) {
var el = d3.select(this);
var words = d.split(' ');
el.text('');
for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(words[i]);
if (i > 0)
tspan.attr('x', 0).attr('dy', '15');
}
};
svg.selectAll('.liquidFillGaugeText').each(insertLinebreaks);
return new GaugeUpdater();
}
</script>
</body>
</html>