Created
October 26, 2020 20:43
-
-
Save valex/684386b7373d7015a241001ca8b1d1be to your computer and use it in GitHub Desktop.
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
<html> | |
<head> | |
<meta charset="utf-8"> | |
<link rel="stylesheet" href="styles.css"> | |
</head> | |
<body> | |
<div style="display: flex; | |
flex-direction: row; justify-content: space-between;"> | |
<div id="chart"></div> | |
<div id="chart_right"></div> | |
</div> | |
</body> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js" ></script> | |
<script src="scripts.js"></script> | |
</html> |
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
function CHART_1_class(id, selector){ | |
this.options = { | |
selector: selector, | |
viewBox: [400, 300], | |
bands: { | |
left: ["pinky", "ring", "middle", "pointing"], | |
right: ["pointing", "middle", "ring", "pinky"] | |
}, | |
colors: { | |
black: '#1f2c37', | |
background: '#d7e5ec', | |
better: '#4bc774', | |
better_hover: '#76e582', | |
worse: '#d63a31', | |
worse_hover: '#eb4a41', | |
}, | |
groups: ['onPeriodStart', 'onPeriodEnd'], | |
yScaleDomain: [0, 160], | |
barStrokeWidth: 2, | |
paddings: { | |
main: { | |
left: 0.2, | |
bottom: 0.3 | |
} | |
}, | |
headerHeight: 0.18 | |
} | |
this.id = id, | |
this.el = null, | |
this.aspect = null, | |
this.onPeriodStartDate = null, | |
this.originalData = null, | |
this.data = null, | |
this.vis = { | |
svg: null, | |
defs: null, | |
background:null, | |
legends: null, | |
hand_legends: null, | |
yAxis: null, | |
markers:null, | |
bars: null, | |
mainRect: { // init values | |
x: 0, | |
y: 0, | |
width: this.options.viewBox[0], | |
height: this.options.viewBox[1] | |
}, | |
headerRect: { | |
x: null, | |
y: null, | |
width: null, | |
height: null, | |
center: { | |
x: null, | |
y: null | |
} | |
}, | |
xScale: null, | |
yScale: null | |
} | |
this.el = d3.select(this.options.selector); | |
this.aspect = this.options.viewBox[0] / this.options.viewBox[1]; | |
window.addEventListener("resize", this.onResize.bind(this), false); | |
}; | |
CHART_1_class.prototype = { | |
setData: function(data){ | |
this.originalData = Object.assign({}, data); | |
this.data = null; | |
this.prepareData(); | |
}, | |
setOnPeriodStartDate: function(onPeriodStartDate){ | |
this.onPeriodStartDate = onPeriodStartDate; | |
}, | |
prepareData: function() { | |
var that = this; | |
this.data = Object.assign({}, this.originalData); | |
// build diff | |
this.data['diff'] = {}; | |
for (var prop in this.data[this.options.groups[1]]) { | |
if( ! this.data[this.options.groups[1]].hasOwnProperty( prop ) ) | |
continue; | |
if( typeof this.data[this.options.groups[0]][prop] === 'undefined' ) | |
continue; | |
this.data['diff'][prop] = this.data[this.options.groups[1]][prop] - this.data[this.options.groups[0]][prop]; | |
} | |
var diff_entries = Object.entries(this.data['diff']); | |
this.data['diff_max_key'] = diff_entries[ | |
d3.maxIndex(diff_entries, function(d){ | |
return d[1]; | |
}) | |
][0]; | |
this.data['diff_min_key'] = diff_entries[ | |
d3.minIndex(diff_entries, function(d){ | |
return d[1]; | |
}) | |
][0]; | |
for(var i=0; i < this.options.groups.length; i++){ | |
var group = this.options.groups[i]; | |
// calculate min and max | |
for (var prop in this.data[group]) { | |
if( this.data[group].hasOwnProperty( prop ) ) { | |
if(typeof this.data['maxValue'] === 'undefined'){ | |
this.data['maxValue'] = +this.data[group][prop]; | |
}else{ | |
if(+this.data[group][prop] > this.data['maxValue']) | |
this.data['maxValue'] = +this.data[group][prop]; | |
} | |
if(typeof this.data['minValue'] === 'undefined'){ | |
this.data['minValue'] = +this.data[group][prop]; | |
}else{ | |
if(+this.data[group][prop] < this.data['minValue']) | |
this.data['minValue'] = +this.data[group][prop]; | |
} | |
} | |
} | |
// build entries | |
this.data[group]['entries'] = Object.entries(this.data[group]) | |
} | |
}, | |
buildVis: function(){ | |
if( ! this.vis.svg ) this.buildUnchanged(); | |
this.makeCalculations(); | |
this.buildDefs(); | |
this.buildSkeleton(); | |
this.buildLegends(); | |
this.buildAxes(); | |
this.buildMarkers(); | |
this.buildBars(); | |
}, | |
makeCalculations: function(){ | |
var headerPlusMainHeigh = Math.ceil(this.options.viewBox[1] - (this.options.paddings.main.bottom * this.options.viewBox[1])); | |
this.vis.headerRect.x = 1 + Math.round(this.options.paddings.main.left * this.options.viewBox[0]); | |
this.vis.headerRect.y = 1; | |
this.vis.headerRect.width = this.options.viewBox[0] - this.vis.headerRect.x -1; | |
this.vis.headerRect.height = Math.floor( this.options.headerHeight * headerPlusMainHeigh); | |
this.vis.headerRect.center.x = this.vis.headerRect.x + this.vis.headerRect.width / 2; | |
this.vis.headerRect.center.y = this.vis.headerRect.y + this.vis.headerRect.height / 2; | |
this.vis.mainRect.x = this.vis.headerRect.x; | |
this.vis.mainRect.y = this.vis.headerRect.y + this.vis.headerRect.height; | |
this.vis.mainRect.width = this.vis.headerRect.width; | |
this.vis.mainRect.height = Math.floor(headerPlusMainHeigh - this.vis.headerRect.height); | |
this.vis.xScale = d3.scaleBand() | |
.domain(this.options.bands[this.data['hand']]) | |
.rangeRound([this.vis.mainRect.x, this.vis.mainRect.x+this.vis.mainRect.width]) | |
.paddingInner(0.25) | |
.paddingOuter(0.5); | |
this.vis.yScale = d3.scaleLinear() | |
.domain(this.options.yScaleDomain) | |
.range([ | |
this.vis.mainRect.y + this.vis.mainRect.height, | |
this.vis.mainRect.y | |
]); | |
}, | |
buildMarkers:function(){ | |
var that = this; | |
this.vis['markers'] | |
.selectAll("g.end") | |
.data(this.options.bands[this.data['hand']]) | |
.join('g') | |
.attr('class', function(fingerKey){ return fingerKey; }) | |
.classed('end', true) | |
.style('opacity', '0') | |
.each(function(fingerKey){ | |
// text | |
var text_bounding; | |
var text = d3.select(this) | |
.selectAll("text") | |
.data([ that.data[that.options.groups[1]][fingerKey] ]) | |
.join("text") | |
.attr("x", function(){return 10}) | |
.attr("y", function(value){return that.vis.yScale( value )}) | |
.attr("dx", 10) | |
.attr("dy", 1) | |
.attr("font-size", "1rem") | |
.attr("fill", that.options.colors.black) | |
.attr("text-anchor", "start") | |
.style("alignment-baseline", "middle") | |
.style("font-weight", "normal") | |
.attr("class", "noselect") | |
.text(function(value, i) { | |
return value+"\u00B0"; | |
}) | |
.each(function(){ | |
text_bounding = d3.select(this).node().getBBox(); | |
}) | |
//rect | |
var rect_padding = 4; | |
var rect_bounding; | |
d3.select(this) | |
.selectAll("rect") | |
.data( [ text_bounding ] ) | |
.join("rect") | |
.attr('rx', 6) | |
.attr('ry', 6) | |
.attr('x', function(d){ return d.x - rect_padding }) | |
.attr('y', function(d){ return d.y -rect_padding }) | |
.attr('width', function(d){ return d.width + 2*rect_padding }) | |
.attr('height', function(d){ return d.height +2*rect_padding }) | |
.attr('fill', 'white') | |
.style('fill-opacity', "1") | |
.style('stroke-width', '1px') | |
.style('stroke', function(d, i){ | |
if( that.data.diff[fingerKey] > 0) | |
return that.options.colors.better; | |
return that.options.colors.worse; | |
}) | |
.each(function(){ | |
rect_bounding = d3.select(this).node().getBBox(); | |
}) | |
text.raise(); | |
//line | |
d3.select(this) | |
.selectAll('line') | |
.data([ that.data[that.options.groups[1]][fingerKey] ]) | |
.join("line") | |
.attr('x1', function(d, i){ | |
return that.vis.xScale(fingerKey) + that.vis.xScale.bandwidth() /2; | |
}) | |
.attr('y1', function(d, i){ | |
return that.vis.yScale( d ) | |
}) | |
.attr('x2', function(d, i){ return rect_bounding.x + rect_bounding.width }) | |
.attr('y2', function(d, i){ | |
return that.vis.yScale( d ) | |
}) | |
.style('stroke-width', '1px') | |
.style('stroke', function(d, i){ | |
if( that.data.diff[fingerKey] > 0) | |
return that.options.colors.better; | |
return that.options.colors.worse; | |
}) | |
}); | |
this.vis['markers'] | |
.selectAll("g.start") | |
.data(this.options.bands[this.data['hand']]) | |
.join('g') | |
.attr('class', function(fingerKey){ return fingerKey; }) | |
.classed('start', true) | |
.style('opacity', '0') | |
.each(function(fingerKey){ | |
// text | |
var text_bounding; | |
var text = d3.select(this) | |
.selectAll("text") | |
.data([ that.data[that.options.groups[0]][fingerKey] ]) | |
.join("text") | |
.attr("x", function(){return 10}) | |
.attr("y", function(value){return that.vis.yScale( value )}) | |
.attr("dx", 10) | |
.attr("dy", 1) | |
.attr("font-size", "1rem") | |
.attr("fill", that.options.colors.black) | |
.attr("text-anchor", "start") | |
.style("alignment-baseline", "middle") | |
.style("font-weight", "normal") | |
.attr("class", "noselect") | |
.text(function(value, i) { | |
return value+"\u00B0"; | |
}) | |
.each(function(){ | |
text_bounding = d3.select(this).node().getBBox(); | |
}) | |
//rect | |
var rect_padding = 4; | |
var rect_bounding; | |
d3.select(this) | |
.selectAll("rect") | |
.data( [ text_bounding ] ) | |
.join("rect") | |
.attr('rx', 6) | |
.attr('ry', 6) | |
.attr('x', function(d){ return d.x - rect_padding }) | |
.attr('y', function(d){ return d.y -rect_padding }) | |
.attr('width', function(d){ return d.width + 2*rect_padding }) | |
.attr('height', function(d){ return d.height +2*rect_padding }) | |
.attr('fill', 'white') | |
.style('fill-opacity', "1") | |
.style('stroke-width', '1px') | |
.style('stroke', function(d, i){ | |
return that.options.colors.black; | |
}) | |
.each(function(){ | |
rect_bounding = d3.select(this).node().getBBox(); | |
}) | |
text.raise(); | |
//line | |
d3.select(this) | |
.selectAll('line') | |
.data([ that.data[that.options.groups[0]][fingerKey] ]) | |
.join("line") | |
.attr('x1', function(d, i){ | |
return that.vis.xScale(fingerKey) + that.vis.xScale.bandwidth() /2; | |
}) | |
.attr('y1', function(d, i){ | |
return that.vis.yScale( d ) | |
}) | |
.attr('x2', function(d, i){ return rect_bounding.x + rect_bounding.width }) | |
.attr('y2', function(d, i){ | |
return that.vis.yScale( d ) | |
}) | |
.style('stroke-width', '1px') | |
.style('stroke', function(d, i){ | |
return that.options.colors.black | |
}) | |
}); | |
}, | |
buildLegends: function(){ | |
var that = this; | |
var handMarker = this.vis.legends | |
.selectAll('text') | |
.data([that.data.hand]) | |
.join("text") | |
.attr("x", function(){return that.vis.headerRect.x;}) | |
.attr("y", function(){return that.vis.headerRect.center.y;}) | |
.attr("dx", 10) | |
.attr("dy", 2) | |
.attr("font-size", "1rem") | |
.attr("fill", that.options.colors.black) | |
.attr("text-anchor", "start") | |
.style("alignment-baseline", "middle") | |
.style("font-weight", "bold") | |
.attr("class", "noselect") | |
.text(function(d) { | |
switch(d){ | |
case 'left': | |
return "Левая рука" | |
break; | |
default: | |
return "Правая рука" | |
break; | |
} | |
}); | |
var data = [1, 2]; | |
if(that.onPeriodStartDate){ | |
data = [0, 1, 2]; | |
} | |
this.vis.legends | |
.selectAll('circle') | |
.data( data ) | |
.enter() | |
.append("circle") | |
.attr("cx", function(d){ | |
switch(d){ | |
case 0: | |
return that.vis.headerRect.center.x - 40; | |
break; | |
case 1: | |
return that.vis.headerRect.center.x + 40; | |
break; | |
case 2: | |
return that.vis.headerRect.center.x + 110; | |
break; | |
} | |
}) | |
.attr("cy", that.vis.headerRect.center.y) | |
.attr("r", function(){return 0.21 * that.vis.headerRect.height / 2}) | |
.attr("fill", function(d){ | |
switch(d){ | |
case 0: | |
return 'white'; | |
break; | |
case 1: | |
return that.options.colors.better; | |
break; | |
case 2: | |
return that.options.colors.worse; | |
break; | |
} | |
}) | |
.style("stroke-width", "2px") | |
.style("stroke", function(d){ | |
switch(d){ | |
case 0: | |
return that.options.colors.black; | |
break; | |
case 1: | |
return that.options.colors.better; | |
break; | |
case 2: | |
return that.options.colors.worse; | |
break; | |
} | |
}); | |
this.vis.legends | |
.selectAll('text.legend-text') | |
.data( data ) | |
.enter() | |
.append('text') | |
.classed("legend-text", true) | |
.attr("x", function(d){ | |
switch(d){ | |
case 0: | |
return that.vis.headerRect.center.x-40; | |
break; | |
case 1: | |
return that.vis.headerRect.center.x + 40; | |
break; | |
case 2: | |
return that.vis.headerRect.center.x + 110; | |
break; | |
} | |
}) | |
.attr("y", that.vis.headerRect.center.y) | |
.attr("dx", 10) | |
.attr("dy", 2) | |
.attr("font-size", "0.8rem") | |
.attr("fill", that.options.colors.black) | |
.attr("text-anchor", "start") | |
.style("alignment-baseline", "middle") | |
.text(function(d) { | |
switch(d){ | |
case 0: | |
return that.onPeriodStartDate; | |
break; | |
case 1: | |
return "Лучше" | |
break; | |
case 2: | |
return "Хуже" | |
break; | |
} | |
}); | |
}, | |
buildSkeleton:function(){ | |
var that = this; | |
this.vis['background'].append('rect') | |
.attr('rx', 6) | |
.attr('ry', 6) | |
.attr('x', this.vis.headerRect.x) | |
.attr('y', this.vis.headerRect.y) | |
.attr('width', this.vis.headerRect.width) | |
.attr('height', this.vis.headerRect.height + this.vis.mainRect.height) | |
.attr('fill', 'white') | |
.style('stroke-width', '1px') | |
.style('stroke', this.options.colors.background) | |
this.vis['background'].append('line') | |
.attr('x1', this.vis.mainRect.x) | |
.attr('y1', this.vis.mainRect.y) | |
.attr('x2', this.vis.mainRect.x + this.vis.mainRect.width) | |
.attr('y2', this.vis.mainRect.y) | |
.style('stroke-width', '1px') | |
.style('stroke', this.options.colors.background) | |
// hand labels | |
this.vis['background'].selectAll('g.label') | |
.data(this.options.bands[this.data.hand]) | |
.join("g") | |
.classed('label', true) | |
.attr("transform",function(fingerKey,i) { | |
var bbox = that.el.select('#'+that.id+'-hand-left-middle').node().getBBox(); | |
x = that.vis.xScale(fingerKey) + that.vis.xScale.bandwidth()/2 - bbox.width/2; | |
ymin = that.vis.mainRect.y+that.vis.mainRect.height; | |
ymax = that.options.viewBox[1]; | |
y = ymin + ((ymax - ymin)/2) - bbox.height/2; | |
return "translate("+x+","+y+")"; | |
}) | |
.each(function(){ | |
d3.select(this).selectAll("*").remove(); | |
}) | |
.append("use") | |
.attr("xlink:href",function(fingerKey,i){return "#"+that.id+"-hand-"+that.data.hand+"-"+fingerKey}) | |
}, | |
buildAxes: function(){ | |
var yAxis = d3.axisLeft() | |
.ticks(5) | |
.tickSize(this.vis.mainRect.width) | |
.tickFormat(function(d){ | |
return d + "\u00B0"; | |
}) | |
.scale(this.vis.yScale); | |
this.vis.yAxis.attr("transform", "translate("+(this.vis.mainRect.x+this.vis.mainRect.width)+",0)") | |
.call(yAxis) | |
.call(function(g){ | |
g.select(".domain").remove(); | |
}) | |
.call(function(g){ | |
g.selectAll(".tick:not(:first-of-type) line") | |
.attr("stroke-opacity", 0.5) | |
.attr("stroke-dasharray", "2,2") | |
g.selectAll(".tick:first-of-type line").remove(); | |
}) | |
.call(function(g){ | |
g.selectAll(".tick text") | |
.style("text-anchor", "end") | |
.attr("dx", -6) | |
//.attr("dy", 0) | |
}) | |
}, | |
buildBars: function(){ | |
var that = this; | |
var roundRadius = 7; | |
// clip paths | |
this.vis.bars.selectAll("clipPath") | |
.data(this.data[this.options.groups[0]]['entries']) | |
.join( | |
function(enter){ | |
return enter | |
.append("clipPath") | |
.attr("id", function(d){ return that.id+"-clip-"+d[0]}) | |
.append("path") | |
.attr("transform",function(d) { | |
var x = that.vis.xScale(d[0]) + that.options.barStrokeWidth/2; | |
var y = that.vis.yScale(d[1]) + that.options.barStrokeWidth/2; | |
return "translate("+x+","+y+")"; | |
}) | |
.attr("d", function(d){ | |
var onEndValue = that.data[that.options.groups[1]][d[0]]; | |
var worse = false; | |
if ((onEndValue - d[1]) < 0){ | |
worse = true; | |
} | |
var width = that.vis.xScale.bandwidth() - that.options.barStrokeWidth; | |
var height = (that.vis.mainRect.y + that.vis.mainRect.height -1) - that.vis.yScale(d[1]); | |
this._current = d; | |
if(worse){ | |
return that.fullRoundedRect( width, height, roundRadius - that.options.barStrokeWidth/2) | |
} | |
return that.bottomRoundedRect( width, height, roundRadius) | |
}); | |
}, | |
function(update){ | |
return update | |
.call(function(update){ | |
return update | |
.select("path") | |
.transition() | |
.duration(1000) | |
.attrTween("transform",function(d) { | |
var intrpl = d3.interpolate(this._current, d); | |
var x = that.vis.xScale(d[0]) + that.options.barStrokeWidth/2; | |
return function(t){ | |
var y = that.vis.yScale(intrpl(t)[1]) + that.options.barStrokeWidth/2; | |
return "translate("+x+","+y+")"; | |
} | |
}) | |
.attrTween("d", function(d){ | |
var intrpl = d3.interpolate(this._current, d); | |
this._current = intrpl(1); | |
return function(t){ | |
var onEndValue = that.data[that.options.groups[1]][d[0]]; | |
var worse = false; | |
if ((onEndValue - intrpl(t)[1]) < 0){ | |
worse = true; | |
} | |
var width = that.vis.xScale.bandwidth() - that.options.barStrokeWidth; | |
var height = (that.vis.mainRect.y + that.vis.mainRect.height -1) - that.vis.yScale(intrpl(t)[1]); | |
if(worse){ | |
return that.fullRoundedRect( width, height, roundRadius - that.options.barStrokeWidth/2) | |
} | |
return that.bottomRoundedRect( width, height, roundRadius) | |
} | |
}) | |
}); | |
}, | |
function(exit){return exit.remove();} | |
) | |
this.vis.bars.selectAll("g") | |
.data(this.options.bands[this.data['hand']]) | |
.join('g') | |
.each(function(fingerKey){ | |
d3.select(this) | |
.selectAll("path.on-start") | |
.data([ that.data[that.options.groups[0]][fingerKey] ]) | |
.join( | |
function(enter) { | |
return enter | |
.append("path") | |
.classed("on-start", true) | |
.style("fill", "white") | |
.style('stroke', that.options.colors.black) | |
.style('stroke-width', that.options.barStrokeWidth) | |
.attr("transform",function(value,i) { | |
var x = that.vis.xScale(fingerKey); | |
var y = that.vis.yScale(value); | |
return "translate("+x+","+y+")"; | |
}) | |
.attr("d", function(value){ | |
var onEndValue = that.data[that.options.groups[1]][fingerKey]; | |
var worse = false; | |
if ((onEndValue - value) < 0){ | |
worse = true; | |
} | |
var width = that.vis.xScale.bandwidth() | |
var height = (that.vis.mainRect.y + that.vis.mainRect.height -1) - that.vis.yScale(value); | |
if(worse || onEndValue - value == 0){ | |
return that.fullRoundedRect( width, height, roundRadius) | |
} | |
return that.bottomRoundedRect( width, height, roundRadius) | |
}) | |
.attr("_current", function(d){return d}) | |
.on("mouseenter", function(event, value){ | |
that.el.select("g.markers g.start."+fingerKey) | |
.style('opacity', '1'); | |
}) | |
.on("mouseleave", function(event, value){ | |
that.el.select("g.markers g.start."+fingerKey) | |
.style('opacity', '0'); | |
}); | |
}, | |
function(update) { | |
return update | |
.call(function(update){ | |
return update | |
.transition() | |
.duration(1000) | |
.attr("transform",function(value,i) { | |
var x = that.vis.xScale(fingerKey); | |
var y = that.vis.yScale(value); | |
return "translate("+x+","+y+")"; | |
}) | |
.attrTween("d", function(value){ | |
var i = d3.interpolate(this.getAttribute("_current"), value); | |
d3.select(this) | |
.attr("_current", i(1)); | |
return function(t) { | |
var onEndValue = that.data[that.options.groups[1]][fingerKey]; | |
var worse = false; | |
if ((onEndValue - value) < 0){ | |
worse = true; | |
} | |
var width = that.vis.xScale.bandwidth() | |
var height = (that.vis.mainRect.y + that.vis.mainRect.height -1) - that.vis.yScale(i(t)); | |
if(worse || onEndValue - value == 0 ){ | |
return that.fullRoundedRect(width, height, roundRadius) | |
} | |
return that.bottomRoundedRect(width, height, roundRadius) | |
}; | |
}); | |
}) | |
}, | |
) | |
// onPeriodEnd | |
d3.select(this) | |
.selectAll("path.on-end") | |
.data([ that.data[that.options.groups[1]][fingerKey] ]) | |
.join( | |
function(enter){ | |
return enter | |
.append("path") | |
.classed("on-end", true) | |
.attr("_current", function(d){return d}) | |
.style("fill", function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse | |
} | |
return that.options.colors.better | |
}) | |
.style('stroke', function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse | |
} | |
return that.options.colors.better | |
}) | |
.style('stroke-width', '0'/*that.options.barStrokeWidth*/) | |
.attr("clip-path",function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse) { | |
return "url(#"+that.id+"-clip-"+fingerKey+")" | |
} | |
return null; | |
}) | |
.attr("d", function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if ((value - onStartValue) == 0){ | |
return null; | |
} | |
var x = that.vis.xScale(fingerKey) - that.options.barStrokeWidth/2; | |
var y = that.vis.yScale(value); | |
var width = that.vis.xScale.bandwidth() + that.options.barStrokeWidth; | |
var height = that.vis.yScale(onStartValue) - that.vis.yScale(value); | |
if(height > that.options.barStrokeWidth/2) | |
{ | |
height -= that.options.barStrokeWidth/2; | |
} | |
this._current_start = onStartValue; | |
if(worse) { | |
} | |
return that.figuredRect(x, y, width, height, roundRadius) | |
}) | |
.on("mouseenter", function(event, value){ | |
d3.select(this) | |
.style('stroke', function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse_hover; | |
} | |
return that.options.colors.better_hover; | |
}) | |
.style('fill', function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse_hover; | |
} | |
return that.options.colors.better_hover; | |
}) | |
that.el.select("g.markers g.end."+fingerKey) | |
.style('opacity', '1'); | |
}) | |
.on("mouseleave", function(event, value){ | |
d3.select(this) | |
.style('stroke', function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse; | |
} | |
return that.options.colors.better; | |
}) | |
.style('fill', function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse; | |
} | |
return that.options.colors.better; | |
}) | |
that.el.select("g.markers g.end."+fingerKey) | |
.style('opacity', '0'); | |
}) | |
}, | |
function (update){ | |
return update | |
.style("fill", function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse | |
} | |
return that.options.colors.better | |
}) | |
.style('stroke', function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse){ | |
return that.options.colors.worse | |
} | |
return that.options.colors.better | |
}) | |
.attr("clip-path",function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var worse = false; | |
if ((value - onStartValue) < 0){ | |
worse = true; | |
} | |
if(worse) { | |
return "url(#"+that.id+"-clip-"+fingerKey+")" | |
} | |
return null; | |
}) | |
.call(function(update){ | |
return update | |
.transition() | |
.duration(1000) | |
.attrTween("d", function(value){ | |
var onStartValue = that.data[that.options.groups[0]][fingerKey]; | |
var intrpl = d3.interpolate(this.getAttribute("_current"), value); | |
var intrplStartValue = d3.interpolate(this._current_start, onStartValue); | |
d3.select(this) | |
.attr("_current", intrpl(1)); | |
this._current_start = intrplStartValue(1); | |
return function(t){ | |
var x = that.vis.xScale(fingerKey) - that.options.barStrokeWidth/2; | |
var y = that.vis.yScale(intrpl(t)); | |
var width = that.vis.xScale.bandwidth() + that.options.barStrokeWidth; | |
var height = that.vis.yScale(intrplStartValue(t)) - that.vis.yScale(intrpl(t)) - that.options.barStrokeWidth/2; | |
return that.figuredRect(x, y, width, height, roundRadius) | |
} | |
}); | |
}) | |
}, | |
function(exit){return exit.remove();} | |
) | |
}) | |
.on("mouseenter", function(event, fingerKey){ | |
}) | |
.on("mouseleave", function(event, fingerKey){ | |
}) | |
}, | |
buildDefs: function(){ | |
var that = this; | |
var active_fingers = [ | |
'left-thumb', | |
'left-pointing', | |
'left-middle', | |
'left-ring', | |
'left-pinky', | |
'right-thumb', | |
'right-pointing', | |
'right-middle', | |
'right-ring', | |
'right-pinky', | |
]; | |
var fingers = { | |
left: [ | |
{ | |
x1: 0, | |
y1: 14, | |
x2: 0, | |
y2: 32, | |
hand: 'left', | |
finger: 'pinky' | |
}, | |
{ | |
x1: 6, | |
y1: 6, | |
x2: 6, | |
y2: 32, | |
hand: 'left', | |
finger: 'ring' | |
}, | |
{ | |
x1: 12, | |
y1: 0, | |
x2: 12, | |
y2: 32, | |
hand: 'left', | |
finger: 'middle' | |
}, | |
{ | |
x1: 18, | |
y1: 5, | |
x2: 18, | |
y2: 32, | |
hand: 'left', | |
finger: 'pointing' | |
}, | |
{ | |
x1: 24, | |
y1: 20, | |
x2: 24, | |
y2: 32, | |
hand: 'left', | |
finger: 'thumb' | |
}, | |
], | |
right: [ | |
{ | |
x1: 24, | |
y1: 14, | |
x2: 24, | |
y2: 32, | |
hand: 'right', | |
finger: 'pinky' | |
}, | |
{ | |
x1: 18, | |
y1: 6, | |
x2: 18, | |
y2: 32, | |
hand: 'right', | |
finger: 'ring' | |
}, | |
{ | |
x1: 12, | |
y1: 0, | |
x2: 12, | |
y2: 32, | |
hand: 'right', | |
finger: 'middle' | |
}, | |
{ | |
x1: 6, | |
y1: 5, | |
x2: 6, | |
y2: 32, | |
hand: 'right', | |
finger: 'pointing' | |
}, | |
{ | |
x1: 0, | |
y1: 20, | |
x2: 0, | |
y2: 32, | |
hand: 'right', | |
finger: 'thumb' | |
}, | |
] | |
}; | |
this.vis['defs'].selectAll('g') | |
.data(active_fingers) | |
.enter() | |
.append('g') | |
.attr("id", function(d,i){return that.id+'-hand-'+d}) | |
.each(function(active_finger,i){ | |
d3.select(this).selectAll("line").data(function(){ | |
if(active_finger.indexOf('left') !== -1) | |
return fingers.left; | |
else | |
return fingers.right; | |
}).enter() | |
.append("line") | |
.attr("x1", function(d){return d.x1}) | |
.attr("y1", function(d){return d.y1}) | |
.attr("x2", function(d){return d.x2}) | |
.attr("y2", function(d){return d.y2}) | |
.style("stroke", function(d, i){ | |
if(active_finger == d.hand+'-'+d.finger) | |
return that.options.colors.better; | |
else | |
return that.options.colors.background; | |
}) | |
.style("stroke-linecap", "round") | |
.style("stroke-width", "4") | |
}) | |
}, | |
buildUnchanged: function(){ | |
var that = this; | |
// create main vis svg | |
this.vis['svg'] = this.el | |
.append("svg") | |
.classed("svg-vis", true) | |
.attr('xmlns', 'http://www.w3.org/2000/svg') | |
.attr("viewBox", "0 0 "+this.options.viewBox[0]+" "+this.options.viewBox[1]) | |
.attr("perserveAspectRatio", "xMinYMid") | |
.on("click", function(event, d){ | |
that.deselectAllAndHide(); | |
}) | |
.append("svg:g") | |
this.vis['defs'] = this.vis['svg'] | |
.append("defs"); | |
this.vis.background = this.vis['svg'].append("svg:g") | |
.classed('background', true); | |
this.vis.legends = this.vis['svg'].append("svg:g") | |
.classed('legends', true); | |
this.vis.hand_legends = this.vis['svg'].append("svg:g") | |
.classed('hand_legends', true); | |
this.vis.yAxis = this.vis['svg'].append("svg:g") | |
.classed('y axis', true); | |
this.vis.markers = this.vis['svg'].append("svg:g") | |
.classed('markers', true); | |
this.vis.bars = this.vis['svg'].append("svg:g") | |
.classed('bars', true); | |
}, | |
figuredRect: function(x, y, width, height, radius){ | |
if(Math.abs(height) < radius) | |
radius = Math.floor(Math.abs(height) / 2); | |
var arcRadius = 6; | |
var hLength = (width - 2 * radius - 2 * arcRadius) / 2; | |
if( hLength < 0){ | |
hLength = 0; | |
arcRadius = (width - 2 * radius) / 2; | |
if(arcRadius < 0) | |
arcRadius = 0; | |
} | |
if(height < 0){ | |
return "M" + (x ) + "," + y | |
+ "h" + ((width - 2 * arcRadius) / 2) | |
+ "a" + arcRadius + "," + arcRadius + " 0 0 1 " + arcRadius + "," + arcRadius | |
+ "a" + arcRadius + "," + arcRadius + " 0 0 1 " + arcRadius + "," + -arcRadius | |
+ "h" + ((width - 2 * arcRadius) / 2) | |
+ "v" + (height) | |
+ "h" + -width | |
+ "z" | |
} | |
return "M" + (x + radius) + "," + y | |
+ "h" + (hLength) | |
+ "a" + arcRadius + "," + arcRadius + " 0 0 0 " + arcRadius + "," + -arcRadius | |
+ "a" + arcRadius + "," + arcRadius + " 0 0 0 " + arcRadius + "," + arcRadius | |
+ "h" + (hLength) | |
+ "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius | |
+ "v" + (height - radius) | |
+ "h" + (-width) | |
+ "v" + (radius - height) | |
+ "a" + radius + "," + radius + " 0 0 1 " + radius + "," + -radius; | |
}, | |
fullRoundedRect: function(width, height, radius){ | |
if(2 * radius > height){ | |
radius = height/2; | |
} | |
var path = d3.path(); | |
path.moveTo(radius, 0); | |
path.lineTo(width - radius, 0); | |
path.arc(width - radius, radius, radius, -Math.PI / 2, 0 ); | |
path.lineTo(width, height - radius); | |
path.arc( width - radius, height - radius, radius, 0, Math.PI / 2); | |
path.lineTo( radius, height); | |
path.arc( radius, height - radius, radius, Math.PI / 2, Math.PI ); | |
path.lineTo(0, radius); | |
path.arc(radius, radius, radius, Math.PI , -Math.PI / 2 ); | |
path.closePath(); | |
return path; | |
}, | |
bottomRoundedRect: function(width, height, radius){ | |
if(radius > height){ | |
radius = height; | |
} | |
var path = d3.path(); | |
path.moveTo(0, 0); | |
path.lineTo( width, 0); | |
path.lineTo( width, height - radius); | |
path.arc( width - radius, height - radius, radius, 0, Math.PI / 2); | |
path.lineTo( radius, height); | |
path.arc( radius, height - radius, radius, Math.PI / 2, Math.PI ); | |
path.closePath(); | |
return path; | |
}, | |
deselectAllAndHide: function(){ | |
}, | |
onResize: function (){ | |
this.deselectAllAndHide(); | |
// this.updateSvgWidthAndHeight(); | |
}, | |
updateSvgWidthAndHeight: function (){ | |
var chartElContainer = d3.select(this.options.selector); | |
var chartEl = d3.select(this.options.selector + " > svg"); | |
var chartContainerBounding = chartElContainer.node().getBoundingClientRect(); | |
var targetWidth = chartContainerBounding.width; | |
chartEl.attr("width", targetWidth); | |
chartEl.attr("height", Math.round(targetWidth / this.aspect)); | |
}, | |
}; | |
var id1_data = { | |
"hand": "left", | |
"onPeriodStart": { | |
"pointing": 72.13, | |
"middle": 75.3, | |
"ring": 70.5, | |
"pinky": 76.9 | |
}, | |
"onPeriodEnd": { | |
"pointing": 79.9, | |
"middle": 75.4, | |
"ring": 72.5, | |
"pinky": 65.6 | |
} | |
}; | |
var chart_left = new CHART_1_class('id1', '#chart'); | |
chart_left.setOnPeriodStartDate('20.02.20'); | |
chart_left.setData(id1_data); | |
chart_left.buildVis(); | |
var id2_data = { | |
"hand": "right", | |
"onPeriodStart": { | |
"pointing": 72.13, | |
"middle": 72.5, | |
"ring": 70.5, | |
"pinky": 76.9 | |
}, | |
"onPeriodEnd": { | |
"pointing": 79.9, | |
"middle": 72.8, | |
"ring": 80.5, | |
"pinky": 75.6 | |
} | |
}; | |
var chart_right = new CHART_1_class('id2', '#chart_right'); | |
chart_right.setData(id2_data); | |
chart_right.buildVis(); | |
var newData = { | |
"hand": "left", | |
"onPeriodStart": { | |
"pointing": 62.13, | |
"middle": 55.3, | |
"ring": 40.5, | |
"pinky": 36.9 | |
}, | |
"onPeriodEnd": { | |
"pointing": 87.9, | |
"middle": 79.2, | |
"ring": 23.5, | |
"pinky": 46.6 | |
} | |
}; | |
setTimeout(function(){ | |
chart_left.setData(newData); | |
chart_left.buildVis(); | |
}, 5000); | |
var newData2 = { | |
"hand": "left", | |
"onPeriodStart": { | |
"pointing": 87.13, | |
"middle": 83.3, | |
"ring": 79.5, | |
"pinky": 99.9 | |
}, | |
"onPeriodEnd": { | |
"pointing": 95.9, | |
"middle": 89.2, | |
"ring": 99.5, | |
"pinky": 120.6 | |
} | |
}; | |
setTimeout(function(){ | |
chart_left.setData(newData2); | |
chart_left.buildVis(); | |
}, 12000) | |
var newData3 = { | |
"hand": "left", | |
"onPeriodStart": { | |
"pointing": 95.9, | |
"middle": 89.2, | |
"ring": 99.5, | |
"pinky": 120.6 | |
}, | |
"onPeriodEnd": { | |
"pointing": 87.13, | |
"middle": 63.3, | |
"ring": 49.5, | |
"pinky": 39.9 | |
} | |
} | |
setTimeout(function(){ | |
chart_left.setData(newData3); | |
chart_left.buildVis(); | |
}, 19000) | |
var newData4 = { | |
"hand": "left", | |
"onPeriodStart": { | |
"pointing": 25.9, | |
"middle": 134.2, | |
"ring": 29.5, | |
"pinky": 32.6 | |
}, | |
"onPeriodEnd": { | |
"pointing": 97.13, | |
"middle": 45.3, | |
"ring": 109.5, | |
"pinky": 98.9 | |
} | |
} | |
setTimeout(function(){ | |
chart_left.setData(newData4); | |
chart_left.buildVis(); | |
}, 25000) |
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
#chart { | |
width: 45%; | |
background-color: #ffffff; | |
} | |
#chart_right { | |
width: 40%; | |
background-color: #ffffff; | |
} | |
.noselect { | |
-webkit-touch-callout: none; /* iOS Safari */ | |
-webkit-user-select: none; /* Safari */ | |
-khtml-user-select: none; /* Konqueror HTML */ | |
-moz-user-select: none; /* Old versions of Firefox */ | |
-ms-user-select: none; /* Internet Explorer/Edge */ | |
user-select: none; /* Non-prefixed version, currently | |
supported by Chrome, Edge, Opera and Firefox */ | |
} | |
.y.axis line{ | |
stroke: #d7e5ec; | |
} | |
.y.axis path{ | |
stroke: #d7e5ec; | |
} | |
.y.axis text{ | |
fill: #487e98; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment