Skip to content

Instantly share code, notes, and snippets.

@j3py
Last active July 22, 2018 17:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save j3py/67bccc27426451b2ab17a882ada0d605 to your computer and use it in GitHub Desktop.
Save j3py/67bccc27426451b2ab17a882ada0d605 to your computer and use it in GitHub Desktop.
Rainbow Bar Chart (D3js) VR (A-Frame) https://bl.ocks.org/j3py

Rainbow Chart VR

This is a work-in-progress

Note: Github limits requests to their APIs and I don't have a reliable error alert in the VR context yet.

ToDo

Currently this solution is not very useful. Here are some things I need to do to correct that:

  • Redo toggle button for color wave in VR (mostly done)
  • Debug possible memory leak (might be done - removed unnecessary attaching of the same event listeners and reassigning of the same attributes)
  • Increase error visibility by showing the API limit alert within the VR context.
  • Allow movement (eg, forward, backward, side to side, etc)
  • Floor plane should vary in size dynamically according to number of bars

The Project

All the things:

  • A-frame (JavaScript VR framework (wrapper for webVR DOM elements))
  • D3js (JavaScript framework for data visualization)
  • Various image assets for bl.ocks.org

Notes

I tried using Mirror.js to create a mirrored floor plane for even more awesome distraction. So far it has failed and I removed the code.

There used to be a regular old html/css button that allowed the user to toggle the color wave on and off. This is functional now as a VR button (clickable box), but it needs more styling.

After I added the tooltips, which are tied to the camera and cursor entities, basic movement like moving forward or backward went away. I'll fix that eventually.

// Register new component to show/hide tooltip via aframe
AFRAME.registerComponent('cursor-listener', {
init: function () {
// Get divs for the tooltip
var tip = document.getElementById("ttip");
var tipcont = document.getElementById("tipcontent");
this.el.addEventListener('mouseenter', function () {
let self = d3.select(this);
tipcont.setAttribute("value", self.attr("data-x") + ":\r\n" + self.attr("data-y") + " bytes");
tipcont.setAttribute("opacity", "1.0");
tip.setAttribute("opacity", "1.0");
});
this.el.addEventListener('mouseleave', function () {
tipcont.setAttribute("opacity", "0.0");
tip.setAttribute("opacity", "0.0");
});
}
});
// Register new component to toggle color wave via aframe
AFRAME.registerComponent('toggle-listener', {
init: function () {
// Get divs for the button
var button = document.getElementById('button');
var toggle = document.getElementById('toggle');
var tflag = true;
this.el.addEventListener('click', function() {
button.setAttribute("depth", "0.0");
if (tflag) {
toggle.setAttribute("value", "Turn on");
clearInterval(barFunInterval);
} else {
toggle.setAttribute("value", "Turn off");
turnOnBarFun();
}
tflag = tflag ? false : true;
setTimeout(function() {
button.setAttribute("depth", "0.1");
}, 500);
});
}
});
<!DOCTYPE html>
<html>
<head>
<title>What is up!</title>
<meta name="description" content="What is up">
<link id="favicon" rel="icon" href="./favicon.ico" type="image/x-icon">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./rainbowchart.css">
<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
<script src="rainbowchartvr.js"></script>
<script src="afComponents.js"></script>
</head>
<body>
<a-scene>
<a-sky color="#282829"></a-sky>
<a-box id="button"
class="button"
position="-1.0 0.01 -5.0"
rotation="-90 0 0"
color="#606060"
width="4"
height="1.0"
depth="0.1"
toggle-listener>
<a-text id="toggle"
class="regPad btnColors"
align="center"
height="1.0"
position="0.0 0.0 0.0"
opacity="1.0"
value="Turn off">
</a-text>
</a-box>
<a-entity camera="userHeight: 1.6" look-controls>
<a-entity cursor="fuse: true; fuseTimeout: 500"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
material="color: #606060; shader: flat">
</a-entity>
<a-circle id="ttip"
cursor="fuse: true; fuseTimeout: 500"
position="0.25 0.25 -1.0"
radius="0.175"
color="#606060"
opacity="0.0"
shader="flat">
<a-text id="tipcontent"
align="center"
height="0.01"
opacity="0.0"
width="1.3"
value="Language">
</a-text>
</a-circle>
</a-entity>
</a-scene>
</body>
</html>
body {
background-color: #E2E1E8;
}
var barFunInterval;
var turnOnBarFun;
var grapher = function(rawData) {
// for testing
// var data = [
// {
// 'language': 'js',
// 'frequency': 1000.0
// },
// {
// 'language': 'python',
// 'frequency': 655.0
// },
// {
// 'language': 'html',
// 'frequency': 200.0
// }
// ];
// the real thing
var languages = Object.keys(rawData);
var data = languages.map(function(language) {
return { 'language': language, 'frequency': rawData[language] };
});
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 20,
height = 20;
/* set y to scaleLinear() if you prefer, then start domain at 0 */
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLog().rangeRound([height, 0]);
/* ************************************************************ */
var scene = d3.select("a-scene");
x.domain(data.map(function(d) { return d.language; }));
/* set the beginning of your log scale if you need to */
y.domain([750, d3.max(data, function(d) { return d.frequency; })]);
/* ************************************************** */
scene.append("a-plane")
.attr("class", "axis axis--x")
.attr("rotation", "-90 0 0")
.attr("depth", 1)
.attr("height", 200)
.attr("width", 200)
.attr("position", "0 -0.01 -6")
.attr("material","color: #FFFFFF; roughness: 0; metalness: 1;")
// .call(d3.axisBottom(x)); // this calls the d3 axis labels
scene.selectAll(".bar")
.data(data)
.enter().append("a-box")
.attr("class", "bar")
.attr("x", function(d) { return x(d.language); })
.attr("y", function(d) { return y(d.frequency); })
.attr("data-x", function(d) { return d.language })
.attr("data-y", function(d) { return d.frequency })
.attr("depth", 1)
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.frequency); });
var bars = d3.selectAll(".bar");
bars.each(function(d, i) {
this.setAttribute("position", {
"x": i * 2 - 5,
"y": 0,
"z": -8
});
this.setAttribute("cursor-listener", "");
});
var count = 0;
var barFun = function() {
var colors = [
"#0C8EE8",
"#E600FF",
"#E8720C",
"#CAFF0A"
];
if (count > 3) {
count = 0;
}
bars.each(function(d, i) {
var self = d3.select(this);
self.transition()
.delay(i*150)
.attr("color", colors[count])
.attr("width", 1.5)
.attr("depth", 1.5)
.duration(150)
.ease(d3.easeLinear)
.transition()
.attr("width", 1)
.attr("depth", 1)
.duration(150)
.ease(d3.easeLinear);
});
count++;
};
barFun();
turnOnBarFun = function() {
barFunInterval = setInterval(barFun, (data.length * 150) + 150);
return;
}
turnOnBarFun();
};
var respData = {};
/* put in your username instead of "j3py"! */
var urls = ['https://api.github.com/users/j3py/repos'];
/* *************************************** */
var requester = function(url, flag, callback) {
var gitReq = new XMLHttpRequest();
gitReq.onload = function() {
if ((gitReq.status >= 200) && (gitReq.status <= 400)) {
var gitJson = JSON.parse(gitReq.response);
if (flag) {
var langArray = gitJson.map(function(repo) {
return repo.languages_url;
});
langArray.forEach(function(langUrl, i) {
if (i < langArray.length-1) {
return requester(langUrl, false, false);
}
if (i === langArray.length-1) {
return requester(langUrl, false, function(rawData) {
return grapher(rawData);
});
}
});
} else {
var keys = Object.keys(respData);
for (let lang in gitJson) {
if (keys.includes(lang)) {
respData[lang] = respData[lang] + gitJson[lang];
} else {
respData[lang] = gitJson[lang];
}
}
}
if (callback) {
return callback(respData);
}
} else {
return alert('Response status:' + gitReq.response);
}
};
gitReq.open('GET', url);
gitReq.send();
};
urls.forEach(function(url) {
// for testing
// grapher();
// the real thing
requester(url, true, false);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment