Created
October 15, 2020 19:41
-
-
Save valex/ea9cfbf2303928c9079b63eeafdddc1c 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
{ | |
"hand": "left", | |
"onPeriodStart": { | |
"thumb": 31.8, | |
"pointing": 15.4, | |
"middle": 25.4, | |
"ring": 10.4, | |
"pinky": 15.4 | |
}, | |
"onPeriodEnd": { | |
"thumb": 45.6, | |
"pointing": 22.4, | |
"middle": 15.3, | |
"ring": 17.4, | |
"pinky": 11.1 | |
} | |
} |
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
{ | |
"hand": "right", | |
"onPeriodStart": { | |
"thumb": 31.8, | |
"pointing": 15.4, | |
"middle": 0.4, | |
"ring": 10.4, | |
"pinky": 15.4 | |
}, | |
"onPeriodEnd": { | |
"thumb": 45.6, | |
"pointing": 22.4, | |
"middle": 0.3, | |
"ring": 12.4, | |
"pinky": 11.1 | |
} | |
} |
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> | |
<link rel="stylesheet" href="styles.css"> | |
</head> | |
<body> | |
<div style="display: flex; | |
flex-direction: row;"> | |
<div id="chart"></div> | |
<div id="chart_right"></div> | |
</div> | |
</body> | |
<script src="https://code.jquery.com/jquery-3.5.1.min.js" ></script> | |
<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
// http://bl.ocks.org/chrisrzhou/2421ac6541b68c1680f8 | |
function CHART_4_class(selector, data_file){ | |
this.options = { | |
selector: selector, | |
data_file: data_file, | |
viewBox: [400, 300], | |
groups: ['onPeriodStart', 'onPeriodEnd'], | |
levels: 3, | |
radius: 0.28, // relative radius, max: 0.5 | |
legendRadius: 0.42 // relative radius, max: 0.5 | |
} | |
this.el = null, | |
this.aspect = null, | |
this.vis = { | |
svg: null, | |
axes: null, | |
levels: null, | |
vertices: null, | |
verticesData: {}, | |
allAxis:[], | |
axisScales: {}, | |
totalAxes:null, | |
radius: null, | |
legendRadius: null, | |
hand:null, | |
defs:null, | |
handDef: null, | |
gradient:null | |
} | |
}; | |
CHART_4_class.prototype = { | |
init: function(){ | |
var that = this; | |
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); | |
d3.json(this.options.data_file) | |
.then((data) => { | |
// calculate max | |
for(var i=0; i < this.options.groups.length; i++){ | |
var group = this.options.groups[i]; | |
for (var prop in data[group]) { | |
if( data[group].hasOwnProperty( prop ) ) { | |
if(typeof data['maxValue'] === 'undefined'){ | |
data['maxValue'] = +data[group][prop]; | |
}else{ | |
if(+data[group][prop] > data['maxValue']) | |
data['maxValue'] = +data[group][prop]; | |
} | |
} | |
} | |
} | |
// add 10% to max | |
//data['maxValue'] = Math.round(1.1 * data['maxValue'] * 100)/100; | |
for(var i=0; i < this.options.groups.length; i++){ | |
var group = this.options.groups[i]; | |
var j = 0; | |
for (var prop in data[group]) { | |
if( data[group].hasOwnProperty( prop ) ) { | |
data[group][prop] = { | |
value: data[group][prop], | |
axis: prop, | |
} | |
j++; | |
} | |
} | |
} | |
for(var i=0; i < this.options.groups.length; i++){ | |
var group = this.options.groups[i]; | |
this.vis.verticesData[group] = []; | |
for (var prop in data[group]) { | |
if( data[group].hasOwnProperty( prop ) ) { | |
var worse = false; | |
if(i === 1){ | |
if(data[group][prop].value - data[this.options.groups[0]][prop].value < 0){ | |
worse = true; | |
} | |
} | |
this.vis.verticesData[group].push(Object.assign({},{ | |
worse: worse | |
}, data[group][prop])); | |
} | |
} | |
} | |
console.log(this.vis.verticesData); | |
that.buildVis(data); | |
}) | |
.catch((error) => { | |
throw error; | |
}); | |
}, | |
//build visualization using the other build helper functions | |
buildVis: function (data) { | |
this.buildVisComponents(data); | |
// this.buildCoordinates(data); | |
this.buildLevels(); | |
// if (config.showLevelsLabels) buildLevelsLabels(); | |
this.buildAxes(data); | |
this.buildAxesLabels(); | |
//this.buildLegend(data); | |
this.buildPolygons(data); | |
this.buildVertices(data); | |
}, | |
buildPolygons: function(data){ | |
var that = this; | |
var group_colors = ['#000000', '#4bc774']; | |
for(var i=0; i < this.options.groups.length; i++){ | |
var that = this; | |
var group = this.options.groups[i]; | |
var lines = []; | |
for (var j=0; j < this.vis.verticesData[group].length; j++){ | |
var vertex0 = this.vis.verticesData[group][j]; | |
var point0 = [ | |
that.vis.axisScales[vertex0.axis].x(vertex0.value), | |
that.vis.axisScales[vertex0.axis].y(vertex0.value) | |
]; | |
var vertex1 = this.vis.verticesData[group][0]; | |
if(typeof this.vis.verticesData[group][j+1] !== 'undefined'){ | |
vertex1 = this.vis.verticesData[group][j+1]; | |
} | |
var point1 = [ | |
that.vis.axisScales[vertex1.axis].x(vertex1.value), | |
that.vis.axisScales[vertex1.axis].y(vertex1.value) | |
]; | |
lines.push([point0, point1]); | |
} | |
this.vis.vertices.data(lines).enter() | |
.append("svg:line").classed("polygon-areas", true) | |
.attr("x1", function(d) { return d[0][0]; }) | |
.attr("y1", function(d) { return d[0][1]; }) | |
.attr("x2", function(d) { return d[1][0]; }) | |
.attr("y2", function(d) { return d[1][1]; }) | |
.attr("stroke", function() { | |
//return 'url(#gradient)'; | |
return group_colors[i]} | |
) | |
.attr("stroke-width", "1px"); | |
} | |
}, | |
buildVertices: function(data){ | |
var that = this; | |
var group_colors = ['#000000', '#4bc774']; | |
for(var i=0; i < this.options.groups.length; i++){ | |
var that = this; | |
var group = this.options.groups[i]; | |
if(i === 1){ | |
this.vis.verticesData[group] | |
} | |
this.vis.vertices.data(this.vis.verticesData[group]) | |
.enter() | |
.append("svg:g").classed("polygon-vertices", true) | |
.each(function(d, fingerIndex){ | |
d3.select(this).append("svg:circle").classed("polygon-vertices", true) | |
.attr("class", "outer-circle") | |
.attr("r", 6) | |
.attr("cx", function(d) { return that.vis.axisScales[d.axis].x(d.value); }) | |
.attr("cy", function(d) { return that.vis.axisScales[d.axis].y(d.value); }) | |
.attr("fill", 'white') | |
.style("stroke", function(d){ | |
if(d.worse){ | |
return 'red'; | |
} | |
return group_colors[i] | |
}) | |
.style("stroke-width", '1px') | |
d3.select(this).append("svg:circle") | |
.attr("class", "inner-circle") | |
.attr("r", 4) | |
.attr("cx", function(d) { return that.vis.axisScales[d.axis].x(d.value); }) | |
.attr("cy", function(d) { return that.vis.axisScales[d.axis].y(d.value); }) | |
.attr("fill", function(d){ | |
if(d.worse){ | |
return 'red'; | |
} | |
return group_colors[i] | |
}) | |
.style("opacity", 0.0) | |
}) | |
.on("click", function(){ | |
d3.selectAll(".polygon-vertices").classed('active', false); | |
d3.select(this).classed('active', true); | |
}) | |
} | |
}, | |
buildVisComponents: function(data){ | |
// build allAxes | |
for(var i=0; i < this.options.groups.length; i++){ | |
var group = this.options.groups[i]; | |
for (var prop in data[group]) { | |
if(this.vis.allAxis.indexOf(prop) === -1 ) | |
this.vis.allAxis.push(prop); | |
} | |
} | |
this.vis.totalAxes = this.vis.allAxis.length; | |
this.vis.radius = d3.min([this.options.radius * this.options.viewBox[0], this.options.radius * this.options.viewBox[1]]); | |
this.vis.legendRadius = d3.min([this.options.legendRadius * this.options.viewBox[0], this.options.legendRadius * this.options.viewBox[1]]); | |
this.vis.hand = data.hand.indexOf('right') === -1 ? 'left' : 'right'; | |
// 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") | |
.append("svg:g"); | |
// hand def | |
var active_fingers = [ | |
'left-thumb', | |
'left-pointing', | |
'left-middle', | |
'left-ring', | |
'left-pinky', | |
'right-thumb', | |
'right-pointing', | |
'right-middle', | |
'right-ring', | |
'right-pinky', | |
]; | |
// for left hand | |
var fingers = { | |
left: [ | |
{ | |
x1: 0, | |
y1: 20, | |
x2: 0, | |
y2: 40, | |
hand: 'left', | |
finger: 'thumb' | |
}, | |
{ | |
x1: 6, | |
y1: 10, | |
x2: 6, | |
y2: 40, | |
hand: 'left', | |
finger: 'pointing' | |
}, | |
{ | |
x1: 12, | |
y1: 0, | |
x2: 12, | |
y2: 40, | |
hand: 'left', | |
finger: 'middle' | |
}, | |
{ | |
x1: 18, | |
y1: 9, | |
x2: 18, | |
y2: 40, | |
hand: 'left', | |
finger: 'ring' | |
}, | |
{ | |
x1: 24, | |
y1: 28, | |
x2: 24, | |
y2: 40, | |
hand: 'left', | |
finger: 'pinky' | |
}, | |
], | |
right: [ | |
{ | |
x1: 24, | |
y1: 20, | |
x2: 24, | |
y2: 40, | |
hand: 'right', | |
finger: 'thumb' | |
}, | |
{ | |
x1: 18, | |
y1: 10, | |
x2: 18, | |
y2: 40, | |
hand: 'right', | |
finger: 'pointing' | |
}, | |
{ | |
x1: 12, | |
y1: 0, | |
x2: 12, | |
y2: 40, | |
hand: 'right', | |
finger: 'middle' | |
}, | |
{ | |
x1: 6, | |
y1: 9, | |
x2: 6, | |
y2: 40, | |
hand: 'right', | |
finger: 'ring' | |
}, | |
{ | |
x1: 0, | |
y1: 28, | |
x2: 0, | |
y2: 40, | |
hand: 'right', | |
finger: 'pinky' | |
}, | |
] | |
}; | |
this.vis['defs'] = this.vis['svg'].append("defs") | |
this.vis['handDef'] = this.vis['defs'].selectAll('g').data(active_fingers).enter() | |
.append('g') | |
.attr("id", function(d,i){return '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 '#4bc774'; // green | |
else | |
return "#d7e5ec" | |
}) | |
.style("stroke-linecap", "round") | |
.style("stroke-width", "4") | |
}) | |
this.vis['gradient'] = this.vis['defs'].append('linearGradient') | |
.attr('id', 'gradient'); | |
// Create the stops of the main gradient. Each stop will be assigned | |
// a class to style the stop using CSS. | |
this.vis['gradient'].append('stop') | |
.attr('class', 'stop-left') | |
.attr('offset', '0'); | |
this.vis['gradient'].append('stop') | |
.attr('class', 'stop-left') | |
.attr('offset', '0.2'); | |
this.vis['gradient'].append('stop') | |
.attr('class', 'stop-right') | |
.attr('offset', '1'); | |
// create levels | |
this.vis.levels = this.vis.svg.selectAll(".levels") | |
.append("svg:g").classed("levels", true); | |
// create axes | |
this.vis['axes'] = this.vis.svg.selectAll(".axes") | |
.append("svg:g").classed("axes", true); | |
// create vertices | |
this.vis['vertices'] = this.vis.svg.selectAll(".vertices"); | |
}, | |
buildAxes: function(data){ | |
var that = this; | |
var center = this.getCenter(); | |
this.vis['axes'] | |
.data(this.vis.allAxis).enter() | |
.append("svg:line").classed("axis-lines", true) | |
.attr("x1", center[0]) | |
.attr("y1", center[1]) | |
.attr("x2", function(d, i) { return x(i); }) | |
.attr("y2", function(d, i) { return y(i); }) | |
.attr("stroke", "#d7e5ec") | |
.attr("stroke-width", "2px"); | |
for(var i=0; i < this.vis.allAxis.length; i++ ){ | |
this.vis.axisScales[this.vis.allAxis[i]] = { | |
x:d3.scaleLinear() | |
.domain([0, data['maxValue']]) | |
.range([center[0], x(i)]), | |
y:d3.scaleLinear() | |
.domain([0, data['maxValue']]) | |
.range([center[1], y(i)]), | |
}; | |
} | |
function x(i){ | |
return center[0] + that.vis.radius * Math.sin(i * 2 * Math.PI / that.vis.totalAxes); | |
} | |
function y(i){ | |
return center[1] + that.vis.radius * -Math.cos(i * 2 * Math.PI / that.vis.totalAxes); | |
} | |
}, | |
buildAxesLabels: function(){ | |
var that = this; | |
var center = this.getCenter(); | |
var boundings = this.vis['handDef'].node().getBBox(); | |
this.vis['axes'] | |
.data(this.vis.allAxis).enter() | |
.append("g") | |
.attr("transform",function(d,i) { | |
var x = center[0] + that.vis.legendRadius * Math.sin(i * 2 * Math.PI / that.vis.totalAxes); | |
x -= boundings.width / 2; | |
var y = center[1] + that.vis.legendRadius * -Math.cos(i * 2 * Math.PI / that.vis.totalAxes) | |
y -= boundings.height / 2; | |
return "translate("+x+","+y+")"; | |
}) | |
.append("use") | |
.attr("xlink:href",function(d,i){return "#hand-"+that.vis.hand+"-"+d}) | |
}, | |
buildLevels: function(){ | |
var that = this; | |
var center = this.getCenter(); | |
for (var level = 0; level < this.options.levels; level++) { | |
var levelFactor = this.vis.radius * ((level + 1) / this.options.levels); | |
// build level-lines | |
this.vis.levels | |
.data(this.vis.allAxis).enter() | |
.append("svg:line").classed("level-lines", true) | |
.attr("x1", function(d, i) { return levelFactor * (1 - Math.sin(i * 2 * Math.PI / that.vis.totalAxes)); }) | |
.attr("y1", function(d, i) { return levelFactor * (1 - Math.cos(i * 2 * Math.PI / that.vis.totalAxes)); }) | |
.attr("x2", function(d, i) { return levelFactor * (1 - Math.sin((i + 1) * 2 * Math.PI / that.vis.totalAxes)); }) | |
.attr("y2", function(d, i) { return levelFactor * (1 - Math.cos((i + 1) * 2 * Math.PI / that.vis.totalAxes)); }) | |
.attr("transform", "translate(" + (center[0] - levelFactor) + ", " + (center[1] - levelFactor) + ")") | |
.attr("stroke", "#d7e5ec") | |
.attr("stroke-width", "1px"); | |
} | |
}, | |
getCenter: function(){ | |
return [this.options.viewBox[0] / 2, this.options.viewBox[1] / 2]; | |
}, | |
onResize: function (){ | |
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 chartBounding = chartEl.node().getBoundingClientRect(); | |
var targetWidth = chartContainerBounding.width; | |
chartEl.attr("width", targetWidth); | |
chartEl.attr("height", Math.round(targetWidth / this.aspect)); | |
}, | |
}; | |
var chart_left = new CHART_4_class('#chart', 'chart4.json'); | |
var chart_right = new CHART_4_class('#chart_right', 'chart4_right.json'); | |
chart_left.init(); | |
chart_right.init(); | |
console.log(chart_left); | |
console.log(chart_right); |
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: 49%; | |
background-color: #ffffff; | |
} | |
#chart_right { | |
width: 49%; | |
background-color: #ffffff; | |
} | |
.polygon-vertices{ | |
cursor: pointer; | |
} | |
.polygon-vertices.active .inner-circle{ | |
opacity: 1 !important; | |
} | |
.stop-left { | |
stop-color: #4bc774; /* Indigo */ | |
} | |
.stop-right { | |
stop-color:red; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment