<commit_before>'use strict';

var L = require('leaflet');

var panelControl = L.Control.extend({
  options: {
    position: 'topright'
  },

  onAdd: function (map) {
    // Add reference to map.
    map.panelControl = this;

    // Main panel div.
    this._div = L.DomUtil.create('div', 'panel-control');

    // Header for panel control.
    var headerDiv = document.createElement('div');
    headerDiv.setAttribute('class', 'panel-header');
    headerDiv.innerHTML = '';
    this._div.appendChild(headerDiv);

    // Wait icon displayed while solving.
    this._waitDisplayDiv = document.createElement('div');
    this._waitDisplayDiv.setAttribute('class', 'wait-display');
    var waitIcon = document.createElement('i');
    waitIcon.setAttribute('id', 'wait-icon');
    this._waitDisplayDiv.appendChild(waitIcon);
    this._div.appendChild(this._waitDisplayDiv);

    // Initial displayed message.
    this._initDiv = document.createElement('div');
    this._initDiv.setAttribute('id', 'init-display');

    var header = document.createElement('p');
    header.innerHTML = 'Add locations either by:'

    var list = document.createElement('ul');
    var clickEl = document.createElement('li');
    clickEl.innerHTML = 'clicking on the map;';
    list.appendChild(clickEl);
    var uploadEl = document.createElement('li');
    uploadEl.innerHTML = 'using a file with one address (or Lat,Lng coord) on each line.';
    list.appendChild(uploadEl);

    var jsonUploadEl = document.createElement('li');
    jsonUploadEl.innerHTML = 'using a json-formatted file.';

    var fileInput = document.createElement('input');
    fileInput.setAttribute('type', 'file');
    fileInput.setAttribute('id', 'user-file');

    jsonUploadEl.appendChild(fileInput);
    list.appendChild(jsonUploadEl);

    this._initDiv.appendChild(header);
    this._initDiv.appendChild(list);
    this._div.appendChild(this._initDiv);

    // Table for vehicles display.
    this._vehiclesDiv = document.createElement('div');
    this._vehiclesDiv.setAttribute('id', 'panel-vehicles');

    // Table for jobs display.
    this._jobTable = document.createElement('table');
    this._jobTable.setAttribute('id', 'panel-jobs');
    this._jobTable.setAttribute('class', 'panel-table');

    // Table for job-ordered solution display.
    this._solutionTable = document.createElement('table');
    this._solutionTable.setAttribute('id', 'panel-solution');
    this._solutionTable.setAttribute('class', 'panel-table');

    // Form for the Overpass query
    this._overpassDiv = document.createElement('div');
    this._overpassDiv.setAttribute('id', 'panel-overpass');
    this._overpassDiv.style.display = 'none';

    this.addOverpassForm(map);

    var tableDiv = document.createElement('div');

    tableDiv.appendChild(this._vehiclesDiv);
    tableDiv.appendChild(document.createElement('hr'));
    tableDiv.appendChild(this._jobTable);
    tableDiv.appendChild(this._solutionTable);
    this._div.appendChild(tableDiv);
    this._div.appendChild(this._overpassDiv);

    // Prevent events on this control to alter the underlying map.
    L.DomEvent.disableClickPropagation(this._div);
    L.DomEvent.on(this._div, 'mousewheel', L.DomEvent.stopPropagation);

    return this._div;
  },

  onRemove: function(map) {
    // Remove reference from map.
    delete map.panelControl;
  },

  clearJobDisplay: function() {
    // Delete jobs display.
    for (var i = this._jobTable.rows.length; i > 0; i--) {
      this._jobTable.deleteRow(i -1);
    }
  },

  clearVehiclesDisplay: function() {
    // Delete vehicles div.
    this._vehiclesDiv.innerHTML = "";
  },

  clearDisplay: function() {
    this.clearJobDisplay();
    this.clearVehiclesDisplay();
    this.hideOverpassDisplay();
    this.showInitDiv();
  },

  clearSolutionDisplay: function() {
    for (var i = this._solutionTable.rows.length; i > 0; i--) {
      this._solutionTable.deleteRow(i -1);
    }
  },

  hideJobDisplay: function() {
    this._jobTable.style.display = 'none';
  },

  showJobDisplay: function() {
    this._jobTable.style.display = 'block';
  },

  hideInitDiv: function() {
    this._initDiv.style.display = 'none';
  },

  showInitDiv: function() {
    this._initDiv.style.display = 'block';
  },

  hideOverpassDisplay: function() {
    this._overpassDiv.style.display = 'none';
  },

  showOverpassDisplay: function() {
    this._overpassDiv.style.display = 'block';
  },

  hideOverpassButton: function() {
    document.getElementById('button-request').style.display = 'none';
  },

  showOverpassButton: function() {
    document.getElementById('button-request').style.display = 'block';
  },

  addOverpassForm: function(map) {
    var overpassForm = document.createElement('table');
    overpassForm.setAttribute('id', 'table-overpass');

    // Title
    var overpassHeading = document.createElement('h2');
    overpassHeading .innerHTML = 'Generate Jobs';
    overpassForm.appendChild(overpassHeading);

    // Table containing the Formular
    var tagTable = document.createElement('table');
    tagTable.setAttribute('class', 'overpass-table');

    // Subtitle
    var overpassSubtitle = document.createElement('text');
    overpassSubtitle .innerHTML = 'Tag OSM';
    overpassSubtitle.setAttribute('class', 'overpass-description');
    tagTable.appendChild(overpassSubtitle);

    var newLine = document.createElement ("br");
    tagTable.appendChild(newLine);

    // Description
    var overpassDescription = document.createElement('text');
    var tag_text = 'Tags';
    var amenity_text = 'amenity'
    overpassDescription.innerHTML = 'Expected OSM ' +
      tag_text.link('https://wiki.openstreetmap.org/wiki/Tags') +
      ' - Find more values associated to the key ' +
      amenity_text.link('https://wiki.openstreetmap.org/wiki/Key:amenity');
    tagTable.appendChild(overpassDescription);

    var newLine = document.createElement ("br");
    tagTable.appendChild(newLine);

    // Formular cells
    var lineForm = document.createElement('form-inline');
    lineForm.setAttribute('id', 'tag-table');
    lineForm.setAttribute('class', 'overpass-tag-table');

    // Key cell
    var keyelement = document.createElement('input');
    keyelement.setAttribute('id', 'key-cell');
    keyelement.setAttribute('class', 'overpass-tag');
    keyelement.setAttribute('type', 'texte');
    keyelement.setAttribute('value', 'amenity');
    lineForm.appendChild(keyelement);

    // Value cell
    var valueelement = document.createElement('input');
    valueelement.setAttribute('id', 'value-cell');
    valueelement.setAttribute('class', 'overpass-value');
    valueelement.setAttribute('type', 'texte');
    valueelement.setAttribute('value', 'pharmacy');
    lineForm.appendChild(valueelement);

    tagTable.appendChild(lineForm);

    // Submit button
    var submitelement = document.createElement('input');
    submitelement.setAttribute('id', 'button-request');
    submitelement.setAttribute('class', 'overpass-button');
    submitelement.setAttribute('type', 'button');
    submitelement.setAttribute('value', 'Add nodes');

    // Call overpass
    submitelement.onclick = function(e) {
      L.DomEvent.stopPropagation(e);
      document.getElementById('wait-icon').setAttribute('class', 'wait-icon');
      panelControl.hideOverpassButton();
      map.fireEvent('overpass');
    };

    tagTable.appendChild(submitelement);
    overpassForm.appendChild(tagTable);
    this._overpassDiv.appendChild(overpassForm);
  },

  toggle: function() {
    if (this._div.style.visibility == 'hidden') {
      this._div.style.visibility = 'visible';
    } else {
      this._div.style.visibility = 'hidden';
    }
  },

  getWidth: function() {
    var width = this._div.offsetWidth;
    if (this._div.style.visibility == 'hidden') {
      width = 0;
    }
    return width;
  }
});

var panelControl = new panelControl();

module.exports = panelControl;
<commit_msg>Min zoom level<commit_after>'use strict';

var L = require('leaflet');

var panelControl = L.Control.extend({
  options: {
    position: 'topright'
  },

  onAdd: function (map) {
    // Add reference to map.
    map.panelControl = this;

    // Main panel div.
    this._div = L.DomUtil.create('div', 'panel-control');

    // Header for panel control.
    var headerDiv = document.createElement('div');
    headerDiv.setAttribute('class', 'panel-header');
    headerDiv.innerHTML = '';
    this._div.appendChild(headerDiv);

    // Wait icon displayed while solving.
    this._waitDisplayDiv = document.createElement('div');
    this._waitDisplayDiv.setAttribute('class', 'wait-display');
    var waitIcon = document.createElement('i');
    waitIcon.setAttribute('id', 'wait-icon');
    this._waitDisplayDiv.appendChild(waitIcon);
    this._div.appendChild(this._waitDisplayDiv);

    // Initial displayed message.
    this._initDiv = document.createElement('div');
    this._initDiv.setAttribute('id', 'init-display');

    var header = document.createElement('p');
    header.innerHTML = 'Add locations either by:'

    var list = document.createElement('ul');
    var clickEl = document.createElement('li');
    clickEl.innerHTML = 'clicking on the map;';
    list.appendChild(clickEl);
    var uploadEl = document.createElement('li');
    uploadEl.innerHTML = 'using a file with one address (or Lat,Lng coord) on each line.';
    list.appendChild(uploadEl);

    var jsonUploadEl = document.createElement('li');
    jsonUploadEl.innerHTML = 'using a json-formatted file.';

    var fileInput = document.createElement('input');
    fileInput.setAttribute('type', 'file');
    fileInput.setAttribute('id', 'user-file');

    jsonUploadEl.appendChild(fileInput);
    list.appendChild(jsonUploadEl);

    this._initDiv.appendChild(header);
    this._initDiv.appendChild(list);
    this._div.appendChild(this._initDiv);

    // Table for vehicles display.
    this._vehiclesDiv = document.createElement('div');
    this._vehiclesDiv.setAttribute('id', 'panel-vehicles');

    // Table for jobs display.
    this._jobTable = document.createElement('table');
    this._jobTable.setAttribute('id', 'panel-jobs');
    this._jobTable.setAttribute('class', 'panel-table');

    // Table for job-ordered solution display.
    this._solutionTable = document.createElement('table');
    this._solutionTable.setAttribute('id', 'panel-solution');
    this._solutionTable.setAttribute('class', 'panel-table');

    // Form for the Overpass query
    this._overpassDiv = document.createElement('div');
    this._overpassDiv.setAttribute('id', 'panel-overpass');
    this._overpassDiv.style.display = 'none';

    this.addOverpassForm(map);

    var tableDiv = document.createElement('div');

    tableDiv.appendChild(this._vehiclesDiv);
    tableDiv.appendChild(document.createElement('hr'));
    tableDiv.appendChild(this._jobTable);
    tableDiv.appendChild(this._solutionTable);
    this._div.appendChild(tableDiv);
    this._div.appendChild(this._overpassDiv);

    // Prevent events on this control to alter the underlying map.
    L.DomEvent.disableClickPropagation(this._div);
    L.DomEvent.on(this._div, 'mousewheel', L.DomEvent.stopPropagation);

    return this._div;
  },

  onRemove: function(map) {
    // Remove reference from map.
    delete map.panelControl;
  },

  clearJobDisplay: function() {
    // Delete jobs display.
    for (var i = this._jobTable.rows.length; i > 0; i--) {
      this._jobTable.deleteRow(i -1);
    }
  },

  clearVehiclesDisplay: function() {
    // Delete vehicles div.
    this._vehiclesDiv.innerHTML = "";
  },

  clearDisplay: function() {
    this.clearJobDisplay();
    this.clearVehiclesDisplay();
    this.hideOverpassDisplay();
    this.showInitDiv();
  },

  clearSolutionDisplay: function() {
    for (var i = this._solutionTable.rows.length; i > 0; i--) {
      this._solutionTable.deleteRow(i -1);
    }
  },

  hideJobDisplay: function() {
    this._jobTable.style.display = 'none';
  },

  showJobDisplay: function() {
    this._jobTable.style.display = 'block';
  },

  hideInitDiv: function() {
    this._initDiv.style.display = 'none';
  },

  showInitDiv: function() {
    this._initDiv.style.display = 'block';
  },

  hideOverpassDisplay: function() {
    this._overpassDiv.style.display = 'none';
  },

  showOverpassDisplay: function() {
    this._overpassDiv.style.display = 'block';
  },

  hideOverpassButton: function() {
    document.getElementById('button-request').style.display = 'none';
  },

  showOverpassButton: function() {
    document.getElementById('button-request').style.display = 'block';
  },

  addOverpassForm: function(map) {
    var overpassForm = document.createElement('table');
    overpassForm.setAttribute('id', 'table-overpass');

    // Title
    var overpassHeading = document.createElement('h2');
    overpassHeading .innerHTML = 'Generate Jobs';
    overpassForm.appendChild(overpassHeading);

    // Table containing the Formular
    var tagTable = document.createElement('table');
    tagTable.setAttribute('class', 'overpass-table');

    // Subtitle
    var overpassSubtitle = document.createElement('text');
    overpassSubtitle .innerHTML = 'Tag OSM';
    overpassSubtitle.setAttribute('class', 'overpass-description');
    tagTable.appendChild(overpassSubtitle);

    var newLine = document.createElement ("br");
    tagTable.appendChild(newLine);

    // Description
    var overpassDescription = document.createElement('text');
    var tag_text = 'Tags';
    var amenity_text = 'amenity'
    overpassDescription.innerHTML = 'Expected OSM ' +
      tag_text.link('https://wiki.openstreetmap.org/wiki/Tags') +
      ' - Find more values associated to the key ' +
      amenity_text.link('https://wiki.openstreetmap.org/wiki/Key:amenity');
    tagTable.appendChild(overpassDescription);

    var newLine = document.createElement ("br");
    tagTable.appendChild(newLine);

    // Formular cells
    var lineForm = document.createElement('form-inline');
    lineForm.setAttribute('id', 'tag-table');
    lineForm.setAttribute('class', 'overpass-tag-table');

    // Key cell
    var keyelement = document.createElement('input');
    keyelement.setAttribute('id', 'key-cell');
    keyelement.setAttribute('class', 'overpass-tag');
    keyelement.setAttribute('type', 'texte');
    keyelement.setAttribute('value', 'amenity');
    lineForm.appendChild(keyelement);

    // Value cell
    var valueelement = document.createElement('input');
    valueelement.setAttribute('id', 'value-cell');
    valueelement.setAttribute('class', 'overpass-value');
    valueelement.setAttribute('type', 'texte');
    valueelement.setAttribute('value', 'pharmacy');
    lineForm.appendChild(valueelement);

    tagTable.appendChild(lineForm);

    // Submit button
    var submitelement = document.createElement('input');
    submitelement.setAttribute('id', 'button-request');
    submitelement.setAttribute('class', 'overpass-button');
    submitelement.setAttribute('type', 'button');
    submitelement.setAttribute('value', 'Add nodes');

    // Call overpass
    submitelement.onclick = function(e) {
      if (map.getZoom() < 9) {
        alert("The area is too large, please zoom in.");
        return;
      }
      L.DomEvent.stopPropagation(e);
      document.getElementById('wait-icon').setAttribute('class', 'wait-icon');
      panelControl.hideOverpassButton();
      map.fireEvent('overpass');
    };

    tagTable.appendChild(submitelement);
    overpassForm.appendChild(tagTable);
    this._overpassDiv.appendChild(overpassForm);
  },

  toggle: function() {
    if (this._div.style.visibility == 'hidden') {
      this._div.style.visibility = 'visible';
    } else {
      this._div.style.visibility = 'hidden';
    }
  },

  getWidth: function() {
    var width = this._div.offsetWidth;
    if (this._div.style.visibility == 'hidden') {
      width = 0;
    }
    return width;
  }
});

var panelControl = new panelControl();

module.exports = panelControl;
<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 * @author bhouston / http://exocortex.com/
 */

( function ( THREE ) {

	THREE.Raycaster = function ( origin, direction, near, far ) {

		this.ray = new THREE.Ray( origin, direction );

		// normalized ray.direction required for accurate distance calculations
		if ( this.ray.direction.lengthSq() > 0 ) {

			this.ray.direction.normalize();

		}

		this.near = near || 0;
		this.far = far || Infinity;

	};

	var sphere = new THREE.Sphere();
	var localRay = new THREE.Ray();
	var facePlane = new THREE.Plane();
	var intersectPoint = new THREE.Vector3();
	var matrixPosition = new THREE.Vector3();

	var inverseMatrix = new THREE.Matrix4();

	var descSort = function ( a, b ) {

		return a.distance - b.distance;

	};

	var intersectObject = function ( object, raycaster, intersects ) {

		if ( object instanceof THREE.Particle ) {

			matrixPosition.getPositionFromMatrix( object.matrixWorld );
			var distance = raycaster.ray.distanceToPoint( matrixPosition );

			if ( distance > object.scale.x ) {

				return intersects;

			}

			intersects.push( {

				distance: distance,
				point: object.position,
				face: null,
				object: object

			} );

		} else if ( object instanceof THREE.LOD ) {

			matrixPosition.getPositionFromMatrix( object.matrixWorld );
			var distance = raycaster.ray.origin.distanceTo( matrixPosition );

			intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );

		} else if ( object instanceof THREE.Mesh ) {

			var geometry = object.geometry;

			// Checking boundingSphere distance to ray
			matrixPosition.getPositionFromMatrix( object.matrixWorld );

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			sphere.set( matrixPosition, geometry.boundingSphere.radius * object.matrixWorld.getMaxScaleOnAxis() );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			var vertices = geometry.vertices;

			if ( geometry instanceof THREE.BufferGeometry ) {

				var material = object.material;

				if ( material === undefined ) return intersects;
				if ( geometry.dynamic === false ) return intersects;

				var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
				var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

				var side = object.material.side;

				var a, b, c;
				var precision = raycaster.precision;

				inverseMatrix.getInverse( object.matrixWorld );

				localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

				var fl;
				var indexed = false;

				if ( geometry.attributes.index ) {

					indexed = true;
					fl = geometry.attributes.index.numItems / 3;

				} else {

					fl = geometry.attributes.position.numItems / 9;

				}

				var vA = new THREE.Vector3();
				var vB = new THREE.Vector3();
				var vC = new THREE.Vector3();
				var vCB = new THREE.Vector3();
				var vAB = new THREE.Vector3();

				for ( var oi = 0; oi < geometry.offsets.length; ++oi ) {

					var start = geometry.offsets[ oi ].start;
					var count = geometry.offsets[ oi ].count;
					var index = geometry.offsets[ oi ].index;

					for ( var i = start, il = start + count; i < il; i += 3 ) {

						if ( indexed ) {

							a = index + geometry.attributes.index.array[ i ];
							b = index + geometry.attributes.index.array[ i + 1 ];
							c = index + geometry.attributes.index.array[ i + 2 ];

						} else {

							a = index;
							b = index + 1;
							c = index + 2;

						}

						vA.set(
							geometry.attributes.position.array[ a * 3 ],
							geometry.attributes.position.array[ a * 3 + 1 ],
							geometry.attributes.position.array[ a * 3 + 2 ]
						);
						vB.set(
							geometry.attributes.position.array[ b * 3 ],
							geometry.attributes.position.array[ b * 3 + 1 ],
							geometry.attributes.position.array[ b * 3 + 2 ]
						);
						vC.set(
							geometry.attributes.position.array[ c * 3 ],
							geometry.attributes.position.array[ c * 3 + 1 ],
							geometry.attributes.position.array[ c * 3 + 2 ]
						);

						facePlane.setFromCoplanarPoints( vA, vB, vC );

						var planeDistance = localRay.distanceToPlane( facePlane );

						// bail if the ray is too close to the plane
						if ( planeDistance < precision ) continue;

						// bail if the ray is behind the plane
						if ( planeDistance === null ) continue;

						// check if we hit the wrong side of a single sided face
						side = material.side;

						if ( side !== THREE.DoubleSide ) {

							var planeSign = localRay.direction.dot( facePlane.normal );

							if ( ! ( side === THREE.FrontSide ? planeSign < 0 : planeSign > 0 ) ) {

								continue;

							}

						}

						// this can be done using the planeDistance from localRay because
						// localRay wasn't normalized, but ray was
						if ( planeDistance < raycaster.near || planeDistance > raycaster.far ) {

							continue;

						}

						// passing in intersectPoint avoids a copy
						intersectPoint = localRay.at( planeDistance, intersectPoint );

						if ( THREE.Triangle.containsPoint( intersectPoint, vA, vB, vC ) === false ) {

							continue;

						}

						intersects.push( {

							// this works because the original ray was normalized,
							// and the transformed localRay wasn't
							distance: planeDistance,
							point: raycaster.ray.at( planeDistance ),
							face: null,
							faceIndex: null,
							object: object

						} );

					}
				}

			} else if ( geometry instanceof THREE.Geometry ) {

				var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
				var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

				var side = object.material.side;

				var a, b, c, d;
				var precision = raycaster.precision;

				inverseMatrix.getInverse( object.matrixWorld );

				localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

				for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {

					var face = geometry.faces[ f ];

					var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;

					if ( material === undefined ) continue;

					facePlane.setFromNormalAndCoplanarPoint( face.normal, vertices[face.a] );

					var planeDistance = localRay.distanceToPlane( facePlane );

					// bail if the ray is too close to the plane
					if ( planeDistance < precision ) continue;

					// bail if the ray is behind the plane
					if ( planeDistance === null ) continue;

					// check if we hit the wrong side of a single sided face
					side = material.side;
					if ( side !== THREE.DoubleSide ) {

						var planeSign = localRay.direction.dot( facePlane.normal );

						if ( ! ( side === THREE.FrontSide ? planeSign < 0 : planeSign > 0 ) ) {

							continue;

						}

					}

					// this can be done using the planeDistance from localRay because localRay
					// wasn't normalized, but ray was
					if ( planeDistance < raycaster.near || planeDistance > raycaster.far ) continue;

					// passing in intersectPoint avoids a copy
					intersectPoint = localRay.at( planeDistance, intersectPoint );

					if ( face instanceof THREE.Face3 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];

						if ( THREE.Triangle.containsPoint( intersectPoint, a, b, c ) === false ) {

							continue;

						}

					} else if ( face instanceof THREE.Face4 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];
						d = vertices[ face.d ];

						if ( THREE.Triangle.containsPoint( intersectPoint, a, b, d ) === false &&
						     THREE.Triangle.containsPoint( intersectPoint, b, c, d ) === false ) {

							continue;

						}

					} else {

						// This is added because if we call out of this if/else group when
						// none of the cases match it will add a point to the intersection
						// list erroneously.
						throw Error( "face type not supported" );

					}

					intersects.push( {

						// this works because the original ray was normalized,
						// and the transformed localRay wasn't
						distance: planeDistance,
						point: raycaster.ray.at( planeDistance ),
						face: face,
						faceIndex: f,
						object: object

					} );

				}

			}

		} else if ( object instanceof THREE.Line ) {

			var precision = raycaster.linePrecision;
			var precisionSq = precision * precision;

			var geometry = object.geometry;

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			// Checking boundingSphere distance to ray
			matrixPosition.getPositionFromMatrix(object.matrixWorld);
			sphere.set( matrixPosition, geometry.boundingSphere.radius * object.matrixWorld.getMaxScaleOnAxis() );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			inverseMatrix.getInverse( object.matrixWorld );
			localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );
			localRay.direction.normalize(); // for scale matrix

			var vertices = geometry.vertices;
			var nbVertices = vertices.length;
			var interSegment = new THREE.Vector3();
			var interRay = new THREE.Vector3();
			var step = object.type === THREE.LineStrip ? 1 : 2;

			for ( var i = 0; i < nbVertices - 1; i = i + step ) {

				localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );
				interSegment.applyMatrix4( object.matrixWorld );
				interRay.applyMatrix4( object.matrixWorld );

				if ( interRay.distanceToSquared( interSegment ) <= precisionSq ) {

					var distance = raycaster.ray.origin.distanceTo( interRay );

					if ( raycaster.near <= distance && distance <= raycaster.far ) {

						intersects.push( {

							distance: distance,
							point: interSegment.clone(),
							face: null,
							faceIndex: null,
							object: object

						} );

					}

				}

			}

		}

	};

	var intersectDescendants = function ( object, raycaster, intersects ) {

		var descendants = object.getDescendants();

		for ( var i = 0, l = descendants.length; i < l; i ++ ) {

			intersectObject( descendants[ i ], raycaster, intersects );

		}
	};

	//

	THREE.Raycaster.prototype.precision = 0.0001;
	THREE.Raycaster.prototype.linePrecision = 1;

	THREE.Raycaster.prototype.set = function ( origin, direction ) {

		this.ray.set( origin, direction );

		// normalized ray.direction required for accurate distance calculations
		if ( this.ray.direction.length() > 0 ) {

			this.ray.direction.normalize();

		}

	};

	THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {

		var intersects = [];

		if ( recursive === true ) {

			intersectDescendants( object, this, intersects );

		}

		intersectObject( object, this, intersects );

		intersects.sort( descSort );

		return intersects;

	};

	THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {

		var intersects = [];

		for ( var i = 0, l = objects.length; i < l; i ++ ) {

			intersectObject( objects[ i ], this, intersects );

			if ( recursive === true ) {

				intersectDescendants( objects[ i ], this, intersects );

			}

		}

		intersects.sort( descSort );

		return intersects;

	};

}( THREE ) );
<commit_msg>We do the line-picking in local space.<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 * @author bhouston / http://exocortex.com/
 */

( function ( THREE ) {

	THREE.Raycaster = function ( origin, direction, near, far ) {

		this.ray = new THREE.Ray( origin, direction );

		// normalized ray.direction required for accurate distance calculations
		if ( this.ray.direction.lengthSq() > 0 ) {

			this.ray.direction.normalize();

		}

		this.near = near || 0;
		this.far = far || Infinity;

	};

	var sphere = new THREE.Sphere();
	var localRay = new THREE.Ray();
	var facePlane = new THREE.Plane();
	var intersectPoint = new THREE.Vector3();
	var matrixPosition = new THREE.Vector3();

	var inverseMatrix = new THREE.Matrix4();

	var descSort = function ( a, b ) {

		return a.distance - b.distance;

	};

	var intersectObject = function ( object, raycaster, intersects ) {

		if ( object instanceof THREE.Particle ) {

			matrixPosition.getPositionFromMatrix( object.matrixWorld );
			var distance = raycaster.ray.distanceToPoint( matrixPosition );

			if ( distance > object.scale.x ) {

				return intersects;

			}

			intersects.push( {

				distance: distance,
				point: object.position,
				face: null,
				object: object

			} );

		} else if ( object instanceof THREE.LOD ) {

			matrixPosition.getPositionFromMatrix( object.matrixWorld );
			var distance = raycaster.ray.origin.distanceTo( matrixPosition );

			intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );

		} else if ( object instanceof THREE.Mesh ) {

			var geometry = object.geometry;

			// Checking boundingSphere distance to ray
			matrixPosition.getPositionFromMatrix( object.matrixWorld );

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			sphere.set( matrixPosition, geometry.boundingSphere.radius * object.matrixWorld.getMaxScaleOnAxis() );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			var vertices = geometry.vertices;

			if ( geometry instanceof THREE.BufferGeometry ) {

				var material = object.material;

				if ( material === undefined ) return intersects;
				if ( geometry.dynamic === false ) return intersects;

				var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
				var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

				var side = object.material.side;

				var a, b, c;
				var precision = raycaster.precision;

				inverseMatrix.getInverse( object.matrixWorld );

				localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

				var fl;
				var indexed = false;

				if ( geometry.attributes.index ) {

					indexed = true;
					fl = geometry.attributes.index.numItems / 3;

				} else {

					fl = geometry.attributes.position.numItems / 9;

				}

				var vA = new THREE.Vector3();
				var vB = new THREE.Vector3();
				var vC = new THREE.Vector3();
				var vCB = new THREE.Vector3();
				var vAB = new THREE.Vector3();

				for ( var oi = 0; oi < geometry.offsets.length; ++oi ) {

					var start = geometry.offsets[ oi ].start;
					var count = geometry.offsets[ oi ].count;
					var index = geometry.offsets[ oi ].index;

					for ( var i = start, il = start + count; i < il; i += 3 ) {

						if ( indexed ) {

							a = index + geometry.attributes.index.array[ i ];
							b = index + geometry.attributes.index.array[ i + 1 ];
							c = index + geometry.attributes.index.array[ i + 2 ];

						} else {

							a = index;
							b = index + 1;
							c = index + 2;

						}

						vA.set(
							geometry.attributes.position.array[ a * 3 ],
							geometry.attributes.position.array[ a * 3 + 1 ],
							geometry.attributes.position.array[ a * 3 + 2 ]
						);
						vB.set(
							geometry.attributes.position.array[ b * 3 ],
							geometry.attributes.position.array[ b * 3 + 1 ],
							geometry.attributes.position.array[ b * 3 + 2 ]
						);
						vC.set(
							geometry.attributes.position.array[ c * 3 ],
							geometry.attributes.position.array[ c * 3 + 1 ],
							geometry.attributes.position.array[ c * 3 + 2 ]
						);

						facePlane.setFromCoplanarPoints( vA, vB, vC );

						var planeDistance = localRay.distanceToPlane( facePlane );

						// bail if the ray is too close to the plane
						if ( planeDistance < precision ) continue;

						// bail if the ray is behind the plane
						if ( planeDistance === null ) continue;

						// check if we hit the wrong side of a single sided face
						side = material.side;

						if ( side !== THREE.DoubleSide ) {

							var planeSign = localRay.direction.dot( facePlane.normal );

							if ( ! ( side === THREE.FrontSide ? planeSign < 0 : planeSign > 0 ) ) {

								continue;

							}

						}

						// this can be done using the planeDistance from localRay because
						// localRay wasn't normalized, but ray was
						if ( planeDistance < raycaster.near || planeDistance > raycaster.far ) {

							continue;

						}

						// passing in intersectPoint avoids a copy
						intersectPoint = localRay.at( planeDistance, intersectPoint );

						if ( THREE.Triangle.containsPoint( intersectPoint, vA, vB, vC ) === false ) {

							continue;

						}

						intersects.push( {

							// this works because the original ray was normalized,
							// and the transformed localRay wasn't
							distance: planeDistance,
							point: raycaster.ray.at( planeDistance ),
							face: null,
							faceIndex: null,
							object: object

						} );

					}
				}

			} else if ( geometry instanceof THREE.Geometry ) {

				var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
				var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

				var side = object.material.side;

				var a, b, c, d;
				var precision = raycaster.precision;

				inverseMatrix.getInverse( object.matrixWorld );

				localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

				for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {

					var face = geometry.faces[ f ];

					var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;

					if ( material === undefined ) continue;

					facePlane.setFromNormalAndCoplanarPoint( face.normal, vertices[face.a] );

					var planeDistance = localRay.distanceToPlane( facePlane );

					// bail if the ray is too close to the plane
					if ( planeDistance < precision ) continue;

					// bail if the ray is behind the plane
					if ( planeDistance === null ) continue;

					// check if we hit the wrong side of a single sided face
					side = material.side;
					if ( side !== THREE.DoubleSide ) {

						var planeSign = localRay.direction.dot( facePlane.normal );

						if ( ! ( side === THREE.FrontSide ? planeSign < 0 : planeSign > 0 ) ) {

							continue;

						}

					}

					// this can be done using the planeDistance from localRay because localRay
					// wasn't normalized, but ray was
					if ( planeDistance < raycaster.near || planeDistance > raycaster.far ) continue;

					// passing in intersectPoint avoids a copy
					intersectPoint = localRay.at( planeDistance, intersectPoint );

					if ( face instanceof THREE.Face3 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];

						if ( THREE.Triangle.containsPoint( intersectPoint, a, b, c ) === false ) {

							continue;

						}

					} else if ( face instanceof THREE.Face4 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];
						d = vertices[ face.d ];

						if ( THREE.Triangle.containsPoint( intersectPoint, a, b, d ) === false &&
						     THREE.Triangle.containsPoint( intersectPoint, b, c, d ) === false ) {

							continue;

						}

					} else {

						// This is added because if we call out of this if/else group when
						// none of the cases match it will add a point to the intersection
						// list erroneously.
						throw Error( "face type not supported" );

					}

					intersects.push( {

						// this works because the original ray was normalized,
						// and the transformed localRay wasn't
						distance: planeDistance,
						point: raycaster.ray.at( planeDistance ),
						face: face,
						faceIndex: f,
						object: object

					} );

				}

			}

		} else if ( object instanceof THREE.Line ) {

			var precision = raycaster.linePrecision;
			var precisionSq = precision * precision;

			var geometry = object.geometry;

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			// Checking boundingSphere distance to ray
			matrixPosition.getPositionFromMatrix(object.matrixWorld);
			sphere.set( matrixPosition, geometry.boundingSphere.radius * object.matrixWorld.getMaxScaleOnAxis() );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			inverseMatrix.getInverse( object.matrixWorld );
			localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );
			localRay.direction.normalize(); // for scale matrix

			var vertices = geometry.vertices;
			var nbVertices = vertices.length;
			var interSegment = new THREE.Vector3();
			var interRay = new THREE.Vector3();
			var step = object.type === THREE.LineStrip ? 1 : 2;

			for ( var i = 0; i < nbVertices - 1; i = i + step ) {

				var distSq = localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );

				if ( distSq <= precisionSq ) {

					var distance = localRay.origin.distanceTo( interRay );

					if ( raycaster.near <= distance && distance <= raycaster.far ) {

						intersects.push( {

							distance: distance,
							// What do we want? intersection point on the ray or on the segment??
							// point: raycaster.ray.at( distance ),
							point: interSegment.clone().applyMatrix4( object.matrixWorld ),
							face: null,
							faceIndex: null,
							object: object

						} );

					}

				}

			}

		}

	};

	var intersectDescendants = function ( object, raycaster, intersects ) {

		var descendants = object.getDescendants();

		for ( var i = 0, l = descendants.length; i < l; i ++ ) {

			intersectObject( descendants[ i ], raycaster, intersects );

		}
	};

	//

	THREE.Raycaster.prototype.precision = 0.0001;
	THREE.Raycaster.prototype.linePrecision = 1;

	THREE.Raycaster.prototype.set = function ( origin, direction ) {

		this.ray.set( origin, direction );

		// normalized ray.direction required for accurate distance calculations
		if ( this.ray.direction.length() > 0 ) {

			this.ray.direction.normalize();

		}

	};

	THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {

		var intersects = [];

		if ( recursive === true ) {

			intersectDescendants( object, this, intersects );

		}

		intersectObject( object, this, intersects );

		intersects.sort( descSort );

		return intersects;

	};

	THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {

		var intersects = [];

		for ( var i = 0, l = objects.length; i < l; i ++ ) {

			intersectObject( objects[ i ], this, intersects );

			if ( recursive === true ) {

				intersectDescendants( objects[ i ], this, intersects );

			}

		}

		intersects.sort( descSort );

		return intersects;

	};

}( THREE ) );
<|endoftext|>


<commit_before>"use strict";

/**
 * @ngdoc service
 * @name mdvorakDataRouter.$dataRouterLoaderProvider
 *
 * @description
 * Provider allows configuration of loading and resolving views. Note that media types are registered in
 * {@link mdvorakDataRouter.$dataRouterRegistryProvider $dataRouterRegistryProvider} and not here.
 */
module.provider('$dataRouterLoader', function dataRouterLoaderProvider() {
    var provider = this;
    var toString = Object.prototype.toString;

    provider.globals = {};

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterLoaderProvider
     * @name global
     *
     * @description
     * Sets global configuration for all routes. It is then merged into each view configuration, with view taking precedence.
     *
     * _Note: This method is also available directly on `$dataRouter` to ease configuration._
     *
     * @example
     * This example shows how to load additional data from URL provided in the response. `relatedData` object will be
     * then available in view controllers.
     *
     * ```js
     *     $dataRouterLoader.global({
     *         resolve: {
     *             relatedData: function($http, $data) {
     *                 if ($data._links && $data._links.related) {
     *                     return $http.get($data._links.related.href)
     *                         .then(function(response) {
     *                             return response.data;
     *                         });
     *                 }
     *
     *                 return null;
     *             }
     *         }
     *     });
     * ```
     *
     * @param {Object} config Configuration object. Properties of object type are merged together instead of overwriting.
     * @returns {Object} Reference to the provider.
     */
    provider.global = function global(config) {
        if (!config) return provider;

        provider.globals = $$mergeConfigObjects(provider.globals, config);

        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterLoaderProvider
     * @name extractType
     *
     * @description
     * Extracts media type from given response. Default implementation returns `Content-Type` header.
     *
     * @example
     * Example of custom implementation.
     * ```js
     *     $dataRouterLoader.extractType = function extractTypeFromJson(response) {
     *         return response.data && response.data._type;
     *     };
     * ```
     *
     * @param {Object} response Response object, see `$http` documentation for details.
     *
     * _Note that while `response` itself is never null, data property can be._
     *
     * @returns {String} Media type of the response. It will be normalized afterwards. If the return value is empty,
     * `application/octet-stream` will be used.
     */
    provider.extractType = function extractTypeFromContentType(response) {
        return response.headers('Content-Type');
    };

    /**
     * @ngdoc service
     * @name mdvorakDataRouter.$dataRouterLoader
     *
     * @description
     * Abstraction of data loading and view preparation. It uses views registered in
     * {@link mdvorakDataRouter.$dataRouterRegistry $dataRouterRegistry}.
     */
    this.$get = function $dataRouterLoaderFactory($log, $sce, $http, $templateCache, $q, $injector, $rootScope, $dataRouterRegistry, $$dataRouterEventSupport) {
        var $dataRouterLoader = {
            /**
             * @ngdoc method
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name normalizeMediaType
             *
             * @description
             * Normalizes the media type. Removes format suffix (everything after +), and prepends application/ if there is
             * just subtype.
             *
             * @param {String} mimeType Media type to match.
             * @returns {String} Normalized media type.
             */
            normalizeMediaType: $dataRouterRegistry.normalizeMediaType,

            /**
             * Extracts media type from the response, using configured
             * {@link mdvorakDataRouter.$dataRouterLoaderProvider#methods_extractType method}.
             * Unlike on provider, this method returns the type already
             * {@link mdvorakDataRouter.$dataRouterRegistry#methods_normalizeMediaType normalized}.
             *
             * @param response {Object} Response object.
             * @returns {String} Normalized media type of the response or null if it cannot be determined.
             */
            extractType: function extractType(response) {
                return $dataRouterRegistry.normalizeMediaType(provider.extractType(response));
            },

            /**
             * @ngdoc method
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name prefetchTemplate
             *
             * @description
             * Eagerly fetches the template for the given media type. If media type is unknown, nothing happens.
             * This method returns immediately, no promise is returned.
             *
             * @param {String} mediaType Media type for which we want to prefetch the template.
             */
            prefetchTemplate: function prefetchTemplate(mediaType) {
                var view = $dataRouterRegistry.match(mediaType);

                if (view) {
                    $log.debug("Pre-fetching template for " + mediaType);
                    $dataRouterLoader.$$loadTemplate(view, mediaType);
                } else {
                    $log.debug("Cannot prefetch template for " + mediaType + ", type is not registered");
                }
            },

            /**
             * @ngdoc method
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name prepareView
             *
             * @description
             * Prepares the view to be displayed. Loads data from given URL, resolves view by its content type,
             * and then finally resolves template and all other resolvables.
             *
             * @param {String} url URL of the data to be fetched. They are always loaded using GET method.
             * @param {Object=} current Current response data. If provided and forceReload is false, routeDataUpdate flag
             *                          of the response may be set, indicating that view doesn't have to be reloaded.
             * @param {boolean=} forceReload When false, it allows just data update. Without current parameter does nothing.
             * @returns {Object} Promise of completely initialized response, including template and locals.
             */
            prepareView: function prepareView(url, current, forceReload) {
                // Load data and view
                return $dataRouterLoader.$$loadData(url)
                    .then(loadDataSuccess)
                    .then(null, loadError); // This must be in the chain after loadDataSuccess

                // Note: We are not chaining promises directly, instead we are returning them,
                // which works in the end the same. However, the chain is shorter in case of failure.

                function loadDataSuccess(response) {
                    // It is worth continuing?
                    // Check whether whole view needs to be refreshed
                    if (!forceReload && isSameView(current, response)) {
                        // Update data
                        response.routeDataUpdate = true;
                        return response;
                    }

                    // Load view
                    return $dataRouterLoader.$$loadView(response);
                }

                function loadError(response) {
                    response.routeError = true;

                    // Try specific view first, then generic
                    response.type = '$error_' + response.status;
                    response.view = $dataRouterRegistry.match(response.type);

                    if (!response.view) {
                        response.type = '$error';
                        response.view = $dataRouterRegistry.match('$error');
                    }

                    // Load the view
                    if (response.view) {
                        return $dataRouterLoader.$$loadView(response);
                    } else {
                        return $q.reject(response);
                    }
                }

                function isSameView(current, next) {
                    return current && next && current.url === next.url && current.type === next.type;
                }
            },

            /**
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name $$loadData
             * @private
             *
             * @description
             * Loads view data from given URL.
             * Tries to automatically match the view by the data Content-Type header.
             * If the view is found, and transformResponse key is set, response is automatically resolved.
             *
             * @param {String} url URL to load data from. They are always loaded using GET method.
             * @returns {Promise} Promise of the response.
             */
            $$loadData: function $$loadData(url) {
                $log.debug("Loading resource " + url);

                // Fetch data and return promise
                return $http({url: url, method: 'GET', dataRouter: true}).then(function dataLoaded(response) {
                    // Match existing resource
                    var type = $dataRouterRegistry.normalizeMediaType(provider.extractType(response)) || 'application/octet-stream';
                    var view = $dataRouterRegistry.match(type);

                    // Unknown media type
                    if (!view) {
                        return $q.reject(asResponse({
                            url: url,
                            status: 999,
                            statusText: "Application Error",
                            data: "Unknown content type " + type,
                            config: response.config,
                            headers: response.headers,
                            type: type
                        }));
                    }

                    // Merge view
                    // Note: If this could be cached in some way, it would be nice
                    view = $$mergeConfigObjects({}, provider.globals, view);

                    // Success
                    var result = {
                        url: url,
                        status: response.status,
                        statusText: response.statusText,
                        headers: response.headers,
                        config: response.config,
                        type: type,
                        data: response.data,
                        view: view
                    };

                    if (view.transformResponse) {
                        return asResponse(view.transformResponse(result));
                    } else {
                        return asResponse(result);
                    }
                }, function dataFailed(response) {
                    response.url = url;

                    return $q.reject(asResponse(response));
                });
            },

            /**
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name $$loadView
             * @private
             *
             * @description
             * Loads view template and initializes resolves.
             *
             * @param {Object|Promise} response Loaded data response. Can be promise.
             * @returns {Promise} Promise of loaded view. Promise is rejected if any of locals or template fails to resolve.
             */
            $$loadView: function $$loadView(response) {
                return $q.when(response).then(function responseReady(response) {
                    // Resolve view
                    if (response.view) {
                        // Prepare locals
                        var locals = angular.extend({}, provider.globals.resolve, response.view.resolve);
                        var template;

                        // Built-in locals
                        var builtInLocals = {
                            $data: response.data,
                            $dataType: response.type,
                            $dataUrl: response.url,
                            $dataResponse: response
                        };

                        // Resolve locals
                        if (locals) {
                            angular.forEach(locals, function resolveLocal(value, key) {
                                locals[key] = angular.isString(value) ?
                                    $injector.get(value) : $injector.invoke(value, '$dataRouterLoader', builtInLocals);
                            });
                        } else {
                            locals = {};
                        }

                        // Load template
                        template = $dataRouterLoader.$$loadTemplate(response.view, response.type);

                        if (angular.isDefined(template)) {
                            locals['$template'] = template;
                        }

                        return $q.all(locals).then(function localsLoaded(locals) {
                            // Built-in locals
                            angular.extend(locals, builtInLocals);

                            // Store locals and continue
                            response.locals = locals;
                            return response;
                        }, function localsError() {
                            // Failure
                            return $q.reject(asResponse({
                                url: response.url,
                                status: 999,
                                statusText: "Application Error",
                                data: "Failed to resolve view " + response.type,
                                config: {},
                                headers: angular.noop
                            }));
                        });
                    }

                    // Return original object
                    return response;
                });
            },

            $$loadTemplate: function $$loadTemplate(view, mediaType) {
                // Ripped from ngRoute
                var template, templateUrl;

                if (angular.isDefined(template = view.template)) {
                    if (angular.isFunction(template)) {
                        template = template(mediaType);
                    }
                } else if (angular.isDefined(templateUrl = view.templateUrl)) {
                    if (angular.isFunction(templateUrl)) {
                        templateUrl = templateUrl(mediaType);
                    }

                    templateUrl = view.loadedTemplateUrl || $sce.getTrustedResourceUrl(templateUrl);

                    if (angular.isDefined(templateUrl)) {
                        view.loadedTemplateUrl = templateUrl;

                        template = $http.get(templateUrl, {
                            cache: $templateCache
                        }).then(function templateLoaded(response) {
                            return response.data;
                        });
                    }
                }

                return template;
            }
        };

        // Converter function
        var responseExtensions = {
            dataAs: function dataAs(scope, name, listener) {
                var _this = this;
                scope[name] = _this.data;

                this.$on('$routeUpdate', function () {
                    // Update data
                    scope[name] = _this.data;

                    if (angular.isFunction(listener)) {
                        listener(_this.data);
                    }
                }, scope);
            }
        };

        function asResponse(response) {
            return angular.extend($$dataRouterEventSupport.$new(), response, responseExtensions);
        }

        // Return
        return $dataRouterLoader;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterLoaderProvider
     * @name $$mergeConfigObjects
     * @private
     *
     * @description
     * Merges configuration objects. Plain objects are merged, all other properties are overwritten.
     * `undefined` values in `src` are ignored.
     *
     * @param {Object} dst Target object.
     * @return {Object} Returns `dst` object.
     */
    function $$mergeConfigObjects(dst) {
        if (!dst) dst = {};

        // Multiple sources
        for (var i = 1; i < arguments.length; i++) {
            var src = arguments[i];

            if (src) {
                // Manual merge
                var keys = Object.keys(src);

                for (var k = 0; k < keys.length; k++) {
                    var key = keys[k];

                    // Skip undefined entries
                    if (angular.isUndefined(src[key])) return;

                    // Current value
                    var val = dst[key];

                    // If both values are plain objects, merge them, otherwise overwrite
                    if (isPlainObject(val) && isPlainObject(src[key])) {
                        // Merge
                        dst[key] = angular.extend(val, src[key]);
                    } else {
                        // Overwrite
                        dst[key] = src[key];
                    }
                }
            }
        }

        return dst;
    }

    provider.$$mergeConfigObjects = $$mergeConfigObjects;

    /**
     * Checks whether object is plain Object, if it is Date or whatever, it returns true.
     *
     * @param {Object} obj Checked object
     * @returns {boolean} true for POJO, false otherwise.
     */
    function isPlainObject(obj) {
        return angular.isObject(obj) && toString.call(obj) === '[object Object]';
    }
});
<commit_msg>$dataRouterLoader.responseExtensions is now configurable property<commit_after>"use strict";

/**
 * @ngdoc service
 * @name mdvorakDataRouter.$dataRouterLoaderProvider
 *
 * @description
 * Provider allows configuration of loading and resolving views. Note that media types are registered in
 * {@link mdvorakDataRouter.$dataRouterRegistryProvider $dataRouterRegistryProvider} and not here.
 */
module.provider('$dataRouterLoader', function dataRouterLoaderProvider() {
    var provider = this;
    var toString = Object.prototype.toString;

    provider.globals = {};

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterLoaderProvider
     * @name global
     *
     * @description
     * Sets global configuration for all routes. It is then merged into each view configuration, with view taking precedence.
     *
     * _Note: This method is also available directly on `$dataRouter` to ease configuration._
     *
     * @example
     * This example shows how to load additional data from URL provided in the response. `relatedData` object will be
     * then available in view controllers.
     *
     * ```js
     *     $dataRouterLoader.global({
     *         resolve: {
     *             relatedData: function($http, $data) {
     *                 if ($data._links && $data._links.related) {
     *                     return $http.get($data._links.related.href)
     *                         .then(function(response) {
     *                             return response.data;
     *                         });
     *                 }
     *
     *                 return null;
     *             }
     *         }
     *     });
     * ```
     *
     * @param {Object} config Configuration object. Properties of object type are merged together instead of overwriting.
     * @returns {Object} Reference to the provider.
     */
    provider.global = function global(config) {
        if (!config) return provider;

        provider.globals = $$mergeConfigObjects(provider.globals, config);

        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterLoaderProvider
     * @name extractType
     *
     * @description
     * Extracts media type from given response. Default implementation returns `Content-Type` header.
     *
     * @example
     * Example of custom implementation.
     * ```js
     *     $dataRouterLoader.extractType = function extractTypeFromJson(response) {
     *         return response.data && response.data._type;
     *     };
     * ```
     *
     * @param {Object} response Response object, see `$http` documentation for details.
     *
     * _Note that while `response` itself is never null, data property can be._
     *
     * @returns {String} Media type of the response. It will be normalized afterwards. If the return value is empty,
     * `application/octet-stream` will be used.
     */
    provider.extractType = function extractTypeFromContentType(response) {
        return response.headers('Content-Type');
    };

    provider.responseExtensions = {
        dataAs: function dataAs(scope, name, listener) {
            var _this = this;
            scope[name] = _this.data;

            this.$on('$routeUpdate', function () {
                // Update data
                scope[name] = _this.data;

                if (angular.isFunction(listener)) {
                    listener(_this.data);
                }
            }, scope);
        }
    };

    /**
     * @ngdoc service
     * @name mdvorakDataRouter.$dataRouterLoader
     *
     * @description
     * Abstraction of data loading and view preparation. It uses views registered in
     * {@link mdvorakDataRouter.$dataRouterRegistry $dataRouterRegistry}.
     */
    this.$get = function $dataRouterLoaderFactory($log, $sce, $http, $templateCache, $q, $injector, $rootScope, $dataRouterRegistry, $$dataRouterEventSupport) {
        var $dataRouterLoader = {
            /**
             * @ngdoc method
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name normalizeMediaType
             *
             * @description
             * Normalizes the media type. Removes format suffix (everything after +), and prepends application/ if there is
             * just subtype.
             *
             * @param {String} mimeType Media type to match.
             * @returns {String} Normalized media type.
             */
            normalizeMediaType: $dataRouterRegistry.normalizeMediaType,

            /**
             * Extracts media type from the response, using configured
             * {@link mdvorakDataRouter.$dataRouterLoaderProvider#methods_extractType method}.
             * Unlike on provider, this method returns the type already
             * {@link mdvorakDataRouter.$dataRouterRegistry#methods_normalizeMediaType normalized}.
             *
             * @param response {Object} Response object.
             * @returns {String} Normalized media type of the response or null if it cannot be determined.
             */
            extractType: function extractType(response) {
                return $dataRouterRegistry.normalizeMediaType(provider.extractType(response));
            },

            /**
             * @ngdoc method
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name prefetchTemplate
             *
             * @description
             * Eagerly fetches the template for the given media type. If media type is unknown, nothing happens.
             * This method returns immediately, no promise is returned.
             *
             * @param {String} mediaType Media type for which we want to prefetch the template.
             */
            prefetchTemplate: function prefetchTemplate(mediaType) {
                var view = $dataRouterRegistry.match(mediaType);

                if (view) {
                    $log.debug("Pre-fetching template for " + mediaType);
                    $dataRouterLoader.$$loadTemplate(view, mediaType);
                } else {
                    $log.debug("Cannot prefetch template for " + mediaType + ", type is not registered");
                }
            },

            /**
             * @ngdoc method
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name prepareView
             *
             * @description
             * Prepares the view to be displayed. Loads data from given URL, resolves view by its content type,
             * and then finally resolves template and all other resolvables.
             *
             * @param {String} url URL of the data to be fetched. They are always loaded using GET method.
             * @param {Object=} current Current response data. If provided and forceReload is false, routeDataUpdate flag
             *                          of the response may be set, indicating that view doesn't have to be reloaded.
             * @param {boolean=} forceReload When false, it allows just data update. Without current parameter does nothing.
             * @returns {Object} Promise of completely initialized response, including template and locals.
             */
            prepareView: function prepareView(url, current, forceReload) {
                // Load data and view
                return $dataRouterLoader.$$loadData(url)
                    .then(loadDataSuccess)
                    .then(null, loadError); // This must be in the chain after loadDataSuccess

                // Note: We are not chaining promises directly, instead we are returning them,
                // which works in the end the same. However, the chain is shorter in case of failure.

                function loadDataSuccess(response) {
                    // It is worth continuing?
                    // Check whether whole view needs to be refreshed
                    if (!forceReload && isSameView(current, response)) {
                        // Update data
                        response.routeDataUpdate = true;
                        return response;
                    }

                    // Load view
                    return $dataRouterLoader.$$loadView(response);
                }

                function loadError(response) {
                    response.routeError = true;

                    // Try specific view first, then generic
                    response.type = '$error_' + response.status;
                    response.view = $dataRouterRegistry.match(response.type);

                    if (!response.view) {
                        response.type = '$error';
                        response.view = $dataRouterRegistry.match('$error');
                    }

                    // Load the view
                    if (response.view) {
                        return $dataRouterLoader.$$loadView(response);
                    } else {
                        return $q.reject(response);
                    }
                }

                function isSameView(current, next) {
                    return current && next && current.url === next.url && current.type === next.type;
                }
            },

            /**
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name $$loadData
             * @private
             *
             * @description
             * Loads view data from given URL.
             * Tries to automatically match the view by the data Content-Type header.
             * If the view is found, and transformResponse key is set, response is automatically resolved.
             *
             * @param {String} url URL to load data from. They are always loaded using GET method.
             * @returns {Promise} Promise of the response.
             */
            $$loadData: function $$loadData(url) {
                $log.debug("Loading resource " + url);

                // Fetch data and return promise
                return $http({url: url, method: 'GET', dataRouter: true}).then(function dataLoaded(response) {
                    // Match existing resource
                    var type = $dataRouterRegistry.normalizeMediaType(provider.extractType(response)) || 'application/octet-stream';
                    var view = $dataRouterRegistry.match(type);

                    // Unknown media type
                    if (!view) {
                        return $q.reject(asResponse({
                            url: url,
                            status: 999,
                            statusText: "Application Error",
                            data: "Unknown content type " + type,
                            config: response.config,
                            headers: response.headers,
                            type: type
                        }));
                    }

                    // Merge view
                    // Note: If this could be cached in some way, it would be nice
                    view = $$mergeConfigObjects({}, provider.globals, view);

                    // Success
                    var result = {
                        url: url,
                        status: response.status,
                        statusText: response.statusText,
                        headers: response.headers,
                        config: response.config,
                        type: type,
                        data: response.data,
                        view: view
                    };

                    if (view.transformResponse) {
                        return asResponse(view.transformResponse(result));
                    } else {
                        return asResponse(result);
                    }
                }, function dataFailed(response) {
                    response.url = url;

                    return $q.reject(asResponse(response));
                });
            },

            /**
             * @methodOf mdvorakDataRouter.$dataRouterLoader
             * @name $$loadView
             * @private
             *
             * @description
             * Loads view template and initializes resolves.
             *
             * @param {Object|Promise} response Loaded data response. Can be promise.
             * @returns {Promise} Promise of loaded view. Promise is rejected if any of locals or template fails to resolve.
             */
            $$loadView: function $$loadView(response) {
                return $q.when(response).then(function responseReady(response) {
                    // Resolve view
                    if (response.view) {
                        // Prepare locals
                        var locals = angular.extend({}, provider.globals.resolve, response.view.resolve);
                        var template;

                        // Built-in locals
                        var builtInLocals = {
                            $data: response.data,
                            $dataType: response.type,
                            $dataUrl: response.url,
                            $dataResponse: response
                        };

                        // Resolve locals
                        if (locals) {
                            angular.forEach(locals, function resolveLocal(value, key) {
                                locals[key] = angular.isString(value) ?
                                    $injector.get(value) : $injector.invoke(value, '$dataRouterLoader', builtInLocals);
                            });
                        } else {
                            locals = {};
                        }

                        // Load template
                        template = $dataRouterLoader.$$loadTemplate(response.view, response.type);

                        if (angular.isDefined(template)) {
                            locals['$template'] = template;
                        }

                        return $q.all(locals).then(function localsLoaded(locals) {
                            // Built-in locals
                            angular.extend(locals, builtInLocals);

                            // Store locals and continue
                            response.locals = locals;
                            return response;
                        }, function localsError() {
                            // Failure
                            return $q.reject(asResponse({
                                url: response.url,
                                status: 999,
                                statusText: "Application Error",
                                data: "Failed to resolve view " + response.type,
                                config: {},
                                headers: angular.noop
                            }));
                        });
                    }

                    // Return original object
                    return response;
                });
            },

            $$loadTemplate: function $$loadTemplate(view, mediaType) {
                // Ripped from ngRoute
                var template, templateUrl;

                if (angular.isDefined(template = view.template)) {
                    if (angular.isFunction(template)) {
                        template = template(mediaType);
                    }
                } else if (angular.isDefined(templateUrl = view.templateUrl)) {
                    if (angular.isFunction(templateUrl)) {
                        templateUrl = templateUrl(mediaType);
                    }

                    templateUrl = view.loadedTemplateUrl || $sce.getTrustedResourceUrl(templateUrl);

                    if (angular.isDefined(templateUrl)) {
                        view.loadedTemplateUrl = templateUrl;

                        template = $http.get(templateUrl, {
                            cache: $templateCache
                        }).then(function templateLoaded(response) {
                            return response.data;
                        });
                    }
                }

                return template;
            }
        };

        // Converter function
        function asResponse(response) {
            return angular.extend($$dataRouterEventSupport.$new(), response, provider.responseExtensions);
        }

        // Return
        return $dataRouterLoader;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterLoaderProvider
     * @name $$mergeConfigObjects
     * @private
     *
     * @description
     * Merges configuration objects. Plain objects are merged, all other properties are overwritten.
     * `undefined` values in `src` are ignored.
     *
     * @param {Object} dst Target object.
     * @return {Object} Returns `dst` object.
     */
    function $$mergeConfigObjects(dst) {
        if (!dst) dst = {};

        // Multiple sources
        for (var i = 1; i < arguments.length; i++) {
            var src = arguments[i];

            if (src) {
                // Manual merge
                var keys = Object.keys(src);

                for (var k = 0; k < keys.length; k++) {
                    var key = keys[k];

                    // Skip undefined entries
                    if (angular.isUndefined(src[key])) return;

                    // Current value
                    var val = dst[key];

                    // If both values are plain objects, merge them, otherwise overwrite
                    if (isPlainObject(val) && isPlainObject(src[key])) {
                        // Merge
                        dst[key] = angular.extend(val, src[key]);
                    } else {
                        // Overwrite
                        dst[key] = src[key];
                    }
                }
            }
        }

        return dst;
    }

    provider.$$mergeConfigObjects = $$mergeConfigObjects;

    /**
     * Checks whether object is plain Object, if it is Date or whatever, it returns true.
     *
     * @param {Object} obj Checked object
     * @returns {boolean} true for POJO, false otherwise.
     */
    function isPlainObject(obj) {
        return angular.isObject(obj) && toString.call(obj) === '[object Object]';
    }
});
<|endoftext|>


<commit_before>var app = require('app');

var template = [{
  label: 'Edit',
  submenu: [{
    label: 'Undo',
    accelerator: 'CmdOrCtrl+Z',
    role: 'undo'
  }, {
    label: 'Redo',
    accelerator: 'Shift+CmdOrCtrl+Z',
    role: 'redo'
  }, {
    type: 'separator'
  }, {
    label: 'Cut',
    accelerator: 'CmdOrCtrl+X',
    role: 'cut'
  }, {
    label: 'Copy',
    accelerator: 'CmdOrCtrl+C',
    role: 'copy'
  }, {
    label: 'Paste',
    accelerator: 'CmdOrCtrl+V',
    role: 'paste'
  }, {
    label: 'Select All',
    accelerator: 'CmdOrCtrl+A',
    role: 'selectall'
  }]
}, {
  label: 'View',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+R',
    click: function(item, focusedWindow) {
      if (focusedWindow){
        focusedWindow.reload();
      }
    }
  }, {
    label: 'Clear cache and reload',
    accelerator: 'CmdOrCtrl+Shift+R',
    click: function(item, focusedWindow) {
      if (focusedWindow){
        // TODO: works only because partition on webview gets lost
        focusedWindow.webContents.session.clearCache(function () {
          focusedWindow.reload(true);
        });
      }
    }
  }, {
    label: 'Toggle Full Screen',
    accelerator: (function() {
      if (process.platform == 'darwin'){
        return 'Ctrl+Command+F';
      } else {
        return 'F11';
      }
    })(),
    click: function(item, focusedWindow) {
      if (focusedWindow){
        focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
      }
    }
  }, {
    label: 'Toggle Developer Tools',
    accelerator: (function() {
      if (process.platform == 'darwin'){
        return 'Alt+Command+I';
      } else {
        return 'Ctrl+Shift+I';
      }
    })(),
    click: function(item, focusedWindow) {
      if (focusedWindow){
        focusedWindow.toggleDevTools();
      }
    }
  }]
}, {
  label: 'Window',
  role: 'window',
  submenu: [{
    label: 'Minimize',
    accelerator: 'CmdOrCtrl+M',
    role: 'minimize'
  }, {
    label: 'Close',
    accelerator: 'CmdOrCtrl+W',
    role: 'close'
  }]
}, {
  label: 'Help',
  role: 'help',
  submenu: [{
    label: 'Learn More',
    click: function() { require('shell').openExternal('http://electron.atom.io') }
  }]
}];

if (process.platform == 'darwin') {
  var name = app.getName();
  template.unshift({
    label: name,
    submenu: [{
      label: 'About ' + name,
      role: 'about'
    }, {
      type: 'separator'
    }, {
      label: 'Services',
      role: 'services',
      submenu: []
    }, {
      type: 'separator'
    }, {
      label: 'Hide ' + name,
      accelerator: 'Command+H',
      role: 'hide'
    }, {
      label: 'Hide Others',
      accelerator: 'Command+Shift+H',
      role: 'hideothers'
    }, {
      label: 'Show All',
      role: 'unhide'
    }, {
      type: 'separator'
    }, {
      label: 'Quit',
      accelerator: 'Command+Q',
      click: function() { app.quit(); }
    }]
  });

  template[3].submenu.push({
    type: 'separator'
  }, {
    label: 'Bring All to Front',
    role: 'front'
  });
}

module.exports = template;
<commit_msg>properly ignore cache during reload<commit_after>var app = require('app');

var template = [{
  label: 'Edit',
  submenu: [{
    label: 'Undo',
    accelerator: 'CmdOrCtrl+Z',
    role: 'undo'
  }, {
    label: 'Redo',
    accelerator: 'Shift+CmdOrCtrl+Z',
    role: 'redo'
  }, {
    type: 'separator'
  }, {
    label: 'Cut',
    accelerator: 'CmdOrCtrl+X',
    role: 'cut'
  }, {
    label: 'Copy',
    accelerator: 'CmdOrCtrl+C',
    role: 'copy'
  }, {
    label: 'Paste',
    accelerator: 'CmdOrCtrl+V',
    role: 'paste'
  }, {
    label: 'Select All',
    accelerator: 'CmdOrCtrl+A',
    role: 'selectall'
  }]
}, {
  label: 'View',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+R',
    click: function(item, focusedWindow) {
      if (focusedWindow){
        focusedWindow.reload();
      }
    }
  }, {
    label: 'Clear cache and reload',
    accelerator: 'CmdOrCtrl+Shift+R',
    click: function(item, focusedWindow) {
      if (focusedWindow){
        // TODO: works only because partition on webview gets lost
        focusedWindow.webContents.session.clearCache(function () {
          focusedWindow.reloadIgnoringCache();
        });
      }
    }
  }, {
    label: 'Toggle Full Screen',
    accelerator: (function() {
      if (process.platform == 'darwin'){
        return 'Ctrl+Command+F';
      } else {
        return 'F11';
      }
    })(),
    click: function(item, focusedWindow) {
      if (focusedWindow){
        focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
      }
    }
  }, {
    label: 'Toggle Developer Tools',
    accelerator: (function() {
      if (process.platform == 'darwin'){
        return 'Alt+Command+I';
      } else {
        return 'Ctrl+Shift+I';
      }
    })(),
    click: function(item, focusedWindow) {
      if (focusedWindow){
        focusedWindow.toggleDevTools();
      }
    }
  }]
}, {
  label: 'Window',
  role: 'window',
  submenu: [{
    label: 'Minimize',
    accelerator: 'CmdOrCtrl+M',
    role: 'minimize'
  }, {
    label: 'Close',
    accelerator: 'CmdOrCtrl+W',
    role: 'close'
  }]
}, {
  label: 'Help',
  role: 'help',
  submenu: [{
    label: 'Learn More',
    click: function() { require('shell').openExternal('http://electron.atom.io') }
  }]
}];

if (process.platform == 'darwin') {
  var name = app.getName();
  template.unshift({
    label: name,
    submenu: [{
      label: 'About ' + name,
      role: 'about'
    }, {
      type: 'separator'
    }, {
      label: 'Services',
      role: 'services',
      submenu: []
    }, {
      type: 'separator'
    }, {
      label: 'Hide ' + name,
      accelerator: 'Command+H',
      role: 'hide'
    }, {
      label: 'Hide Others',
      accelerator: 'Command+Shift+H',
      role: 'hideothers'
    }, {
      label: 'Show All',
      role: 'unhide'
    }, {
      type: 'separator'
    }, {
      label: 'Quit',
      accelerator: 'Command+Q',
      click: function() { app.quit(); }
    }]
  });

  template[3].submenu.push({
    type: 'separator'
  }, {
    label: 'Bring All to Front',
    role: 'front'
  });
}

module.exports = template;
<|endoftext|>


<commit_before>var fetchValue = require("../fetch/value.js")
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Nests and groups the data.
//------------------------------------------------------------------------------
var dataNest = function( vars , flatData , nestingLevels , requirements ) {

  var nestedData   = d3.nest()
    , groupedData  = []
    , segments     = vars.shell === "viz"
                    ? [ "active" , "temp" , "total" ] : []
    , requirements = requirements || vars.types[vars.type.value].requirements || []
    , exceptions   = vars.shell === "viz"
                   ? [ vars.time.value , vars.icon.value ] : []
    , checkAxes    = function() {

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // If the visualization has method requirements, check to see if we need
      // to key the data by a continuous scale variable.
      //------------------------------------------------------------------------
      if ( requirements && requirements.length ) {

        vars.axes.values.forEach(function(axis){

          var axisKey = vars[axis].value

          if ( requirements.indexOf(axis) >= 0 && axisKey
               && vars[axis].scale.value === "continuous") {

            exceptions.push(axisKey)

            nestedData.key(function(d){
              return fetchValue( vars , d , axisKey )
            })

          }

        })

      }

    }

  if (!(requirements instanceof Array)) requirements = [requirements]

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Loop through each nesting level.
  //----------------------------------------------------------------------------
  nestingLevels.forEach(function( level , i ){

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create a nest key for the current level.
    //--------------------------------------------------------------------------
    nestedData
      .key(function(d){

        if ( typeof level === "function" ) {
          return level(d)
        }

        return fetchValue( vars , d , level )
      })

    checkAxes()

  })

  if ( !nestingLevels.length ) {

    nestedData
      .key(function(d){
        return true
      })

    checkAxes()
  }

  var i = nestingLevels.length ? nestingLevels.length - 1 : 0

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // If we're at the deepest level, create the rollup function.
  //----------------------------------------------------------------------------
  nestedData.rollup(function( leaves ) {

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // If there's only 1 leaf, and it's been processed, return it as-is.
    //--------------------------------------------------------------------------
    if ( leaves.length === 1 && ("d3plus" in leaves[0]) ) {
      var returnObj = d3plus.util.copy(leaves[0])
      returnObj.d3plus.depth = i
      groupedData.push(returnObj)
      return returnObj
    }

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create the "d3plus" object for the return variable, starting with
    // just the current depth.
    //--------------------------------------------------------------------------
    var returnObj = {
      "d3plus": {
        "depth": i
      }
    }

    if ("d3plus" in leaves[0]) {

      leaves.forEach(function(l){
        if ("d3plus" in l) {
          if (l.d3plus.merged instanceof Array) {
            if (!returnObj.d3plus.merged) returnObj.d3plus.merged = []
            returnObj.d3plus.merged = returnObj.d3plus.merged.concat(l.d3plus.merged)
          }
          if (l.d3plus.text) returnObj.d3plus.text = l.d3plus.text
        }
      })
    }

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create a reference sum for the 3 different "segment" variables.
    //--------------------------------------------------------------------------
    segments.forEach(function(c){

      var key = vars[c].value || c

      returnObj.d3plus[key] = d3.sum(leaves, function( d ) {

        if ( vars[c].value ) {

          var a = fetchValue(vars,d,vars[c].value)

          if ( typeof a !== "number" ) {
            a = a ? 1 : 0
          }

        }
        else if ( c === "total" ) {
          var a = 1
        }
        else {
          var a = 0
        }

        return a

      })

    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Aggregate all values detected in the data.
    //--------------------------------------------------------------------------
    for ( var key in vars.data.keys ) {

      var agg     = vars.aggs.value[key] || "sum"
        , aggType = typeof agg
        , keyType = vars.data.keys[key]
        , idKey   = vars.id.nesting.indexOf(key) >= 0
        , timeKey = "time" in vars && key === vars.time.value

      if ( key in returnObj.d3plus ) {

        returnObj[key] = returnObj.d3plus[key]

      }
      else if ( aggType === "function" ) {

        returnObj[key] = vars.aggs.value[key](leaves)

      }
      else if ( timeKey ) {
        var uniques = d3plus.util.uniques(leaves,key), dates = []

        function parseDates(arr) {

          for ( var i = 0; i < arr.length ; i++ ) {
            var d = arr[i]
            if (d !== undefined) {
              if (d.constructor === Date) dates.push(d)
              else if (d.constructor === Array) {
                parseDates(d)
              }
              else {
                d = new Date(d.toString())
                if (d !== "Invalid Date") {
                  d.setTime( d.getTime() + d.getTimezoneOffset() * 60 * 1000 )
                  dates.push(d)
                }
              }
            }
          }

        }

        parseDates(uniques)

        if (dates.length === 1) returnObj[key] = dates[0]
        else if (uniques.length) returnObj[key] = dates
        else returnObj[key] = null
      }
      else if ( keyType === "number" && aggType === "string" && !idKey ) {
        var uniques = d3plus.util.uniques(leaves,key)
        if (uniques.length) returnObj[key] = d3[agg](uniques)
        else returnObj[key] = null
      }
      else {

        var testVals = []
        function checkVal(obj) {
          if (obj instanceof Array) {
            obj.forEach(checkVal)
          }
          else if (d3plus.object.validate(obj) && key in obj) {
            if (obj[key] instanceof Array) {
              obj[key].forEach(checkVal)
            }
            else {
              testVals.push(obj)
            }
          }
        }
        checkVal(leaves)

        var keyValues = testVals.length === 1 ? testVals[0][key]
                      : d3plus.util.uniques( testVals , key )

        if ( keyValues !== undefined && keyValues !== null ) {

          if ( !(keyValues instanceof Array) ) {
            keyValues = [ keyValues ]
          }

          if ( keyValues.length ) {

            if ( idKey && vars.id.nesting.indexOf(key) > i && testVals.length > 1 ) {
              if (key == "id" && nestingLevels.length == 1 && testVals.length > leaves.length) {
                var newNesting = nestingLevels.concat(key)
                testVals = dataNest(vars,testVals,newNesting)
              }
              returnObj[key] = testVals.length === 1 ? testVals[0] : testVals

            }
            else {

              returnObj[key] = keyValues.length === 1 ? keyValues[0] : keyValues

            }

          }

        }
        else if (idKey) {
          var endPoint = vars.id.nesting.indexOf(key) - 1
          if (endPoint >= i && (!("endPoint" in returnObj.d3plus) || returnObj.d3plus.endPoint > i)) {
            returnObj.d3plus.endPoint = i
          }
        }

      }

    }

    groupedData.push(returnObj)

    return returnObj

  })

  var rename_key_value = function(obj) {
    if (obj.values && obj.values.length) {
      obj.children = obj.values.map(function(obj) {
        return rename_key_value(obj);
      })
      delete obj.values
      return obj
    }
    else if(obj.values) {
      return obj.values
    }
    else {
      return obj;
    }
  }

  var find_keys = function(obj,depth,keys) {
    if (obj.children) {
      if (vars.data.keys[nestingLevels[depth]] == "number") {
        obj.key = parseFloat(obj.key)
      }
      keys[nestingLevels[depth]] = obj.key
      delete obj.key
      for ( var k in keys ) {
        obj[k] = keys[k]
      }
      depth++
      obj.children.forEach(function(c){
        find_keys(c,depth,keys)
      })
    }
  }

  nestedData = nestedData
    .entries(flatData)
    .map(rename_key_value)
    .map(function(obj){
      find_keys(obj,0,{})
      return obj
    })

  return groupedData

}

module.exports = dataNest
<commit_msg>removed unnecessary object copy<commit_after>var fetchValue = require("../fetch/value.js")
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Nests and groups the data.
//------------------------------------------------------------------------------
var dataNest = function( vars , flatData , nestingLevels , requirements ) {

  var nestedData   = d3.nest()
    , groupedData  = []
    , segments     = vars.shell === "viz"
                    ? [ "active" , "temp" , "total" ] : []
    , requirements = requirements || vars.types[vars.type.value].requirements || []
    , exceptions   = vars.shell === "viz"
                   ? [ vars.time.value , vars.icon.value ] : []
    , checkAxes    = function() {

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // If the visualization has method requirements, check to see if we need
      // to key the data by a continuous scale variable.
      //------------------------------------------------------------------------
      if ( requirements && requirements.length ) {

        vars.axes.values.forEach(function(axis){

          var axisKey = vars[axis].value

          if ( requirements.indexOf(axis) >= 0 && axisKey
               && vars[axis].scale.value === "continuous") {

            exceptions.push(axisKey)

            nestedData.key(function(d){
              return fetchValue( vars , d , axisKey )
            })

          }

        })

      }

    }

  if (!(requirements instanceof Array)) requirements = [requirements]

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Loop through each nesting level.
  //----------------------------------------------------------------------------
  nestingLevels.forEach(function( level , i ){

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create a nest key for the current level.
    //--------------------------------------------------------------------------
    nestedData
      .key(function(d){

        if ( typeof level === "function" ) {
          return level(d)
        }

        return fetchValue( vars , d , level )
      })

    checkAxes()

  })

  if ( !nestingLevels.length ) {

    nestedData
      .key(function(d){
        return true
      })

    checkAxes()
  }

  var i = nestingLevels.length ? nestingLevels.length - 1 : 0

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // If we're at the deepest level, create the rollup function.
  //----------------------------------------------------------------------------
  nestedData.rollup(function( leaves ) {

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // If there's only 1 leaf, and it's been processed, return it as-is.
    //--------------------------------------------------------------------------
    if ( leaves.length === 1 && ("d3plus" in leaves[0]) ) {
      // var returnObj = leaves[0]
      // returnObj.d3plus.depth = i
      groupedData.push(leaves[0])
      return leaves[0]
    }

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create the "d3plus" object for the return variable, starting with
    // just the current depth.
    //--------------------------------------------------------------------------
    var returnObj = {
      "d3plus": {
        "depth": i
      }
    }

    if ("d3plus" in leaves[0]) {

      leaves.forEach(function(l){
        if ("d3plus" in l) {
          if (l.d3plus.merged instanceof Array) {
            if (!returnObj.d3plus.merged) returnObj.d3plus.merged = []
            returnObj.d3plus.merged = returnObj.d3plus.merged.concat(l.d3plus.merged)
          }
          if (l.d3plus.text) returnObj.d3plus.text = l.d3plus.text
        }
      })
    }

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create a reference sum for the 3 different "segment" variables.
    //--------------------------------------------------------------------------
    segments.forEach(function(c){

      var key = vars[c].value || c

      returnObj.d3plus[key] = d3.sum(leaves, function( d ) {

        if ( vars[c].value ) {

          var a = fetchValue(vars,d,vars[c].value)

          if ( typeof a !== "number" ) {
            a = a ? 1 : 0
          }

        }
        else if ( c === "total" ) {
          var a = 1
        }
        else {
          var a = 0
        }

        return a

      })

    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Aggregate all values detected in the data.
    //--------------------------------------------------------------------------
    for ( var key in vars.data.keys ) {

      var agg     = vars.aggs.value[key] || "sum"
        , aggType = typeof agg
        , keyType = vars.data.keys[key]
        , idKey   = vars.id.nesting.indexOf(key) >= 0
        , timeKey = "time" in vars && key === vars.time.value

      if ( key in returnObj.d3plus ) {

        returnObj[key] = returnObj.d3plus[key]

      }
      else if ( aggType === "function" ) {

        returnObj[key] = vars.aggs.value[key](leaves)

      }
      else if ( timeKey ) {
        var uniques = d3plus.util.uniques(leaves,key), dates = []

        function parseDates(arr) {

          for ( var i = 0; i < arr.length ; i++ ) {
            var d = arr[i]
            if (d !== undefined) {
              if (d.constructor === Date) dates.push(d)
              else if (d.constructor === Array) {
                parseDates(d)
              }
              else {
                d = new Date(d.toString())
                if (d !== "Invalid Date") {
                  d.setTime( d.getTime() + d.getTimezoneOffset() * 60 * 1000 )
                  dates.push(d)
                }
              }
            }
          }

        }

        parseDates(uniques)

        if (dates.length === 1) returnObj[key] = dates[0]
        else if (uniques.length) returnObj[key] = dates
        else returnObj[key] = null
      }
      else if ( keyType === "number" && aggType === "string" && !idKey ) {
        var uniques = d3plus.util.uniques(leaves,key)
        if (uniques.length) returnObj[key] = d3[agg](uniques)
        else returnObj[key] = null
      }
      else {

        var testVals = []
        function checkVal(obj) {
          if (obj instanceof Array) {
            obj.forEach(checkVal)
          }
          else if (d3plus.object.validate(obj) && key in obj) {
            if (obj[key] instanceof Array) {
              obj[key].forEach(checkVal)
            }
            else {
              testVals.push(obj)
            }
          }
        }
        checkVal(leaves)

        var keyValues = testVals.length === 1 ? testVals[0][key]
                      : d3plus.util.uniques( testVals , key )

        if ( keyValues !== undefined && keyValues !== null ) {

          if ( !(keyValues instanceof Array) ) {
            keyValues = [ keyValues ]
          }

          if ( keyValues.length ) {

            if ( idKey && vars.id.nesting.indexOf(key) > i && testVals.length > 1 ) {
              if (key == "id" && nestingLevels.length == 1 && testVals.length > leaves.length) {
                var newNesting = nestingLevels.concat(key)
                testVals = dataNest(vars,testVals,newNesting)
              }
              returnObj[key] = testVals.length === 1 ? testVals[0] : testVals

            }
            else {

              returnObj[key] = keyValues.length === 1 ? keyValues[0] : keyValues

            }

          }

        }
        else if (idKey) {
          var endPoint = vars.id.nesting.indexOf(key) - 1
          if (endPoint >= i && (!("endPoint" in returnObj.d3plus) || returnObj.d3plus.endPoint > i)) {
            returnObj.d3plus.endPoint = i
          }
        }

      }

    }

    groupedData.push(returnObj)

    return returnObj

  })

  var rename_key_value = function(obj) {
    if (obj.values && obj.values.length) {
      obj.children = obj.values.map(function(obj) {
        return rename_key_value(obj);
      })
      delete obj.values
      return obj
    }
    else if(obj.values) {
      return obj.values
    }
    else {
      return obj;
    }
  }

  var find_keys = function(obj,depth,keys) {
    if (obj.children) {
      if (vars.data.keys[nestingLevels[depth]] == "number") {
        obj.key = parseFloat(obj.key)
      }
      keys[nestingLevels[depth]] = obj.key
      delete obj.key
      for ( var k in keys ) {
        obj[k] = keys[k]
      }
      depth++
      obj.children.forEach(function(c){
        find_keys(c,depth,keys)
      })
    }
  }

  nestedData = nestedData
    .entries(flatData)
    .map(rename_key_value)
    .map(function(obj){
      find_keys(obj,0,{})
      return obj
    })

  return groupedData

}

module.exports = dataNest
<|endoftext|>