lines.js

Copyright 2013 Allen Institute for Brain Science Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Visualize the spatial search density lines and injection site coordinates downloaded via download_data.py in 3D using threejs.

var dataFile = "data.json";

var mouseX = 0, mouseY = 0;
var targetRotationX = 0, targetRotationY = 0;
var targetRotationXOnMouseDown = 0, targetRotationYOnMouseDown = 0;

var mouseXOnMouseDown = 0, mouseYOnMouseDown = 0;

var container = null;

var maximum_number_of_lines = 600;
var scale = 0.01;
var dimensions = [ 133, 81, 115 ];

var camera, controls, scene, renderer, light;

Kick things off.

initialize();

function initialize() {
  container = document.getElementById('chart');

If the browser supports WebGL, download the data and initialize the 3D components.
Otherwise, display an error.

  if ( Detector.webgl ) {
    $(container).append($("<div>").addClass("loading").html("Loading WebGL..."));
    
    download_data(function(data) { 
      initialize_threejs(data); 
      
      $(".loading").remove();
    });
  } else {
    $(container).append(
      $('<div>').addClass('svgError')
        .html("This demo requires WebGL support. " +
          "Click <a href='http://caniuse.com/webgl'>here</a> " + 
            "to see which browsers support WebGL."));
  }
}

Retrieve the data generated by download_data.py. The number of downloaded lines can be a bit large for the browser, so some lines are filtered out.

function download_data(on_success) {
  $.ajax(dataFile, {
    dataType: "json",
    success: function(response) {

      var lines = response.lines;

If there are still too many lines, skip some.

      var stride = Math.round(lines.length / maximum_number_of_lines);
      if (stride > 1) {
        var out_lines = [];
        for (var i = 0; i < lines.length; i += stride) {
          out_lines.push(lines[i]);
        }
        response.lines = out_lines;
      }

      on_success(response);
    }
  });
}

Initialize all of the components required by threejs once the data exists.

function initialize_threejs(data) {
  var lines = data.lines || [];
  var injection_coordinates = data.injectionCoordinates;

  var jqcontainer = $(container);
  var width = jqcontainer.width();
  var height = jqcontainer.height();

Build the camera (field of view, aspect ratio, clipping planes, orientation, position).

  camera = new THREE.PerspectiveCamera( 33, width / height, 1, 10000 );
  camera.up.x = 0;
  camera.up.y = -1;
  camera.up.z = 0;
  camera.position.z = -300;

Initialize the mouse controls to allow pan/rotate/zoom.

  controls = new THREE.TrackballControls( camera, container );
  controls.rotateSpeed = 2.0;
  controls.zoomSpeed = 1.2;
  controls.panSpeed = 0.8;
  
  controls.noZoom = false;
  controls.noPan = false;
  
  controls.staticMoving = true;
  controls.dynamicDampingFactor = 0.3;
  
  controls.keys = [ 65, 83, 68 ];
  
  controls.addEventListener( 'change', render );
  

The scene will hold all of the lines and spheres.

  scene = new THREE.Scene();

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setClearColor(0x000000);
  renderer.setSize( width, height );

Don’t forget to add the WebGL element to the DOM.

  container.appendChild( renderer.domElement );

This is a light that will track the camera position.

  light =	new THREE.PointLight(0xFFFFFF);
  scene.add(light);

Make the wireframe outline around the reference space.

  var wireframe_geometry = new THREE.CubeGeometry( dimensions[0],
                           dimensions[1],
                           dimensions[2] );
  
  var wireframe_material = new THREE.MeshBasicMaterial({ color: 0xFFFFFF, 
                               shading: THREE.FlatShading, 
                               wireframe: true, 
                               wireframeLinewidth: 1 } );
  
  var wireframe_cube = new THREE.Mesh( wireframe_geometry, wireframe_material );

  scene.add( wireframe_cube );

Add spheres to indicate injection site coordinates.

  for (var i = 0; i < injection_coordinates.length; i++) {
    var coord = injection_coordinates[i];

    var sphere_geometry = new THREE.SphereGeometry( 0.5, 8, 8);
    var sphere_material = new THREE.MeshLambertMaterial({ color: 0xFFFF00 });
    var sphere = new THREE.Mesh( sphere_geometry, sphere_material );

The coordinates are in microns. World coordinates are in reference space voxel 100um coordinates. Scale the coordinate accordingly and translate to the center of the reference space cube.

    sphere.position.x = coord[0]*scale - dimensions[0] * 0.5;
    sphere.position.y = coord[1]*scale - dimensions[1] * 0.5;
    sphere.position.z = coord[2]*scale - dimensions[2] * 0.5;

    scene.add( sphere );
  }

Add the lines now.

  for (var i = 0; i < lines.length; i++) {
    var path = lines[i].path;

Create a line strip for all of the path coordinates.

    var geometry = new THREE.Geometry();
    for (var j = 0; j < path.length-1; j++) {
      geometry.vertices.push( new THREE.Vector3( path[j].coord[0], path[j].coord[1], path[j].coord[2] ) );
    }

Color the line by its injection structure color.

    var colorint = parseInt(lines[i]['structure-color'], 16);
    material = new THREE.LineBasicMaterial( { color: colorint, opacity: 1, linewidth: 1 } );
    

The coordinates are in microns, so set a global vertex position scale and then translate to the center of the reference space.

    var line = new THREE.Line(geometry, material );
    line.scale.x = line.scale.y = line.scale.z =  scale;
    line.position.x = -dimensions[0] * 0.5;
    line.position.y = -dimensions[1] * 0.5;
    line.position.z = -dimensions[2] * 0.5;

    scene.add( line );
  }

  animate();
}

Enable touch panning/rotation.

function onDocumentTouchMove( event ) {

  if ( event.touches.length === 1 ) {

    event.preventDefault();

    var jqcontainer = $j(container);
    var hw = jqcontainer.width() / 2;
    var hh = jqcontainer.height() / 2;

    mouseX = event.touches[ 0 ].pageX - hw;
    mouseY = event.touches[ 0 ].pageY - hh;
    targetRotationX = targetRotationXOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
    targetRotationY = targetRotationYOnMouseDown + ( mouseY - mouseYOnMouseDown ) * 0.05;

  }

}

function animate() {
  requestAnimationFrame( animate );
  controls.update();
}

function render() {

no need to render if the camera position hasn’t change.

  if (light.position.x != camera.position.x ||
    light.position.y != camera.position.y ||
    light.position.z != camera.position.z) {

    light.position.x = camera.position.x;
    light.position.y = camera.position.y;
    light.position.z = camera.position.z;
    
    renderer.render( scene, camera );
  }
}