API Mashups

Mashups - Data and Three JS

Final project - Changing the original proposal

(4/24/18 update) - Github page for this project. My original idea for API Mashups class was to create a 3D representation of webdata that could be experienced in VR, but since then I've been struggling with the web development assignments, specially with coding in JavaScript. Last class I made the proposal of scaling the project down and just making a 3D version of the FlickerTimes tutorial. When reminded by the teacher, I accepted that it seemed like an uninspired solution to my coding woes.

Three.js as inspiration

Three.js is a JavaScript library that allows for rich 3D content on the web - using only the browser. The examples at threejs.org are truly inspiring. Since the beginning of this class - semester even - I focused mostly on coding, but the result was that I didn't do anything visually exciting. Being more of a visual person, I decided to push myself into three.js and try another idea that has been in my head for a while already.

New Idea - 3D Data experience

My goal now is to create a 3D data visualization of NYCs water consumption using swimming pools. I believe 3D visualization of data, specially through VR, a great tool of seeing data through a more visceral point of view, instead of the detached analytical experience of spreadsheets. I'll add some sort of interaction as soon as I get this thing working. Also, swimming pools are big cubes, and I can make cubes in three.js. At least that's the idea for now. If the first prototype works, I'll think on another API interaction.

Learning ThreeJs

Managed to make the first, and very basic tutorial run through a local node server. The node server is run by localserver.js and is refreshed by the "forever" npm plugin.

tutorial_cube.gif

Now I'm looking at Threejs examples using RawGit to see in action what's in their GitHub repository

Tried to use a WEBGL detector, but it broke the code.

Running using different files

Instead of writing a long HTML file with all my style and JavaScript inside, I'm creating separete ones. It worked, but now I have two instances of the canvas and two cubes running on top o each other!

cubeNplane.JPG

Found out I copied code by mistake inside the main.js. When fixed, everything runs ok.

Kept following the instrunctions and now I have a cube AND a plane.

After going through the Lynda Workshop managed to create hierarchies, named objects and separate functions.

JavaScript code - main.js

//Lynda - Create a "init" function to keep it organized.
function init(){

  //Create Scene, Camera and Render
  //using "Perspective" camera (one of several types of camera)
  var scene = new THREE.Scene();

  //calling the GetBox function
  var box = getBox(1, 1, 1);
  //calling plane
  var plane = getPlane(4);

  //NAME OBJECTS - allows to call specific objects with "get" commands.
  plane.name = 'plane-1';
  //ROTATE PLANE = can't use "plane.rotation.x= 90;" because THREEJS uses Radians instead of degrees. For this will use the "math" object.
  plane.rotation.x = Math.PI/2;
  //BOX POSITION: makes its position half its height, so it keeps on the grid no matter what size.
  box.position.y =box.geometry.parameters.height/2;
  plane.position.y = 1;
  //after calling the function you have to add the object to the scene. PARENTING: (box) will become child of "plane" (or any other object, like "scene" fxp).
  plane.add(box);
  scene.add(plane);
    //camera parameters: field of view in degrees, aspect ratio, near, far clipping planes.
  var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight,1,1000);
//move the camera by 5, so it is in a different position than the cube (BoxGeometry)
camera.position.x = 1;
camera.position.y = 2;
  camera.position.z = 5;
  camera.lookAt(new THREE.Vector3(0,0,0));
    //"Where the magic happens".Parameters: setsize,
  //For lower resolution: setSize(window.innerWidth/2, window.innerHeight/2, false)
  var renderer = new THREE.WebGLRenderer();
  renderer.setSize( window.innerWidth, window.innerHeight);
  document.getElementById('webgl').appendChild(renderer.domElement);
//instead of just "renderer.render( scene, camera);", call the "update" function, adding "renderer" to its paramaters.
update(renderer, scene, camera);


//lets check the parameters on the browser console by typing "scene" on it.
  return scene;


}

function getBox(w, h, d){
  // The Object: Cube (BoxGeometry)

        var geometry = new THREE.BoxGeometry( w, h, d);
          //"to keep it simple we are just using color attribute"
        var material = new THREE.MeshBasicMaterial ( {color: 0x00ff00});
        var mesh = new THREE.Mesh( geometry, material);

//forgot to add "return mesh". I was just stating the var, but not running it. Thought that "add.box" would be enough.
        return mesh;
}

//LOOP RENDER AND ANIMATION
function update(renderer, scene, camera){
 renderer.render(scene, camera);
//"var plane" will find the "plane-1" plane object.
 var plane = scene.getObjectByName('plane-1');
 plane.rotation.y += 0.001;
 plane.rotation.z += 0.001;
 //call requestAnimationFrame and runs the "update" function itself, making a loop. requestAnimationFrame also optimizes the render for animation 60/s .
  requestAnimationFrame(function(){
  update(renderer,scene, camera );
  })
}
//PLANE GEOMETRY: Copied getBox and changed the parameters
function getPlane(size){
  // The Object: Cube (BoxGeometry)

        var geometry = new THREE.PlaneGeometry(size, size);
          //"to keep it simple we are just using color attribute"
        var material = new THREE.MeshBasicMaterial({
          color: 006800,
           side: THREE.DoubleSide
        });
        var mesh = new THREE.Mesh( geometry, material);

        return mesh;
}


//added "var scene" to "init();" so I can see the parameters on the browser inspector
var scene = init();
Forever Spinning. Two objects being constantly redrawn by the "update" function. 60 times a second to be more exact.

Forever Spinning. Two objects being constantly redrawn by the "update" function. 60 times a second to be more exact.

Day 2 - Let there be light

Single point light had to be moved away from initial position, so it would't be obscured by the cube.

Single point light had to be moved away from initial position, so it would't be obscured by the cube.

Continued the Lynda.com THREE.js tutorial. As I expected, working with coding through visuals has been more stimulating for me than pushing databases around. Also, I'm aware that following tutorials are much less frustrating than real life coding since improvements are obvious and often.

Changed materials from "basic" to "phong". This allows for light sources to interact with objects. Created a single point light and spent 15 minutes trying to figure out why it wasn't working only to find out that I misspelled a variable name (PointLight instead of the correct pointLight). Disabled Fog for testing the light.

dat.gui.cube.gif

dat.gui - adding interaction

Dat.gui is a JavaScript library that creates user interfaces that change variables: buttons, sliders...inputs Here's an example To use it, just download it, add to the library folder and call it from you HTML and JavasCript file. After creating the variables that will be controled by dat.gui, I tested controling the pointLight intensity. Now I can control the intensity through a slider inside the browser.

gui.add(pointLight, 'intensity', 0, 10);

Adding custom 3D models - FBX

Kept following the Lynda tutorial, and I feel it really helped me grasp the use of functions. After creating a group of cubes and trying different light sources, I wanted to see a 3D model I created displayed on the browser. 
I downloaded the FBXloader.ls from the THREEjs.org webpage. This allows the browser to read FBX format, which is commonly used in the Unity game development platform. After setting up the library, I created the "object" function that would load my model in the scene.

loader.load('./models/RoboHausAscii.fbx', function (object) {
 object.rotation.x= -1* Math.PI/2;

  object.traverse(function(child){
    if (child.isMesh){
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
  scene.add(object);
});

 

But nothing showed up in Chrome and the consoled displayed a message telling I was not allowed to use objects that were not scripts or html! After some quick googling I read that Firefox had none of these limitations and it worked. Magic: my 3D model is running inside a browser. 

robohaus_sm.gif

mAKING IT MY OWN

Today I reloaded my custom model inside Chrome, to see exactly what error message I was getting, and for some strange reason it just loaded my FBX model, just like Firefox the day before. Go figure....

The goal now is to start using what I learned from the tutorials to sketch my final project. I will experiment with some more interactions, but the main challenge will be to combine THREEjs with some other API, allowing external data to interact with the 3D models. This will require different JavaScripts files to talk to one another. I could write everything inside a single main.js file, but I know already that this would make everything quite messy.

But first... Gotta go back to the Lynda.com videos and review some basic tutorials on functions and objects

 

I think this time I finally understood how functions and objects work in JavaScript.

I think this time I finally understood how functions and objects work in JavaScript.

Connecting with the API

The goal now is to make the Three.js file work together with the NYC Data API. I have downloaded a Json file with all water usage data, but the goal of the project is for it to be update online. I didn't say "in real time" because the water consumption data is only updated once a year.  It sort of defeats the purpose of having a realtime API running instead of just getting the data from the static json file, but if I can get it to work I could make other visualizations using the same basic structure.

Now I have a grid of 3D cubes, lights and a free roaming camera. The next steps are:

  • make the number of squares on screen be relative to the water consumption data from the NYC database API, or any dataset for now.
  • Create option of seeing the data set through different perspectives (per capita, yearly, individual). This changes the number of cubes and camera position. 
  • Animate camera to exhibit different amounts of cubes/swimming pools and perspectives.
  • Create the website and CSS style of the whole experience.
  • change cube to a custom made swimming pool model.
  • link to other websites about conscious use of water.
  • (extra bonus) user input for custom data visualization. User watercalculator.org as reference.

5 hours later...

...and I'm still on the first bullet of the above list! I'm having issues getting a value from inside the function. More specifically, how to change the 'amount' value from the 'getBoxGrid' function. 

 

function getBoxGrid(amount, separationMultiplier) {
  //GROUP = objects container
  var group = new THREE.Group();
  this.amount = amount;
  for (var  i=0; i<this.amount;i++){
    var obj = getBox(1,1,1);
    obj.position.x = i * separationMultiplier;
    obj.position.y = obj.geometry.parameters.height/2;
    group.add(obj);
    for (var j=1; j<amount; j++) {
//?? why I need to create the "var object" again?
      var obj = getBox(1,1,1);
      obj.position.x = i * separationMultiplier;
      obj.position.y = obj.geometry.parameters.height/2;
      obj.position.z = j * separationMultiplier;
      group.add(obj);
    }

 

I can control the value of 'amount' on:

var boxGrid =  getBoxGrid(4, 1.5);

But if I try to call 'amount' anywhere else I get :

"Object "[object Object]" has no property "amount"

Tried to create a global variable called 'amount', but still didn't work. It doesn't override the value from var boxGrid(amount, ...);

9 hours later....

still can't change the 'amount' parameter. With some help I managed to get a new function that removes the former boxGrid and adds a new one with one extra 'amount' every 3 seconds, but I couldn't run the function any other way.

Spinning out of control. Looks cool though.

Spinning out of control. Looks cool though.

Created a button that was supposed to run the "addBoxGrid" function, instead of using the 3 second interval, but It says that there is no "addBoxGrid" function! Or I try to add "amout = amount +1", but it also says there is no amount variable, even when I create a global 'amount' variable. Even just running the function "addBoxGrid" gives no result. The button works if I try to add to other parameters, but never to the 'amount'. I have no idea whats going on.....If I can't create a single new box, there is no way I can control the number of boxes using the API. 

"Final" Presentation

The "quotes" on "final" are the best result I could get. After not being able to directly edit the 3D objects with the data, I had to accept that this was the best I could deliver for now. Something strange happened after showing this cheeky post and my floating cubes in my last API Mashpus class, and that was an uplifting mood and realization that I've done something neat...flawed, but neat. Not only I displayed cool 3D objects on a browser, but I've also managed to make the API work, or at least connect to the server and get the data. I have the pieces and basic knowledge to make a fully functional data visualization website, though I still have some hard time debugging and fixing it. 

The goal now is to come back to this post in the future, and make the "quotes" on "final" stand to it's purpose and signal not the end, but the continuing of my creative coding journey. 

Link to the GIT repository for this project here