Created
December 4, 2019 17:34
-
-
Save dpineiden/fe3142b311dec6f07ce0ce5cd6204f59 to your computer and use it in GitHub Desktop.
Radar chart
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
var color = d3.scaleOrdinal() | |
.range(["#EDC951","#CC333F","#00A0B0"]); | |
var radarChartOptions = { | |
width: 600, | |
height: 600, | |
color: color | |
}; | |
radarChart = RadarChart() | |
d3.select('#radarChart') | |
.call(radarChart); | |
radarChart.options(radarChartOptions).update(); | |
!function() { | |
var operation = d3.select('body').append('div').append('h2'); | |
data = | |
[ | |
{ | |
"key":"Nokia Smartphone", | |
"values":[ | |
{ "axis":"Battery Life", "value":0.26 }, { "axis":"Brand", "value":0.10 }, | |
{ "axis":"Contract Cost", "value":0.30 }, { "axis":"Design And Quality", "value":0.14 }, | |
{ "axis":"Have Internet Connectivity", "value":0.22 }, { "axis":"Large Screen", "value":0.04 }, | |
{ "axis":"Price Of Device", "value":0.41 }, { "axis":"To Be A Smartphone", "value":0.30 } | |
] | |
}, | |
{ | |
"key":"Samsung", | |
"values":[ | |
{ "axis":"Battery Life", "value":0.27 }, { "axis":"Brand", "value":0.16 }, | |
{ "axis":"Contract Cost", "value":0.35 }, { "axis":"Design And Quality", "value":0.13 }, | |
{ "axis":"Have Internet Connectivity", "value":0.20 }, { "axis":"Large Screen", "value":0.13 }, | |
{ "axis":"Price Of Device", "value":0.35 }, { "axis":"To Be A Smartphone", "value":0.38 } | |
] | |
}, | |
{ | |
"key":"iPhone", | |
"values":[ | |
{ "axis":"Battery Life", "value":0.22 }, { "axis":"Brand", "value":0.28 }, | |
{ "axis":"Contract Cost", "value":0.29 }, { "axis":"Design And Quality", "value":0.17 }, | |
{ "axis":"Have Internet Connectivity", "value":0.22 }, { "axis":"Large Screen", "value":0.02 }, | |
{ "axis":"Price Of Device", "value":0.21 }, { "axis":"To Be A Smartphone", "value":0.50 } | |
] | |
} | |
]; | |
setTimeout(function() { | |
operation.text(' radarChart.data(data).duration(1000).update(); '); | |
radarChart.data(data).duration(1000).update(); | |
}, 200); | |
setTimeout(function() { | |
operation.html(" radarChart.options({'legend': {display: true}}); <br> radarChart.colors({'iPhone': 'blue', 'Samsung': 'red', 'Nokia Smartphone': 'yellow'}).update(); "); | |
radarChart.options({'legend': {display: true}}); | |
radarChart.colors({'iPhone': 'blue', 'Samsung': 'red', 'Nokia Smartphone': 'yellow'}).update(); | |
}, 4000); | |
setTimeout(function() { | |
operation.html(" radarChart.filterAxes(7); <br> radarChart.options({circles: {maxValue: 1, levels: 4}}).update(); "); | |
radarChart.filterAxes(7); | |
radarChart.options({circles: {maxValue: 1, levels: 4}}).update(); | |
}, 8000); | |
setTimeout(function() { | |
operation.text(" radarChart.maxValue(.5).levels(7).update(); "); | |
radarChart.maxValue(.5).levels(7).update(); | |
}, 12000); | |
setTimeout(function() { | |
operation.text(" radarChart.invert(4).update(); "); | |
radarChart.invert(4).update(); | |
}, 16000); | |
setTimeout(function() { | |
operation.text(" radarChart.ranges({'Contract Cost': [-1, 2]}).update(); "); | |
radarChart.ranges({'Contract Cost': [-1, 2]}).update(); | |
}, 20000); | |
setTimeout(function() { | |
operation.html(" data.forEach(function(e) { e.values.forEach(function(v) { v.value = (Math.random() * .6) + .2; }) })<br> radarChart.data(data).update(); "); | |
chart_data = JSON.parse(JSON.stringify(data)); | |
chart_data.forEach(function(e) { e.values.forEach(function(v) { v.value = (Math.random() * .6) + .2; }) }) | |
radarChart.data(chart_data).update(); | |
}, 24000); | |
setTimeout(function() { | |
operation.html(" var one = radarChart.slice(1, 2); <br> radarChart.data(one).update(); "); | |
var one = radarChart.slice(1, 2); | |
radarChart.data(one).update(); | |
}, 28000); | |
setTimeout(function() { | |
operation.html(" radarChart.ranges({'Contract Cost': []}).invert(4); <br> radarChart.data(data).update(); "); | |
radarChart.ranges({'Contract Cost': []}).invert(4); | |
radarChart.data(data).update(); | |
}, 32000); | |
setTimeout(function() { | |
operation.html(" radarChart.options({circles: {fill: 'violet'}}); <br> radarChart.options({axes: {lineColor: 'lightyellow'}}); <br> radarChart.options({circles: {color: '#FF99CC'}}); <br> radarChart.colors({'iPhone': 'black', 'Samsung': 'green', 'Nokia Smartphone': 'purple'}); "); | |
radarChart.options({circles: {fill: 'violet', color: '#FF99CC'}}); | |
radarChart.options({axes: {lineColor: "lightyellow"}}); | |
radarChart.colors({'iPhone': 'black', 'Samsung': 'green', 'Nokia Smartphone': 'purple'}); | |
radarChart.update(); | |
}, 36000); | |
setTimeout(function() { | |
operation.text(" radarChart.options({circles: {maxValue: 1, levels: 3}, legend: {symbol: 'circle'}, filter: false}).update(); "); | |
radarChart.options({circles: {maxValue: 1, levels: 3}, legendSymbol: 'circle', filter: false}).update(); | |
}, 40000); | |
setTimeout(function() { | |
operation.text(" radarChart.height(300).width(300).options({'areas': {'dotRadius': 2}}).update(); "); | |
radarChart.height(300).width(300).options({'areas': {'dotRadius': 2}}).update(); | |
}, 44000); | |
setTimeout(function() { | |
operation.text(" radarChart.height(500).width(500).options({'areas': {'dotRadius': 4}}).update(); "); | |
radarChart.height(600).width(600).options({'areas': {'dotRadius': 4}}).update(); | |
}, 48000); | |
setTimeout(function() { | |
operation.html(" radarChart.options({circles: {fill: '#CDCDCD', color: '#CDCDCD'}}); <br> radarChart.options({filter: 'rc_glow'}); <br> radarChart.colors({}).data(data).update(); <br> radarChart.maxValue(.5).levels(7).filterAxes(7);"); | |
radarChart.options({circles: {fill: '#CDCDCD', color: '#CDCDCD'}}); | |
radarChart.options({filter: 'rc_glow'}); | |
radarChart.maxValue(.5).levels(7).filterAxes(7); | |
radarChart.colors({}).data(data).update(); | |
}, 52000); | |
}(); |
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> | |
<!doctype html> | |
<!--[if lt IE 7]> | |
<html class="no-js lt-ie9 lt-ie8 lt-ie7" lang=""> <![endif]--> | |
<!--[if IE 7]> | |
<html class="no-js lt-ie9 lt-ie8" lang=""> <![endif]--> | |
<!--[if IE 8]> | |
<html class="no-js lt-ie9" lang=""> <![endif]--> | |
<!--[if gt IE 8]><!--> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/ > | |
<title>Smoothed D3.js Radar Chart</title> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
<title>{% block head_title %}{% endblock %}</title> | |
<meta name="description" content=""> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<!-- Google fonts --> | |
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,300" rel='stylesheet' type='text/css'> | |
<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'> | |
<style type="text/css"> | |
</style> | |
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>--> | |
<link rel="stylesheet" type="text/css" href="./dop.css"> | |
</head> | |
<body> | |
<h1>Radar Chart</h1> | |
<article> | |
<section id="radarChart"> | |
</section> | |
<section id="stations_status" > | |
<span id="barGradient"></span> | |
</section> | |
</article> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<script src="radarChart_up_v5.js"></script> | |
<script src="dop.js"></script> | |
</body> | |
</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 RadarChart() { | |
var uuid = UUID.generate(); | |
// TODO: | |
// wrapWidth should probably be calculated rather than an option | |
// slider to change maxValue on the fly | |
// filter update make sure there is an element with URL | |
// | |
// show axis tics/legend when hover over axis label or data point | |
// add abstract axis legend hover area... 'eyebrows' or 'dots' | |
// | |
// popup div/panel for selecting axes to display and ranges and whether to invert | |
// options which should be accessible via ACCESSORS | |
var data = []; | |
var options = { | |
filter: 'rcGlow' + uuid, // define your own filter; false = no filter; | |
filter_id: 'rcGlow' + uuid, // assign unique name for default filter | |
resize: false, | |
width: window.innerWidth, | |
widthMax: window.innerWidth, | |
height: window.innerHeight, | |
heightMax: window.innerHeight, | |
minRadius: 80, | |
// Margins for the SVG | |
margins: { | |
top: 100, | |
right: 100, | |
bottom: 100, | |
left: 100 | |
}, | |
circles: { | |
levels: 8, | |
maxValue: 0, | |
labelFactor: 1.25, | |
opacity: 0.1, | |
fill: "#CDCDCD", | |
color: "#CDCDCD" | |
}, | |
areas: { | |
colors: {}, // color lookup by key | |
opacity: 0.35, | |
borderWidth: 2, | |
rounded: true, | |
dotRadius: 4, | |
sort: true, // sort layers by approximation of size, smallest on top | |
filter: [] | |
}, | |
axes: { | |
display: true, | |
threshold: 90, // radius threshold for hiding | |
lineColor: "white", | |
lineWidth: "2px", | |
fontWidth: "11px", | |
fontColor: "black", | |
wrapWidth: 60, // The number of pixels after which a label needs to be given a new line | |
filter: [], | |
invert: [], | |
ranges: {} // { axisname: [min, max], axisname: [min, max] } | |
}, | |
legend: { | |
display: false, | |
symbol: 'cross', // 'circle', 'cross', 'diamond', 'triangle-up', 'triangle-down' | |
toggle: 'circle', | |
position: { x: 25, y: 25 } | |
}, | |
class: "rc", | |
color: d3.scaleOrdinal(d3.schemeCategory10) | |
} | |
// nodes layered such that radarInvisibleCircles always on top of radarAreas | |
// and tooltip layer is at topmost layer | |
var chart_node; // parent node for this instance of radarChart | |
var hover_node; // parent node for invisibleRadarCircles | |
var tooltip_node; // parent node for tooltip, to keep on top | |
var legend_node; // parent node for tooltip, to keep on top | |
// DEFINABLE EVENTS | |
// Define with ACCESSOR function chart.events() | |
var events = { | |
'update': { 'begin': null, 'end': null }, | |
'gridCircle': { 'mouseover': null, 'mouseout': null, 'mouseclick': null }, | |
'axisLabel': { 'mouseover': null, 'mouseout': null, 'mouseclick': null }, | |
'line': { 'mouseover': null, 'mouseout': null, 'mouseclick': null }, | |
'legend': { 'mouseover': legendMouseover, 'mouseout': areaMouseout, 'mouseclick': legendClick }, | |
'axisLegend': { 'mouseover': null, 'mouseout': null, 'mouseclick': null }, | |
'radarArea': { 'mouseover': areaMouseover, 'mouseout': areaMouseout, 'mouseclick': null }, | |
'radarInvisibleCircle': { 'mouseover': tooltip_show, 'mouseout': tooltip_hide, 'mouseclick': null } | |
}; | |
// functions which should be accessible via ACCESSORS | |
var update; | |
// helper functions | |
var tooltip; | |
// programmatic | |
var _data = []; | |
var legend_toggles = []; | |
var radial_calcs = {}; | |
var Format = d3.format('%'); // Percentage formatting | |
var transition_time = 0; | |
var delay = 0; | |
var keys; | |
var keyScale; | |
var colorScale; | |
var dom_parent; | |
function chart(selection) { | |
selection.each(function () { | |
dataCalcs(); | |
radialCalcs(); | |
dom_parent = d3.select(this); | |
scaleChart(); | |
//////////// Create the container SVG and children g ///////////// | |
var svg = dom_parent.append('svg') | |
.attr('width', options.width) | |
.attr('height', options.height); | |
// append parent g for chart | |
chart_node = svg.append('g').attr('class', options.class + 'RadarNode'); | |
hover_node = svg.append('g').attr('class', options.class + 'HoverNode'); | |
tooltip_node = svg.append('g').attr('class', options.class + 'TooltipNode'); | |
legend_node = svg.append("g").attr("class", options.class + "Legend"); | |
// Wrapper for the grid & axes | |
var axisGrid = chart_node.append("g").attr("class", options.class + "AxisWrapper"); | |
////////// Glow filter for some extra pizzazz /////////// | |
var filter = chart_node.append('defs').append('filter').attr('id', options.filter_id), | |
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation','2.5').attr('result','coloredBlur'), | |
feMerge = filter.append('feMerge'), | |
feMergeNode_1 = feMerge.append('feMergeNode').attr('in','coloredBlur'), | |
feMergeNode_2 = feMerge.append('feMergeNode').attr('in','SourceGraphic'); | |
// Set up the small tooltip for when you hover over a circle | |
tooltip = tooltip_node.append("text") | |
.attr("class", options.class + 'Tooltip') | |
.style("opacity", 0); | |
// update | |
update = function() { | |
var duration = transition_time; | |
dataCalcs(); | |
radialCalcs(); | |
keys = _data.map(function(m) { return m.key; }); | |
keyScale = d3.scaleOrdinal() | |
.domain(_data.map(function(m) { return m._i; })) | |
.range(_data.map(function(m) { return m.key; })); | |
colorScale = d3.scaleOrdinal() | |
.domain(_data.map(function(m) { | |
return options.areas.colors[keyScale(m._i)] ? | |
keyScale(m._i) | |
: m._i.toString(); | |
})) | |
.range(_data.map(function(m) { return setColor(m); })); | |
svg.transition().delay(delay).duration(duration) | |
.attr('width', options.width) | |
.attr('height', options.height) | |
chart_node.transition().delay(delay).duration(duration) | |
.attr('width', options.width) | |
.attr('height', options.height) | |
.attr("transform", | |
"translate(" + ((options.width - (options.margins.left + options.margins.right)) / 2 + options.margins.left) + "," | |
+ ((options.height - (options.margins.top + options.margins.bottom)) / 2 + options.margins.top) + ")") | |
hover_node.transition().delay(delay).duration(duration) | |
.attr('width', options.width) | |
.attr('height', options.height) | |
.attr("transform", | |
"translate(" + ((options.width - (options.margins.left + options.margins.right)) / 2 + options.margins.left) + "," | |
+ ((options.height - (options.margins.top + options.margins.bottom)) / 2 + options.margins.top) + ")") | |
tooltip_node.transition().delay(delay).duration(duration) | |
.attr('width', options.width) | |
.attr('height', options.height) | |
.attr("transform", | |
"translate(" + ((options.width - (options.margins.left + options.margins.right)) / 2 + options.margins.left) + "," | |
+ ((options.height - (options.margins.top + options.margins.bottom)) / 2 + options.margins.top) + ")") | |
legend_node | |
.attr("transform", "translate(" + options.legend.position.x + "," + options.legend.position.y + ")"); | |
var update_gridCircles = axisGrid.selectAll("." + options.class + "GridCircle") | |
.data(d3.range(1, (options.circles.levels + 1)).reverse()) | |
update_gridCircles | |
.transition().duration(duration) | |
.attr("r", function(d, i) { return radial_calcs.radius / options.circles.levels * d; }) | |
.style("fill", options.circles.fill) | |
.style("fill-opacity", options.circles.opacity) | |
.style("stroke", options.circles.color) | |
.style("filter" , function() { if (options.filter) return "url(#" + options.filter + ")" }); | |
update_gridCircles.enter() | |
.append("circle") | |
.attr("class", options.class + "GridCircle") | |
.attr("r", function(d, i) { return radial_calcs.radius / options.circles.levels * d; }) | |
.on('mouseover', function(d, i) { if (events.gridCircle.mouseover) events.gridCircle.mouseover(d, i); }) | |
.on('mouseout', function(d, i) { if (events.gridCircle.mouseout) events.gridCircle.mouseout(d, i); }) | |
.style("fill", options.circles.fill) | |
.style("fill-opacity", options.circles.opacity) | |
.style("stroke", options.circles.color) | |
.style("filter" , function() { if (options.filter) return "url(#" + options.filter + ")" }); | |
update_gridCircles.exit() | |
.transition().duration(duration * .5) | |
.delay(function(d, i) { return 0; }) | |
.remove(); | |
var update_axisLabels = axisGrid.selectAll("." + options.class + "AxisLabel") | |
.data(d3.range(1, (options.circles.levels + 1)).reverse()) | |
update_axisLabels | |
.transition().duration(duration / 2) | |
.style('opacity', 1) // don't change to 0 if there has been no change in dimensions! possible?? | |
.transition().duration(duration / 2) | |
.text(function(d, i) { if (radial_calcs.maxValue) return Format(radial_calcs.maxValue * d / options.circles.levels); }) | |
.attr("y", function(d) { return -d * radial_calcs.radius / options.circles.levels; }) | |
.style('opacity', 1); | |
update_axisLabels.enter() | |
.append("text") | |
.attr("class", options.class + "AxisLabel") | |
.attr("x", 4) | |
.attr("y", function(d) { return -d * radial_calcs.radius / options.circles.levels; }) | |
.attr("dy", "0.4em") | |
.style("font-size", "10px") | |
.attr("fill", "#737373") | |
.on('mouseover', function(d, i) { if (events.axisLabel.mouseover) events.axisLabel.mouseover(d, i); }) | |
.on('mouseout', function(d, i) { if (events.axisLabel.mouseout) events.axisLabel.mouseout(d, i); }) | |
.text(function(d, i) { if (radial_calcs.maxValue) return Format(radial_calcs.maxValue * d / options.circles.levels); }); | |
update_axisLabels.exit() | |
.transition().duration(duration * .5) | |
.remove(); | |
var update_axes = axisGrid.selectAll("." + options.class + "Axis") | |
.data(radial_calcs.axes, get_axis) | |
update_axes | |
.enter().append("g") | |
.attr("class", options.class + "Axis") | |
.attr("key", function(d) { return d.axis; }); | |
update_axes.exit() | |
.transition().duration(duration) | |
.style('opacity', 0) | |
.remove() | |
var update_lines = update_axes.selectAll("." + options.class + "Line") | |
.data(function(d) { return [d]; }, get_axis) | |
update_lines.enter() | |
.append("line") | |
.attr("class", options.class + "Line") | |
.attr("x1", 0) | |
.attr("y1", 0) | |
.attr("x2", function(d, i, j) { | |
let cxv=calcX(null, 1.1, i); | |
console.log("Resultado X",cxv); | |
return cxv; }) | |
.attr("y2", function(d, i, j) { | |
let cyv=calcY(null, 1.1, i); | |
console.log("Resultado y",cyv); | |
return cyv; }) | |
.on('mouseover', function(d, i, j) { | |
if (events.line.mouseover) events.line.mouseover(d, i); }) | |
.on('mouseout', function(d, i, j) { | |
if (events.line.mouseout) events.line.mouseout(d, i); }) | |
.style("stroke", options.axes.lineColor) | |
.style("stroke-width", "2px"); | |
update_lines.exit() | |
.transition().duration(duration * .5) | |
.delay(function(d, i) { return 0; }) | |
.remove(); | |
update_lines | |
.transition().duration(duration) | |
.style("stroke", options.axes.lineColor) | |
.style("stroke-width", options.axes.lineWidth) | |
.attr("x2", function(d, i, j) { return calcX(null, 1.1, i); }) | |
.attr("y2", function(d, i, j) { return calcY(null, 1.1, i); }) | |
var update_axis_legends = update_axes.selectAll("." + options.class + "AxisLegend") | |
.data(function(d) { return [d]; }, get_axis) | |
update_axis_legends.enter() | |
.append("text") | |
.attr("class", options.class + "AxisLegend") | |
.style("font-size", options.axes.fontWidth) | |
.attr("text-anchor", "middle") | |
.attr("dy", "0.35em") | |
.attr("x", function(d, i, j) { | |
return calcX(null, options.circles.labelFactor, i); }) | |
.attr("y", function(d, i, j) { | |
return calcY(null, options.circles.labelFactor, i); }) | |
.style('opacity', function(d, i) { return options.axes.display ? 1 : 0}) | |
.on('mouseover', function(d, i, j) { if (events.axisLegend.mouseover) events.axisLegend.mouseover(d, i, j); }) | |
.on('mouseout', function(d, i, j) { if (events.axisLegend.mouseout) events.axisLegend.mouseout(d, i, j); }) | |
.call(wrap, options.axes.wrapWidth) | |
update_axis_legends.exit() | |
.transition().duration(duration * .5) | |
.delay(function(d, i) { return 0; }) | |
.remove(); | |
update_axis_legends | |
.transition().duration(duration) | |
.style('opacity', function(d, i) { | |
return options.axes.display && radial_calcs.radius > options.axes.threshold ? 1 : 0 | |
}) | |
.attr("x", function(d, i, j) { return calcX(null, options.circles.labelFactor, i); }) | |
.attr("y", function(d, i, j) { return calcY(null, options.circles.labelFactor, i); }) | |
.selectAll('tspan') | |
.attr("x", function(d, i, j) { | |
console.log("Label factor", options.circles.labelFactor); | |
let cxv=calcX(null, options.circles.labelFactor, i); | |
console.log("Resultado X, tspan",cxv); | |
return cxv }) | |
.attr("y", function(d, i, j) { | |
let cyv=calcX(null, options.circles.labelFactor, i); | |
console.log("Resultado Y, tspan",cyv); | |
return cyv;}) | |
var radarLine = d3.radialLine() | |
.curve(options.areas.rounded ? d3.curveCardinalClosed:d3.curveLinearClosed ) | |
.radius(function(d) { return radial_calcs.rScale(d.value); }) | |
.angle(function(d,i) { return i * radial_calcs.angleSlice; }); | |
var update_blobWrapper = chart_node.selectAll("." + options.class + "RadarWrapper") | |
.data(_data, get_key); | |
update_blobWrapper.enter() | |
.append("g") | |
.attr("class", options.class + "RadarWrapper") | |
.attr("key", function(d) { return d.key; }); | |
update_blobWrapper.exit() | |
.transition().duration(duration) | |
.style('opacity', 0) | |
.remove(); | |
update_blobWrapper | |
.style("fill-opacity", function(d, i) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : options.areas.opacity; | |
}); | |
var update_radarArea = update_blobWrapper.selectAll('.' + options.class + 'RadarArea') | |
.data(function(d) { return [d]; }, get_key); | |
update_radarArea.enter() | |
.append("path") | |
.attr("class", | |
function(d) { return options.class + "RadarArea " + d.key.replace(/\s+/g, '') }) | |
.attr("d", | |
function(d, i) { return radarLine(d.values); }) | |
.style("fill", | |
function(d, i, j) { return setColor(d); }) | |
.style("fill-opacity", 0) | |
.on('mouseover', | |
function(d, i) { if (events.radarArea.mouseover) events.radarArea.mouseover(d, i, this); }) | |
.on('mouseout', | |
function(d, i) { if (events.radarArea.mouseout) events.radarArea.mouseout(d, i, this); }); | |
update_radarArea.exit().remove(); | |
update_radarArea | |
.transition().duration(duration) | |
.style("fill", function(d, i, j) { return setColor(d); }) | |
.attr("d", function(d, i) { return radarLine(d.values); }) | |
.style("fill-opacity", function(d, i) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : options.areas.opacity; | |
}) | |
var update_radarStroke = update_blobWrapper.selectAll('.' + options.class + 'RadarStroke') | |
.data(function(d) { return [d]; }, get_key); | |
update_radarStroke.enter() | |
.append("path") | |
.attr("class", options.class + "RadarStroke") | |
.attr("d", function(d, i) { return radarLine(d.values); }) | |
.style("opacity", 0) | |
.style("stroke-width", options.areas.borderWidth + "px") | |
.style("stroke", function(d, i, j) { return setColor(d); }) | |
.style("fill", "none") | |
.style("filter" , function() { if (options.filter) return "url(#" + options.filter + ")" }); | |
update_radarStroke.exit().remove(); | |
update_radarStroke | |
.transition().duration(duration) | |
.style("stroke", function(d, i, j) { return setColor(d); }) | |
.attr("d", function(d, i) { return radarLine(d.values); }) | |
.style("filter" , function() { if (options.filter) return "url(#" + options.filter + ")" }) | |
.style("opacity", function(d, i) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : 1; | |
}); | |
update_radarCircle = update_blobWrapper.selectAll('.' + options.class + 'RadarCircle') | |
.data(function(d, i) { return add_index(d._i, d.key, d.values) }); | |
update_radarCircle.enter() | |
.append("circle") | |
.attr("class", options.class + "RadarCircle") | |
.attr("r", options.areas.dotRadius) | |
.attr("cx", function(d, i, j){ return calcX(0, 0, i); }) | |
.attr("cy", function(d, i, j){ return calcY(0, 0, i); }) | |
.style("fill", function(d, i, j) { | |
return setColor(d, i, _data[d._i].key);} ) | |
.style("fill-opacity", function(d, i) { return 0; }) | |
.transition().duration(duration) | |
.attr("cx", function(d, i, j){ return calcX(d.value, 0, i); }) | |
.attr("cy", function(d, i, j){ return calcY(d.value, 0, i); }); | |
update_radarCircle.exit().remove(); | |
update_radarCircle | |
.transition().duration(duration) | |
.style("fill", function(d, i, j) { return setColor(d, d._i, data[d._i].key); }) | |
.style("fill-opacity", function(d, i, j) { | |
var key = _data.map(function(m) {return m.key})[i]; | |
return options.areas.filter.indexOf(key) >= 0 ? 0 : 0.8; | |
}) | |
.attr("r", options.areas.dotRadius) | |
.attr("cx", function(d, i){ return calcX(d.value, 0, i); }) | |
.attr("cy", function(d, i){ return calcY(d.value, 0, i); }) | |
var update_blobCircleWrapper = hover_node.selectAll("." + options.class + "RadarCircleWrapper") | |
.data(_data, get_key) | |
update_blobCircleWrapper.enter() | |
.append("g") | |
.attr("class", options.class + "RadarCircleWrapper") | |
.attr("key", function(d) { return d.key; }); | |
update_blobCircleWrapper.exit() | |
.transition().duration(duration) | |
.style('opacity', 0) | |
.remove() | |
update_radarInvisibleCircle = update_blobCircleWrapper.selectAll("." + options.class + "RadarInvisibleCircle") | |
.data(function(d, i) { return add_index(d._i, d.key, d.values); }); | |
update_radarInvisibleCircle.enter() | |
.append("circle") | |
.attr("class", options.class + "RadarInvisibleCircle") | |
.attr("r", options.areas.dotRadius * 1.5) | |
.attr("cx", function(d, i){ return calcX(d.value, 0, i); }) | |
.attr("cy", function(d, i){ return calcY(d.value, 0, i); }) | |
.style("fill", "none") | |
.style("pointer-events", "all") | |
.on('mouseover', function(d, i) { | |
if (events.radarInvisibleCircle.mouseover) events.radarInvisibleCircle.mouseover(d, i, this); | |
}) | |
.on("mouseout", function(d, i) { | |
if (events.radarInvisibleCircle.mouseout) events.radarInvisibleCircle.mouseout(d, i, this); | |
}) | |
update_radarInvisibleCircle.exit().remove(); | |
update_radarInvisibleCircle | |
.attr("cx", function(d, i){ return calcX(d.value, 0, i); }) | |
.attr("cy", function(d, i){ return calcY(d.value, 0, i); }) | |
if (options.legend.display) { | |
var shape = d3.symbol().type(options.legend.symbol).size(150); | |
var colorScale = d3.scaleOrdinal() | |
.domain(_data.map(function(m) { return m._i; })) | |
.range(_data.map(function(m) { return setColor(m); })); | |
if (d3.legend) { | |
var legendOrdinal = d3.legend.color() | |
.shape("path", shape) | |
.shapePadding(10) | |
.scale(colorScale) | |
.labels(colorScale.domain().map(function(m) { return keyScale(m); } )) | |
.on("cellclick", function(d, i) { | |
if (events.legend.mouseclick) events.legend.mouseclick(d, i, this); | |
}) | |
.on("cellover", function(d, i) { | |
if (events.legend.mouseover) events.legend.mouseover(d, i, this); | |
}) | |
.on("cellout", function(d, i) { | |
if (events.legend.mouseout) events.legend.mouseout(d, i, this); | |
}); | |
legend_node | |
.call(legendOrdinal); | |
legend_node.selectAll('.cell') | |
.attr('gen', function(d, i) { | |
if (legend_toggles[d] == true) { | |
var shape = d3.svg.symbol().type(options.legend.toggle).size(150)() | |
} else { | |
var shape = d3.svg.symbol().type(options.legend.symbol).size(150)() | |
} | |
d3.select(this).select('path').attr('d', function() { return shape; }); | |
return legend_toggles[d]; | |
}); | |
} | |
} | |
} | |
}); | |
} | |
// REUSABLE FUNCTIONS | |
// ------------------ | |
// calculate average for sorting, add unique indices for color | |
// accounts for data updates and assigns unique colors when possible | |
function dataCalcs() { | |
// this deep copy method has limitations which should not be encountered | |
// in this context | |
_data = JSON.parse(JSON.stringify(data)); | |
var axes = getAxisLabels(_data); | |
var ranges = {}; | |
// determine min/max range for each axis | |
_data.forEach( function(e) { e.values.forEach (function(d, i) { | |
var range = ranges[axes[i]] ? // already started? | |
ranges[axes[i]] | |
: options.axes.ranges[axes[i]] ? // rande defined in options? | |
options.axes.ranges[axes[i]].slice() | |
: [0, 1]; // default | |
var max = d.value > range[1] ? d.value : range[1]; | |
var min = d.value < range[0] ? d.value : range[0]; | |
ranges[axes[i]] = [min, max]; // update | |
}) }); | |
// convert all axes to range [0,1] (procrustean) | |
_data.forEach( function(e) { e.values.forEach (function(d, i) { | |
if (ranges[axes[i]][0] != 0 && ranges[axes[i]][1] != 1) { | |
var range = ranges[axes[i]]; | |
d.original_value = Number(d.value); | |
d.value = (d.value - range[0]) / (range[1] - range[0]); | |
} | |
if (options.axes.invert.indexOf(axes[i]) >= 0) { d.value = 1 - d.value; } | |
}) }) | |
_data.forEach( function(d) { d['_avg'] = d3.mean(d.values, function(e){ return e.value }); }) | |
_data = options.areas.sort ? | |
_data.sort( function(a, b) { | |
var a = a['_avg']; | |
var b = b['_avg']; | |
return b - a; | |
}) | |
: _data; | |
// filter out axes | |
var d_indices = axes.map(function(m, i) { return (options.axes.filter.indexOf(axes[i]) >= 0) ? i : undefined; }).reverse(); | |
_data.forEach( function(e) { | |
d_indices.forEach(function(i) { if (i >= 0) e.values.splice(i, 1); }); | |
}); | |
var color_indices = (function(a,b){while(a--)b[a]=a;return b})(10,[]); | |
var indices = _data.map(function (i) { return i._i }); | |
var unassigned = color_indices.filter(function(x) { return indices.indexOf(x) < 0; }).reverse(); | |
_data = _data.map(function(d, i) { | |
if (d['_i'] >= 0) { | |
return d; | |
} else { | |
d['_i'] = unassigned.length ? unassigned.pop() : i; | |
return d; | |
} | |
}); | |
} | |
function getAxisLabels(dataArray) { | |
return dataArray.length ? | |
dataArray[0].values.map(function(i, j) { return i.axis;}) | |
: []; | |
} | |
function radialCalcs() { | |
var axes = _data.length ? | |
_data[0].values.map(function(i, j) { return i;}) | |
: []; | |
console.log("Axes", axes); | |
var axisLabels = getAxisLabels(_data); | |
radial_calcs = { | |
// Radius of the outermost circle | |
radius: Math.min((options.width - (options.margins.left + options.margins.right)) / 2, | |
(options.height - (options.margins.bottom + options.margins.top)) / 2), | |
axes: axes, | |
axisLabels: axisLabels, | |
// If the supplied maxValue is smaller than the actual one, replace by the max in the data | |
maxValue: Math.max(options.circles.maxValue, d3.max(_data, function(i) { | |
return d3.max(i.values.map( function(o) { return o.value; })); | |
})) | |
} | |
radial_calcs.radius = Math.max(radial_calcs.radius, options.minRadius); | |
radial_calcs.total = radial_calcs.axes.length; | |
// The width in radians of each "slice" | |
radial_calcs.angleSlice = radial_calcs.total > 0 ? | |
Math.PI * 2 / radial_calcs.total | |
: 1; | |
//Scale for the radius | |
radial_calcs.rScale = d3.scaleLinear() | |
.range([0, radial_calcs.radius]) | |
.domain([0, radial_calcs.maxValue]); | |
} | |
function modifyList(list, values, valid_list) { | |
if ( values.constructor === Array ) { | |
values.forEach(function(e) { checkType(e); }); | |
} else if (typeof values != "object") { | |
checkType(values); | |
} else { | |
return chart; | |
} | |
function checkType(v) { | |
if (!isNaN(v) && (function(x) { return (x | 0) === x; })(parseFloat(v))) { | |
checkValue(parseInt(v)); | |
} else if (typeof v == "string") { | |
checkValue(v); | |
} | |
} | |
function checkValue(val) { | |
if ( valid_list.indexOf(val) >= 0 ) { | |
modify(val); | |
} else if ( val >= 0 && val < valid_list.length ) { | |
modify(valid_list[val]); | |
} | |
} | |
function modify(index) { | |
if (list.indexOf(index) >= 0) { | |
remove(list, index); | |
} else { | |
list.push(index); | |
} | |
} | |
function remove(arr, item) { | |
for (var i = arr.length; i--;) { if (arr[i] === item) { arr.splice(i, 1); } } | |
} | |
} | |
function calcX(value, scale, index) { | |
return radial_calcs.rScale(value ? | |
value | |
: radial_calcs.maxValue * scale) * Math.cos(radial_calcs.angleSlice * index - Math.PI/2); | |
} | |
function calcY(value, scale, index) { | |
return radial_calcs.rScale(value ? | |
value | |
: radial_calcs.maxValue * scale) * Math.sin(radial_calcs.angleSlice * index - Math.PI/2); | |
} | |
function setColor(d, index, key) { | |
index = index ? index : d._i; | |
key = key ? key : d.key; | |
return options.areas.colors[key] ? options.areas.colors[key] : options.color(index); | |
} | |
// END REUSABLE FUNCTIONS | |
// ACCESSORS | |
// --------- | |
chart.nodes = function() { | |
return { svg: svg, chart: chart_node, hover: hover_node, tooltip: tooltip_node, legend: legend_node }; | |
} | |
chart.events = function(functions) { | |
if (!arguments.length) return events; | |
var fKeys = Object.keys(functions); | |
var eKeys = Object.keys(events); | |
for (var k=0; k < fKeys.length; k++) { | |
if (eKeys.indexOf(fKeys[k]) >= 0) events[fKeys[k]] = functions[fKeys[k]]; | |
} | |
return chart; | |
} | |
chart.width = function(value) { | |
if (!arguments.length) return options.width; | |
if (options.resize) { | |
options.widthMax = value; | |
} else { | |
options.width = value; | |
} | |
scaleChart(); | |
return chart; | |
}; | |
chart.height = function(value) { | |
if (!arguments.length) return options.height; | |
if (options.resize) { | |
options.heightMax = value; | |
} else { | |
options.height = value; | |
} | |
scaleChart(); | |
return chart; | |
}; | |
chart.duration = function(value) { | |
if (!arguments.length) return transition_time; | |
transition_time = value; | |
return chart; | |
} | |
chart.update = function() { | |
if (events.update.begin) events.update.begin(_data); | |
if (typeof update === 'function') update(); | |
setTimeout(function() { | |
if (events.update.end) events.update.end(_data); | |
}, transition_time); | |
} | |
chart.data = function(value) { | |
if (!arguments.length) return data; | |
if (legend_toggles.length) { | |
var keys = _data.map(function(m) {return m.key}); | |
legend_toggles.forEach(function (e, i) { chart.filterAreas(keys[i]); }) | |
} | |
legend_toggles = []; | |
data = value; | |
return chart; | |
}; | |
chart.pop = function() { | |
var row = data.pop() | |
if (typeof update === 'function') update(); | |
return row; | |
}; | |
chart.push = function(row) { | |
if ( row && row.constructor === Array ) { | |
for (var i=0; i < row.length; i++) { | |
check_key(row[i]); | |
} | |
} else { | |
check_key(row); | |
} | |
function check_key(one_row) { | |
if (one_row.key && data.map(function(m) { return m.key }).indexOf(one_row.key) < 0) { | |
data.push(one_row); | |
} | |
} | |
return chart; | |
}; | |
chart.shift = function() { | |
var row = data.shift(); | |
if (typeof update === 'function') update(); | |
return row; | |
}; | |
chart.unshift = function(row) { | |
if ( row && row.constructor === Array ) { | |
for (var i=0; i < row.length; i++) { | |
check_key(row[i]); | |
} | |
} else { | |
check_key(row); | |
} | |
function check_key(one_row) { | |
if (one_row.key && data.map(function(m) { return m.key }).indexOf(one_row.key) < 0) { | |
data.unshift(one_row); | |
} | |
} | |
return chart; | |
}; | |
chart.slice = function(begin, end) { | |
return data.slice(begin, end); | |
}; | |
// allows updating individual options and suboptions | |
// while preserving state of other options | |
chart.options = function(values) { | |
if (!arguments.length) return options; | |
var vKeys = Object.keys(values); | |
var oKeys = Object.keys(options); | |
for (var k=0; k < vKeys.length; k++) { | |
if (oKeys.indexOf(vKeys[k]) >= 0) { | |
if (typeof(options[vKeys[k]]) == 'object') { | |
var sKeys = Object.keys(values[vKeys[k]]); | |
var osKeys = Object.keys(options[vKeys[k]]); | |
for (var sk=0; sk < sKeys.length; sk++) { | |
if (osKeys.indexOf(sKeys[sk]) >= 0) { | |
options[vKeys[k]][sKeys[sk]] = values[vKeys[k]][sKeys[sk]]; | |
} | |
} | |
} else { | |
options[vKeys[k]] = values[vKeys[k]]; | |
} | |
} | |
} | |
return chart; | |
} | |
chart.margins = function(value) { | |
if (!arguments.length) return options.margins; | |
var vKeys = Object.keys(values); | |
var mKeys = Object.keys(options.margins); | |
for (var k=0; k < vKeys.length; k++) { | |
if (mKeys.indexOf(vKeys[k]) >= 0) options.margins[vKeys[k]] = values[vKeys[k]]; | |
} | |
return chart; | |
} | |
chart.levels = function(value) { | |
if (!arguments.length) return options.circles.levels; | |
options.circles.levels = value; | |
return chart; | |
} | |
chart.maxValue = function(value) { | |
if (!arguments.length) return options.circles.maxValue; | |
options.circles.maxValue = value; | |
return chart; | |
} | |
chart.opacity = function(value) { | |
if (!arguments.length) return options.areas.opacity; | |
options.areas.opacity = value; | |
return chart; | |
} | |
chart.borderWidth = function(value) { | |
if (!arguments.length) return options.areas.borderWidth; | |
options.areas.borderWidth = value; | |
return chart; | |
} | |
chart.rounded = function(value) { | |
if (!arguments.length) return options.areas.rounded; | |
options.areas.rounded = value; | |
return chart; | |
} | |
// range of colors to set color based on index | |
chart.color = function(value) { | |
if (!arguments.length) return options.color; | |
options.color = value; | |
return chart; | |
} | |
// colors set according to data keys | |
chart.colors = function(colores) { | |
if (!arguments.length) return options.areas.colors; | |
options.areas.colors = colores; | |
return chart; | |
} | |
chart.keys = function() { | |
return data.map(function(m) {return m.key}); | |
} | |
chart.axes = function() { | |
return getAxisLabels(data); | |
} | |
// add or remove keys (or key indices) to filter axes | |
chart.filterAxes = function(values) { | |
if (!arguments.length) return options.axes.filter; | |
var axes = getAxisLabels(data); | |
modifyList(options.axes.filter, values, axes); | |
return chart; | |
} | |
// add or remove keys (or key indices) to filter areas | |
chart.filterAreas = function(values) { | |
if (!arguments.length) return options.areas.filter; | |
var keys = data.map(function(m) {return m.key}); | |
modifyList(options.areas.filter, values, keys); | |
return chart; | |
} | |
// add or remove keys (or key indices) to invert | |
chart.invert = function(values) { | |
if (!arguments.length) return options.axes.invert; | |
var axes = getAxisLabels(data); | |
modifyList(options.axes.invert, values, axes); | |
return chart; | |
} | |
// add or remove ranges for keys | |
chart.ranges = function(values) { | |
if (!arguments.length) return options.axes.ranges; | |
if (typeof values == "string") return chart; | |
var axes = getAxisLabels(data); | |
if ( values && values.constructor === Array ) { | |
values.forEach(function(e) { checkRange(e); } ); | |
} else { | |
checkRange(values); | |
} | |
function checkRange(range_declarations) { | |
var keys = Object.keys(range_declarations); | |
for (var k=0; k < keys.length; k++) { | |
if ( axes.indexOf(keys[k]) >= 0 // is valid axis | |
&& range_declarations[keys[k]] // range array not undefined | |
&& range_declarations[keys[k]].constructor === Array | |
&& checkValues(keys[k], range_declarations[keys[k]]) ) { | |
options.axes.ranges[keys[k]] = range_declarations[keys[k]]; | |
} | |
} | |
} | |
function checkValues(key, range) { | |
if (range.length == 2 && !isNaN(range[0]) && !isNaN(range[1])) { | |
return true; | |
} else if (range.length == 0) { | |
delete options.axes.ranges[key]; | |
} | |
return false; | |
} | |
return chart; | |
} | |
// END ACCESSORS | |
// DEFAULT EVENTS | |
// -------------- | |
function areaMouseover(d, i, self) { | |
if (legend_toggles[d._i]) return; | |
//Dim all blobs | |
chart_node.selectAll("." + options.class + "RadarArea") | |
.transition().duration(200) | |
.style("fill-opacity", function(d, i, j) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : 0.1; | |
}) | |
//Bring back the hovered over blob | |
d3.select(self) | |
.transition().duration(200) | |
.style("fill-opacity", function(d, i, j) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : 0.7; | |
}); | |
} | |
function areaMouseout(d, i, self) { | |
//Bring back all blobs | |
chart_node.selectAll("." + options.class + "RadarArea") | |
.transition().duration(200) | |
.style("fill-opacity", function(d, i, j) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : options.areas.opacity; | |
}); | |
} | |
// on mouseover for the legend symbol | |
function legendMouseover(d, i, self) { | |
if (legend_toggles[d]) return; | |
var area = keys.indexOf(d) >= 0 ? d : keyScale(d); | |
//Dim all blobs | |
chart_node.selectAll("." + options.class + "RadarArea") | |
.transition().duration(200) | |
.style("fill-opacity", function(d, i, j) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : 0.1; | |
}); | |
//Bring back the hovered over blob | |
chart_node.selectAll("." + options.class + "RadarArea." + area.replace(/\s+/g, '')) | |
.transition().duration(200) | |
.style("fill-opacity", function(d, i, j) { | |
return options.areas.filter.indexOf(d.key) >= 0 ? 0 : 0.7; | |
}); | |
} | |
function legendClick(d, i, self) { | |
var keys = _data.map(function(m) {return m.key}); | |
modifyList(options.areas.filter, keys[d], keys); | |
legend_toggles[d] = legend_toggles[d] ? false : true; | |
update(); | |
} | |
function tooltip_show(d, i, self) { | |
if (legend_toggles[d._i]) return; | |
var labels = getAxisLabels(_data); | |
chart_node.select('[key="'+d.axis+'"]').select('text').style('opacity', 1); | |
var value = d.original_value ? d.original_value : Format(d.value); | |
newX = parseFloat(d3.select(self).attr('cx')) - 10; | |
newY = parseFloat(d3.select(self).attr('cy')) - 10; | |
tooltip | |
.attr('x', newX) | |
.attr('y', newY) | |
.text(value) | |
.transition().duration(200) | |
.style('opacity', 1); | |
} | |
function tooltip_hide(d, i, self) { | |
chart_node.select('[key="'+d.axis+'"]').select('text') | |
.style('opacity', options.axes.display && radial_calcs.radius > options.axes.threshold ? 1 : 0); | |
tooltip | |
.transition().duration(200) | |
.style("opacity", 0); | |
} | |
// Helper Functions | |
// ---------------- | |
function add_index(index, key, values) { | |
for (var v=0; v<values.length; v++) { | |
values[v]['_i'] = index; | |
values[v]['key'] = key; | |
} | |
return values; | |
} | |
var get_key = function(d) { return d && d.key; }; | |
var get_axis = function(d) { return d && d.axis; }; | |
// Wraps SVG text | |
// modification of: http://bl.ocks.org/mbostock/7555321 | |
function wrap(text, width) { | |
text.each(function(d, i, j) { | |
var text = d3.select(this); | |
var words = d.axis.split(/\s+/).reverse(); | |
var word; | |
var line = []; | |
var lineNumber = 0; | |
var lineHeight = 1.4; // ems | |
var x = calcX(null, options.circles.labelFactor, i); | |
var y = calcY(null, options.circles.labelFactor, i); | |
var dy = parseFloat(text.attr("dy")); | |
var tspan = text.text(null).append("tspan").attr("dy", dy + "em"); | |
while (word = words.pop()) { | |
line.push(word); | |
tspan.text(line.join(" ")); | |
if (tspan.node().getComputedTextLength() > width) { | |
line.pop(); | |
tspan.text(line.join(" ")); | |
line = [word]; | |
tspan = text.append("tspan").attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); | |
} | |
} | |
}); | |
} | |
window.addEventListener( 'resize', scaleChart, false ); | |
function scaleChart() { | |
if (!options.resize || !dom_parent) return; | |
var width_offset = dom_parent.node().getBoundingClientRect().left; | |
var height_offset = dom_parent.node().getBoundingClientRect().top; | |
var width = Math.min(options.widthMax, document.documentElement.clientWidth - width_offset); | |
var height = Math.min(options.heightMax, document.documentElement.clientHeight - height_offset); | |
options.height = height; | |
options.width = width; | |
chart.update(); | |
} | |
return chart; | |
} | |
var UUID = (function() { | |
var self = {}; | |
var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); } | |
self.generate = function() { | |
var d0 = Math.random()*0xffffffff|0; | |
var d1 = Math.random()*0xffffffff|0; | |
var d2 = Math.random()*0xffffffff|0; | |
var d3 = Math.random()*0xffffffff|0; | |
return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+ | |
lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+ | |
lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+ | |
lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff]; | |
} | |
return self; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment