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