<commit_before>'use strict';

/**
 * ### Trip Query Functions
 * These functions query the `gtfs_trips` table in the Right Track Database.
 * @module query/trips
 */

const cache = require('memory-cache');
const Agency = require('../gtfs/Agency.js');
const Route = require('../gtfs/Route.js');
const Stop = require('../gtfs/Stop.js');
const StopTime = require('../gtfs/StopTime.js');
const Trip = require('../gtfs/Trip.js');
const DateTime = require('../utils/DateTime.js');
const CalendarTable = require('./CalendarTable.js');
const StopTimesTable = require('./StopTimesTable.js');

// ==== QUERY FUNCTIONS ==== //

/**
 * Get the Trip (with Route, Service and StopTimes) specified by
 * the Trip ID from the passed database
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} id Trip ID
 * @param {int} date The date (yyyymmdd) that the Trip operates on
 * @param {function} callback Callback function
 * @param {Error} callback.error Database Query Error
 * @param {Trip} [callback.trip] The selected Trip
 */
let getTrip = function(db, id, date, callback) {

  // Check cache for trip
  let cacheKey = db.id + "-" + id;
  let cache = cache_tripsById.get(cacheKey);
  if ( cache !== null ) {
    return callback(null, cache);
  }

  // Build the select statement
  let select = "SELECT " +
    "gtfs_trips.trip_id, trip_short_name, wheelchair_accessible, service_id, " +
    "gtfs_directions.direction_id, description, " +
    "gtfs_routes.route_id, route_short_name, route_long_name, route_type, route_color, route_text_color, " +
    "gtfs_agency.agency_id, agency_name, agency_url, agency_timezone, " +
    "gtfs_stop_times.arrival_time, arrival_time_seconds, departure_time, departure_time_seconds, stop_sequence, pickup_type, drop_off_type, " +
    "gtfs_stops.stop_id, stop_name, stop_lat, stop_lon, stop_url, wheelchair_boarding, " +
    "rt_stops_extra.display_name, status_id, transfer_weight " +
    "FROM gtfs_trips " +
    "INNER JOIN gtfs_directions ON gtfs_trips.direction_id=gtfs_directions.direction_id " +
    "INNER JOIN gtfs_routes ON gtfs_trips.route_id=gtfs_routes.route_id " +
    "INNER JOIN gtfs_agency ON gtfs_routes.agency_id=gtfs_agency.agency_id " +
    "INNER JOIN gtfs_stop_times ON gtfs_trips.trip_id=gtfs_stop_times.trip_id " +
    "INNER JOIN gtfs_stops ON gtfs_stop_times.stop_id=gtfs_stops.stop_id " +
    "INNER JOIN rt_stops_extra ON gtfs_stops.stop_id=rt_stops_extra.stop_id " +
    "WHERE gtfs_trips.trip_id='" + id + "';";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(
        new Error('Could not get Trip from database')
      );
    }

    // No Trip Found
    if ( results.length === 0 ) {
      return callback(null, undefined);
    }

    // Get Agency and Route info from first row
    let row = results[0];

    // Build Agency
    let agency = new Agency(
      row.agency_name,
      row.agency_url,
      row.agency_timezone,
      row.agency_id
    );

    // Build Route
    let route = new Route(
      row.route_id,
      row.route_short_name,
      row.route_long_name,
      row.route_type,
      agency,
      row.route_color,
      row.route_text_color
    );

    // Get Service from service id
    CalendarTable.getService(db, row.service_id, function(err, service) {

      // Database Query Error
      if ( err ) {
        return callback(err);
      }

      // List of StopTimes
      let stopTimes = [];

      // Get StopTimes
      for ( let i = 0; i < results.length; i++ ) {
        let row = results[i];

        // Use display name for stop name, if present
        let stop_name = row.stop_name;
        if ( row.display_name !== undefined && row.display_name !== null && row.display_name !== "" ) {
          stop_name = row.display_name;
        }

        // Build Stop
        let stop = new Stop(
          row.stop_id,
          stop_name,
          row.stop_lat,
          row.stop_lon,
          row.stop_url,
          row.wheelchair_boarding,
          row.status_id,
          row.transfer_weight
        );

        // Build StopTime
        let stopTime = new StopTime(
          stop,
          row.arrival_time,
          row.departure_time,
          row.stop_sequence,
          row.arrival_time_seconds,
          row.departure_time_seconds,
          row.pickup_type,
          row.drop_off_type,
          date
        );

        // Add stop time to list
        stopTimes.push(stopTime);
      }

      // Build Trip
      let trip = new Trip(
        id,
        route,
        service,
        stopTimes,
        row.trip_short_name,
        row.direction_id,
        row.wheelchair_accessible,
        row.description
      );

      // Add Trip to Cache
      cache_tripsById.put(cacheKey, trip);

      // Return trip with callback
      return callback(null, trip);

    });

  });

};

/**
 * Get the Trip specified by the Trip short name that operates on the specified date
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} shortName Trip short name
 * @param {int} date Date Integer (yyyymmdd)
 * @param {function} callback Callback function
 * @param {Error} callback.error Database Query Error
 * @param {Trip} [callback.trip] The selected Trip
 */
function getTripByShortName(db, shortName, date, callback) {

  // Check Cache for Trip
  let cacheKey = db.id + "-" + shortName + "-" + date;
  let cache = cache_tripsByShortName.get(cacheKey);
  if ( cache !== null ) {
    return callback(null, cache);
  }

  // Get effective service ids
  _buildEffectiveServiceIDString(db, date, function(err, serviceIdString) {
    if ( err ) {
      return callback(err);
    }

    // Get Trip ID
    let select = "SELECT trip_id FROM gtfs_trips WHERE trip_short_name = '" +
      shortName + "' AND service_id IN " + serviceIdString + ";";

    // Query database
    db.get(select, function(err, result) {
      if ( err ) {
        return callback(err);
      }

      // No Trip Found
      if ( result === undefined ) {
        return callback(null, undefined);
      }

      // Get the Trip
      getTrip(db, result.trip_id, date, function(err, trip) {

        // Add Trip to Cache
        cache_tripsByShortName.put(cacheKey, trip);

        // Return Trip
        return callback(err, trip);

      });

    });

  });

}

/**
 * Find the Trip that leaves the specified origin Stop for the specified
 * destination Stop at the departure time specified by the DateTime
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} originId Origin Stop ID
 * @param {string} destinationId Destination Stop ID
 * @param {DateTime} departure DateTime of trip departure
 * @param {function} callback Callback function
 * @param {Error} callback.error Database Query Error
 * @param {Trip} [callback.trip] The selected Trip
 */
function getTripByDeparture(db, originId, destinationId, departure, callback) {

  // Check cache for trip
  let cacheKey = db.id + "-" + originId + "-" + destinationId + "-" + departure.getTimeSeconds() + "-" + departure.getDateInt();
  let cache = cache_tripsByDeparture.get(cacheKey);
  if ( cache !== null ) {
    return callback(null, cache);
  }

  // Check to make sure both origin and destination have IDs set
  if ( originId === "" || originId === undefined ||
      destinationId === "" || destinationId === undefined ) {
    return callback(
      new Error('Could not get Trip, origin and/or destination id not set')
    );
  }

  // ORIGINAL DEPARTURE DATE
  _getTripByDeparture(db, originId, destinationId, departure, function(err, trip) {
    if ( err ) {
      return callback(err);
    }

    // Trip Found...
    if ( trip !== undefined ) {
      cache_tripsByDeparture.put(cacheKey, trip);
      return callback(null, trip);
    }

    // PREVIOUS DEPARTURE DATE, +24 HOUR TIME
    let prev = DateTime.create(
      departure.getTimeSeconds() + 86400,
      departure.clone().deltaDays(-1).getDateInt()
    );

    _getTripByDeparture(db, originId, destinationId, prev, function(err, trip) {

      // Return results
      cache_tripsByDeparture.put(cacheKey, trip);
      return callback(err, trip);

    });

  });

}

/**
 * Get the Trip that matches the origin, destination and departure information
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} originId Origin Stop ID
 * @param {string} destinationId Destination Stop ID
 * @param {DateTime} departure Departure DateTime
 * @param {function} callback getTrip callback function(err, trip)
 * @private
 */
function _getTripByDeparture(db, originId, destinationId, departure, callback) {

  // Get Effective Services for departure date
  _buildEffectiveServiceIDString(db, departure.getDateInt(), function(err, serviceIdString) {
    if ( err ) {
      return callback(err);
    }

    // Find a matching trip
    _getMatchingTripId(db, originId, destinationId, departure, serviceIdString, function(err, tripId) {
      if ( err ) {
        return callback(err);
      }

      // --> No matching trip found
      if ( tripId === undefined ) {
        return callback(null, undefined);
      }

      // Build the matching trip...
      getTrip(db, tripId, departure.getDateInt(), function(err, trip) {
        if ( err ) {
          return callback(err);
        }

        // --> Return the matching trip
        return callback(err, trip);

      });

    });

  });

}

/**
 * Build the Effective Service ID String for SELECT query
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {int} date Date Integer (yyyymmdd)
 * @param {function} callback Callback function(err, serviceIdString)
 * @private
 */
function _buildEffectiveServiceIDString(db, date, callback) {

  // Query the Calendar for effective services
  CalendarTable.getServicesEffective(db, date, function(err, services) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // Build Service ID String
    let serviceIds = [];
    for ( let i = 0; i < services.length; i++ ) {
      serviceIds.push(services[i].id);
    }
    let serviceIdString = "('" + serviceIds.join("', '") + "')";

    // Return Service ID String
    return callback(null, serviceIdString);

  });

}

/**
 * Get the Trip ID of the trip matching the origin, destination, and departure information
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} originId Origin Stop ID
 * @param {string} destinationId Destination Stop ID
 * @param {DateTime} departure DateTime of departure
 * @param {string} serviceIdString Effective Service ID string
 * @param {function} callback Callback function(err, tripId)
 * @private
 */
function _getMatchingTripId(db, originId, destinationId, departure, serviceIdString, callback) {

  // Find a matching trip in the gtfs_stop_times table
  let select = "SELECT trip_id FROM gtfs_trips " +
    "WHERE service_id IN " + serviceIdString + " " +
    "AND trip_id IN (" +
    "SELECT trip_id FROM gtfs_stop_times WHERE stop_id='" + destinationId + "' " +
    "AND trip_id IN (" +
    "SELECT trip_id FROM gtfs_stop_times " +
    "WHERE stop_id='" + originId + "' AND departure_time_seconds=" + departure.getTimeSeconds() +
    "));";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // No results found
    if ( results.length === 0 ) {
      return callback(null, undefined);
    }

    // Find the best match
    let found = false;
    let count = 0;
    for ( let j = 0; j < results.length; j++ ) {
      let row = results[j];

      // Get StopTimes for origin and destination
      StopTimesTable.getStopTimeByTripStop(db, row.trip_id, originId, departure.getDateInt(), function(orErr, originStopTime) {
        StopTimesTable.getStopTimeByTripStop(db, row.trip_id, destinationId, departure.getDateInt(), function(deErr, destinationStopTime) {
          if ( orErr ) {
            return callback(orErr);
          }
          if ( deErr ) {
            return callback(deErr);
          }

          // Check stop sequence
          // If origin comes before destination, use that trip
          if ( originStopTime.stopSequence <= destinationStopTime.stopSequence ) {
            found = true;
            return callback(null, row.trip_id);
          }

          // No match found
          count ++;
          if ( !found && count === results.length ) {
            return callback(null, undefined);
          }

        });
      });

    }

  });

}

// ==== SETUP CACHES ==== //
let cache_tripsById = new cache.Cache();
let cache_tripsByShortName = new cache.Cache();
let cache_tripsByDeparture = new cache.Cache();

/**
 * Clear the TripsTable caches
 * @private
 */
function clearCache() {
  cache_tripsById.clear();
  cache_tripsByShortName.clear();
  cache_tripsByDeparture.clear();
}

// Export Functions
module.exports = {
  getTrip: getTrip,
  getTripByShortName: getTripByShortName,
  getTripByDeparture: getTripByDeparture,
  clearCache: clearCache
};<commit_msg>Query Trips: null wheelchair_accessible<commit_after>'use strict';

/**
 * ### Trip Query Functions
 * These functions query the `gtfs_trips` table in the Right Track Database.
 * @module query/trips
 */

const cache = require('memory-cache');
const Agency = require('../gtfs/Agency.js');
const Route = require('../gtfs/Route.js');
const Stop = require('../gtfs/Stop.js');
const StopTime = require('../gtfs/StopTime.js');
const Trip = require('../gtfs/Trip.js');
const DateTime = require('../utils/DateTime.js');
const CalendarTable = require('./CalendarTable.js');
const StopTimesTable = require('./StopTimesTable.js');

// ==== QUERY FUNCTIONS ==== //

/**
 * Get the Trip (with Route, Service and StopTimes) specified by
 * the Trip ID from the passed database
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} id Trip ID
 * @param {int} date The date (yyyymmdd) that the Trip operates on
 * @param {function} callback Callback function
 * @param {Error} callback.error Database Query Error
 * @param {Trip} [callback.trip] The selected Trip
 */
let getTrip = function(db, id, date, callback) {

  // Check cache for trip
  let cacheKey = db.id + "-" + id;
  let cache = cache_tripsById.get(cacheKey);
  if ( cache !== null ) {
    return callback(null, cache);
  }

  // Build the select statement
  let select = "SELECT " +
    "gtfs_trips.trip_id, trip_short_name, wheelchair_accessible, service_id, " +
    "gtfs_directions.direction_id, description, " +
    "gtfs_routes.route_id, route_short_name, route_long_name, route_type, route_color, route_text_color, " +
    "gtfs_agency.agency_id, agency_name, agency_url, agency_timezone, " +
    "gtfs_stop_times.arrival_time, arrival_time_seconds, departure_time, departure_time_seconds, stop_sequence, pickup_type, drop_off_type, " +
    "gtfs_stops.stop_id, stop_name, stop_lat, stop_lon, stop_url, wheelchair_boarding, " +
    "rt_stops_extra.display_name, status_id, transfer_weight " +
    "FROM gtfs_trips " +
    "INNER JOIN gtfs_directions ON gtfs_trips.direction_id=gtfs_directions.direction_id " +
    "INNER JOIN gtfs_routes ON gtfs_trips.route_id=gtfs_routes.route_id " +
    "INNER JOIN gtfs_agency ON gtfs_routes.agency_id=gtfs_agency.agency_id " +
    "INNER JOIN gtfs_stop_times ON gtfs_trips.trip_id=gtfs_stop_times.trip_id " +
    "INNER JOIN gtfs_stops ON gtfs_stop_times.stop_id=gtfs_stops.stop_id " +
    "INNER JOIN rt_stops_extra ON gtfs_stops.stop_id=rt_stops_extra.stop_id " +
    "WHERE gtfs_trips.trip_id='" + id + "';";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(
        new Error('Could not get Trip from database')
      );
    }

    // No Trip Found
    if ( results.length === 0 ) {
      return callback(null, undefined);
    }

    // Get Agency and Route info from first row
    let row = results[0];

    // Build Agency
    let agency = new Agency(
      row.agency_name,
      row.agency_url,
      row.agency_timezone,
      row.agency_id
    );

    // Build Route
    let route = new Route(
      row.route_id,
      row.route_short_name,
      row.route_long_name,
      row.route_type,
      agency,
      row.route_color,
      row.route_text_color
    );

    // Get Service from service id
    CalendarTable.getService(db, row.service_id, function(err, service) {

      // Database Query Error
      if ( err ) {
        return callback(err);
      }

      // List of StopTimes
      let stopTimes = [];

      // Get StopTimes
      for ( let i = 0; i < results.length; i++ ) {
        let row = results[i];

        // Use display name for stop name, if present
        let stop_name = row.stop_name;
        if ( row.display_name !== undefined && row.display_name !== null && row.display_name !== "" ) {
          stop_name = row.display_name;
        }

        // Build Stop
        let stop = new Stop(
          row.stop_id,
          stop_name,
          row.stop_lat,
          row.stop_lon,
          row.stop_url,
          row.wheelchair_boarding,
          row.status_id,
          row.transfer_weight
        );

        // Build StopTime
        let stopTime = new StopTime(
          stop,
          row.arrival_time,
          row.departure_time,
          row.stop_sequence,
          row.arrival_time_seconds,
          row.departure_time_seconds,
          row.pickup_type,
          row.drop_off_type,
          date
        );

        // Add stop time to list
        stopTimes.push(stopTime);
      }

      // Parse null wheelchair_accessible values
      if ( row.wheelchair_accessible === null ) {
        row.wheelchair_accessible = Trip.WHEELCHAIR_ACCESSIBLE_UNKNOWN;
      }

      // Build Trip
      let trip = new Trip(
        id,
        route,
        service,
        stopTimes,
        row.trip_short_name,
        row.direction_id,
        row.wheelchair_accessible,
        row.description
      );

      // Add Trip to Cache
      cache_tripsById.put(cacheKey, trip);

      // Return trip with callback
      return callback(null, trip);

    });

  });

};

/**
 * Get the Trip specified by the Trip short name that operates on the specified date
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} shortName Trip short name
 * @param {int} date Date Integer (yyyymmdd)
 * @param {function} callback Callback function
 * @param {Error} callback.error Database Query Error
 * @param {Trip} [callback.trip] The selected Trip
 */
function getTripByShortName(db, shortName, date, callback) {

  // Check Cache for Trip
  let cacheKey = db.id + "-" + shortName + "-" + date;
  let cache = cache_tripsByShortName.get(cacheKey);
  if ( cache !== null ) {
    return callback(null, cache);
  }

  // Get effective service ids
  _buildEffectiveServiceIDString(db, date, function(err, serviceIdString) {
    if ( err ) {
      return callback(err);
    }

    // Get Trip ID
    let select = "SELECT trip_id FROM gtfs_trips WHERE trip_short_name = '" +
      shortName + "' AND service_id IN " + serviceIdString + ";";

    // Query database
    db.get(select, function(err, result) {
      if ( err ) {
        return callback(err);
      }

      // No Trip Found
      if ( result === undefined ) {
        return callback(null, undefined);
      }

      // Get the Trip
      getTrip(db, result.trip_id, date, function(err, trip) {

        // Add Trip to Cache
        cache_tripsByShortName.put(cacheKey, trip);

        // Return Trip
        return callback(err, trip);

      });

    });

  });

}

/**
 * Find the Trip that leaves the specified origin Stop for the specified
 * destination Stop at the departure time specified by the DateTime
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} originId Origin Stop ID
 * @param {string} destinationId Destination Stop ID
 * @param {DateTime} departure DateTime of trip departure
 * @param {function} callback Callback function
 * @param {Error} callback.error Database Query Error
 * @param {Trip} [callback.trip] The selected Trip
 */
function getTripByDeparture(db, originId, destinationId, departure, callback) {

  // Check cache for trip
  let cacheKey = db.id + "-" + originId + "-" + destinationId + "-" + departure.getTimeSeconds() + "-" + departure.getDateInt();
  let cache = cache_tripsByDeparture.get(cacheKey);
  if ( cache !== null ) {
    return callback(null, cache);
  }

  // Check to make sure both origin and destination have IDs set
  if ( originId === "" || originId === undefined ||
      destinationId === "" || destinationId === undefined ) {
    return callback(
      new Error('Could not get Trip, origin and/or destination id not set')
    );
  }

  // ORIGINAL DEPARTURE DATE
  _getTripByDeparture(db, originId, destinationId, departure, function(err, trip) {
    if ( err ) {
      return callback(err);
    }

    // Trip Found...
    if ( trip !== undefined ) {
      cache_tripsByDeparture.put(cacheKey, trip);
      return callback(null, trip);
    }

    // PREVIOUS DEPARTURE DATE, +24 HOUR TIME
    let prev = DateTime.create(
      departure.getTimeSeconds() + 86400,
      departure.clone().deltaDays(-1).getDateInt()
    );

    _getTripByDeparture(db, originId, destinationId, prev, function(err, trip) {

      // Return results
      cache_tripsByDeparture.put(cacheKey, trip);
      return callback(err, trip);

    });

  });

}

/**
 * Get the Trip that matches the origin, destination and departure information
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} originId Origin Stop ID
 * @param {string} destinationId Destination Stop ID
 * @param {DateTime} departure Departure DateTime
 * @param {function} callback getTrip callback function(err, trip)
 * @private
 */
function _getTripByDeparture(db, originId, destinationId, departure, callback) {

  // Get Effective Services for departure date
  _buildEffectiveServiceIDString(db, departure.getDateInt(), function(err, serviceIdString) {
    if ( err ) {
      return callback(err);
    }

    // Find a matching trip
    _getMatchingTripId(db, originId, destinationId, departure, serviceIdString, function(err, tripId) {
      if ( err ) {
        return callback(err);
      }

      // --> No matching trip found
      if ( tripId === undefined ) {
        return callback(null, undefined);
      }

      // Build the matching trip...
      getTrip(db, tripId, departure.getDateInt(), function(err, trip) {
        if ( err ) {
          return callback(err);
        }

        // --> Return the matching trip
        return callback(err, trip);

      });

    });

  });

}

/**
 * Build the Effective Service ID String for SELECT query
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {int} date Date Integer (yyyymmdd)
 * @param {function} callback Callback function(err, serviceIdString)
 * @private
 */
function _buildEffectiveServiceIDString(db, date, callback) {

  // Query the Calendar for effective services
  CalendarTable.getServicesEffective(db, date, function(err, services) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // Build Service ID String
    let serviceIds = [];
    for ( let i = 0; i < services.length; i++ ) {
      serviceIds.push(services[i].id);
    }
    let serviceIdString = "('" + serviceIds.join("', '") + "')";

    // Return Service ID String
    return callback(null, serviceIdString);

  });

}

/**
 * Get the Trip ID of the trip matching the origin, destination, and departure information
 * @param {RightTrackDB} db The Right Track DB to query
 * @param {string} originId Origin Stop ID
 * @param {string} destinationId Destination Stop ID
 * @param {DateTime} departure DateTime of departure
 * @param {string} serviceIdString Effective Service ID string
 * @param {function} callback Callback function(err, tripId)
 * @private
 */
function _getMatchingTripId(db, originId, destinationId, departure, serviceIdString, callback) {

  // Find a matching trip in the gtfs_stop_times table
  let select = "SELECT trip_id FROM gtfs_trips " +
    "WHERE service_id IN " + serviceIdString + " " +
    "AND trip_id IN (" +
    "SELECT trip_id FROM gtfs_stop_times WHERE stop_id='" + destinationId + "' " +
    "AND trip_id IN (" +
    "SELECT trip_id FROM gtfs_stop_times " +
    "WHERE stop_id='" + originId + "' AND departure_time_seconds=" + departure.getTimeSeconds() +
    "));";

  // Query the database
  db.select(select, function(err, results) {

    // Database Query Error
    if ( err ) {
      return callback(err);
    }

    // No results found
    if ( results.length === 0 ) {
      return callback(null, undefined);
    }

    // Find the best match
    let found = false;
    let count = 0;
    for ( let j = 0; j < results.length; j++ ) {
      let row = results[j];

      // Get StopTimes for origin and destination
      StopTimesTable.getStopTimeByTripStop(db, row.trip_id, originId, departure.getDateInt(), function(orErr, originStopTime) {
        StopTimesTable.getStopTimeByTripStop(db, row.trip_id, destinationId, departure.getDateInt(), function(deErr, destinationStopTime) {
          if ( orErr ) {
            return callback(orErr);
          }
          if ( deErr ) {
            return callback(deErr);
          }

          // Check stop sequence
          // If origin comes before destination, use that trip
          if ( originStopTime.stopSequence <= destinationStopTime.stopSequence ) {
            found = true;
            return callback(null, row.trip_id);
          }

          // No match found
          count ++;
          if ( !found && count === results.length ) {
            return callback(null, undefined);
          }

        });
      });

    }

  });

}

// ==== SETUP CACHES ==== //
let cache_tripsById = new cache.Cache();
let cache_tripsByShortName = new cache.Cache();
let cache_tripsByDeparture = new cache.Cache();

/**
 * Clear the TripsTable caches
 * @private
 */
function clearCache() {
  cache_tripsById.clear();
  cache_tripsByShortName.clear();
  cache_tripsByDeparture.clear();
}

// Export Functions
module.exports = {
  getTrip: getTrip,
  getTripByShortName: getTripByShortName,
  getTripByDeparture: getTripByDeparture,
  clearCache: clearCache
};<|endoftext|>


<commit_before>'use strict';

angular.module('argus.directives.charts.chart', [])
.directive('agChart', ['Metrics', 'Annotations', 'ChartRenderingService', 'ChartDataProcessingService', 'ChartOptionService', 'DateHandlerService', 'CONFIG', 'VIEWELEMENT', '$compile',
function(Metrics, Annotations, ChartRenderingService, ChartDataProcessingService, ChartOptionService, DateHandlerService, CONFIG, VIEWELEMENT, $compile) {
    var chartNameIndex = 1;

    function compileLineChart(scope, newChartId, series, dateConfig) {
        // empty any previous content
        $("#" + newChartId).empty();

        // create a new scope to pass to compiled line-chart directive
        var lineChartScope = scope.$new(false);     // true will set isolate scope, false = inherit

        // assign items to new $scope
        lineChartScope.chartConfig = {
            chartId: newChartId,
            chartTitle: scope.options.title || ''   // title is an attribute of <option>, and could be undefined
        };
        lineChartScope.series = series;
        lineChartScope.dateConfig = dateConfig;

        // give each series an unique ID if it has data
        for (var i = 0; i < series.length; i++) {
            // use graphClassName to bind all the graph element of a metric together
            lineChartScope.series[i].graphClassName = newChartId + "_graph" + (i + 1);
        }

        scope.seriesDataLoaded = true; //

        // append, compile, & attach new scope to line-chart directive
        angular.element("#" + newChartId).append( $compile('<line-chart chartConfig="chartConfig" series="series" dateconfig="dateConfig"></line-chart>')(lineChartScope) );
    }

    function setupAnnotations(scope, newChartId, series, updatedAnnotationList, dateConfig) {

        if (updatedAnnotationList.length === 0) {
            // no annotations list, continue to render chart as normal
            compileLineChart(scope, newChartId, series, dateConfig);
        } else {
            // check annotations & add to series data for line-chart
            for (var i=0; i < updatedAnnotationList.length; i++) {
                Annotations.query({expression: updatedAnnotationList[i]}, function (data) {
                    if (data && data.length > 0) {
                        var forName = ChartDataProcessingService.createSeriesName(data[0]);
                        var flagSeries = ChartDataProcessingService.copyFlagSeries(data);
                        flagSeries.linkedTo = forName;

                        // add flagSeries if any data exists
                        //TODO: handle undefined flagSeries situation
                        series[0].flagSeries = (flagSeries) ? flagSeries: null;
                    }

                    // append, compile, & attach new scope to line-chart directive
                    compileLineChart(scope, newChartId, series, dateConfig);

                }, function (error) {
                    console.log( 'no data found', error.data.message );
                    // append, compile, & attach new scope to line-chart directive
                    compileLineChart(scope, newChartId, series, dateConfig);
                });
            }
        }
    }

    // TODO: below functions 'should' be refactored to the chart services.
    function setupChart(scope, element, attributes, controls) {
        // remove/clear any previous chart rendering from DOM
        element.empty();
        // generate a new chart ID, set css options for main chart container
        var newChartId = 'element_' + VIEWELEMENT.chart + chartNameIndex++;
        var smallChart = !!attributes.smallchart;
        var chartType = attributes.type ? attributes.type : 'LINE';
        var cssOpts = ( smallChart ) ? 'smallChart' : '';

        // set the charts container for rendering
        ChartRenderingService.setChartContainer(element, newChartId, cssOpts);

        var data = {
            metrics: scope.metrics,
            annotations: scope.annotations,
            options: scope.options
        };

        // get start and end time for the charts as well as whether GMT/UTC scale is used or not
        var dateConfig = {};
        var GMTon = false;
        for (var i = 0; i < controls.length; i++) {
            if (controls[i].type === "agDate") {
                var timeValue = controls[i].value;
                if (controls[i].name === "start") {
                    dateConfig.startTime = DateHandlerService.timeProcessingHelper(timeValue);
                    GMTon = GMTon || DateHandlerService.GMTVerifier(timeValue);
                } else if (controls[i].name === "end"){
                    dateConfig.endTime = DateHandlerService.timeProcessingHelper(timeValue);
                    GMTon = GMTon || DateHandlerService.GMTVerifier(timeValue);
                }
            }
        }
        dateConfig.gmt = GMTon;

        // process data for: metrics, annotations, options
        var processedData = ChartDataProcessingService.processMetricData(data, event, controls);

        // re-assign each list for: metrics, annotations, options
        // TODO: updatedMetricList is not defined sometimes
        var updatedMetricList = processedData.updatedMetricList;
        var updatedAnnotationList = processedData.updatedAnnotationList;
        var updatedOptionList = processedData.updatedOptionList;

        // define series first, then build list for each metric expression
        var series = [];
        var metricCount = updatedMetricList.length;

        scope.seriesDataLoaded = false; //used for load spinner
        angular.element("#" + newChartId).append( $compile('

')(scope) );

        for (var i = 0; i < updatedMetricList.length; i++) {
            var metricItem = updatedMetricList[i];
            // make api call to get data for each metric item
            Metrics.query({expression: metricItem.expression}, function (data) {
                var tempSeries;
                if (data && data.length > 0) {

                    // check to update statusIndicator with correct status color
                    if (smallChart) {
                        // get the last data point
                        var lastStatusVal = ChartDataProcessingService.getLastDataPoint(data[0].datapoints);

                        // update status indicator
                        ChartRenderingService.updateIndicatorStatus(attributes, lastStatusVal);

                        // add 'smallChart' flag to scope
                        scope.chartOptions = {smallChart: smallChart};
                    }

                    // metric item attributes are assigned to the data (i.e. name, color, etc.)
                    tempSeries = ChartDataProcessingService.copySeriesDataNSetOptions(data, metricItem);

                } else {
                    // growl.info('No data found for the metric expression: ' + JSON.stringify(metricItem.expression));
                    console.log('Empty result returned for the metric expression');
                    tempSeries = [{
                        noData: true,
                        errorMessage: 'Empty result returned for the metric expression',
                        name: JSON.stringify(metricItem.expression).slice(1, -1),
                        color: 'Maroon'
                    }];
                }
                Array.prototype.push.apply(series, tempSeries);
                // decrement metric count each time an expression is added to the series.
                metricCount = metricCount - 1;

                if (metricCount === 0) {
                    // check for Annotations
                    setupAnnotations(scope, newChartId, series, updatedAnnotationList, dateConfig);
                }

            }, function (error) {
                // growl.error(data.message);
                console.log('Metric expression does not exist in database');
                var tempSeries = [{
                    invalidMetric: true,
                    errorMessage: error.statusText + '(' + error.status + ') - ' + error.data.message.substring(0, 31),
                    name: error.config.params.expression,
                    color: 'Black'
                }];
                Array.prototype.push.apply(series, tempSeries);

                metricCount = metricCount - 1;

                if (metricCount === 0) {
                    // display chart with series data and populate annotations
                    // bindDataToChart(newChartId, series, updatedAnnotationList);
                    setupAnnotations(scope, newChartId, series, updatedAnnotationList, dateConfig);
                }
            });
        }
    }

    return {
        restrict: 'E',
        transclude: true,
        scope: {},
        require: '^agDashboard',
        controller: 'ViewElements',
        template: '

',
        compile: function (element, attrs, transclude) {
            return {
                post: function postLink(scope, element, attributes, dashboardCtrl, transcludeFn) {
                    scope.$on(dashboardCtrl.getSubmitBtnEventName(), function(event, controls) {
                        setupChart(scope, element, attributes, controls);
                    });
                }
            };
        }
    };
}]);
<commit_msg>fix chart not showing chart title<commit_after>'use strict';

angular.module('argus.directives.charts.chart', [])
.directive('agChart', ['Metrics', 'Annotations', 'ChartRenderingService', 'ChartDataProcessingService', 'ChartOptionService', 'DateHandlerService', 'CONFIG', 'VIEWELEMENT', '$compile',
function(Metrics, Annotations, ChartRenderingService, ChartDataProcessingService, ChartOptionService, DateHandlerService, CONFIG, VIEWELEMENT, $compile) {
    var chartNameIndex = 1;

    function compileLineChart(scope, newChartId, series, dateConfig) {
        // empty any previous content
        $("#" + newChartId).empty();

        // create a new scope to pass to compiled line-chart directive
        var lineChartScope = scope.$new(false);     // true will set isolate scope, false = inherit

        // assign items to new $scope
        lineChartScope.chartConfig = {
            chartId: newChartId,
            chartTitle: scope.options['title.text'] || ''   // title is an attribute of <option>, and could be undefined
        };
        lineChartScope.series = series;
        lineChartScope.dateConfig = dateConfig;

        // give each series an unique ID if it has data
        for (var i = 0; i < series.length; i++) {
            // use graphClassName to bind all the graph element of a metric together
            lineChartScope.series[i].graphClassName = newChartId + "_graph" + (i + 1);
        }

        scope.seriesDataLoaded = true; //

        // append, compile, & attach new scope to line-chart directive
        angular.element("#" + newChartId).append( $compile('<line-chart chartConfig="chartConfig" series="series" dateconfig="dateConfig"></line-chart>')(lineChartScope) );
    }

    function setupAnnotations(scope, newChartId, series, updatedAnnotationList, dateConfig) {

        if (updatedAnnotationList.length === 0) {
            // no annotations list, continue to render chart as normal
            compileLineChart(scope, newChartId, series, dateConfig);
        } else {
            // check annotations & add to series data for line-chart
            for (var i=0; i < updatedAnnotationList.length; i++) {
                Annotations.query({expression: updatedAnnotationList[i]}, function (data) {
                    if (data && data.length > 0) {
                        var forName = ChartDataProcessingService.createSeriesName(data[0]);
                        var flagSeries = ChartDataProcessingService.copyFlagSeries(data);
                        flagSeries.linkedTo = forName;

                        // add flagSeries if any data exists
                        //TODO: handle undefined flagSeries situation
                        series[0].flagSeries = (flagSeries) ? flagSeries: null;
                    }

                    // append, compile, & attach new scope to line-chart directive
                    compileLineChart(scope, newChartId, series, dateConfig);

                }, function (error) {
                    console.log( 'no data found', error.data.message );
                    // append, compile, & attach new scope to line-chart directive
                    compileLineChart(scope, newChartId, series, dateConfig);
                });
            }
        }
    }

    // TODO: below functions 'should' be refactored to the chart services.
    function setupChart(scope, element, attributes, controls) {
        // remove/clear any previous chart rendering from DOM
        element.empty();
        // generate a new chart ID, set css options for main chart container
        var newChartId = 'element_' + VIEWELEMENT.chart + chartNameIndex++;
        var smallChart = !!attributes.smallchart;
        var chartType = attributes.type ? attributes.type : 'LINE';
        var cssOpts = ( smallChart ) ? 'smallChart' : '';

        // set the charts container for rendering
        ChartRenderingService.setChartContainer(element, newChartId, cssOpts);

        var data = {
            metrics: scope.metrics,
            annotations: scope.annotations,
            options: scope.options
        };

        // get start and end time for the charts as well as whether GMT/UTC scale is used or not
        var dateConfig = {};
        var GMTon = false;
        for (var i = 0; i < controls.length; i++) {
            if (controls[i].type === "agDate") {
                var timeValue = controls[i].value;
                if (controls[i].name === "start") {
                    dateConfig.startTime = DateHandlerService.timeProcessingHelper(timeValue);
                    GMTon = GMTon || DateHandlerService.GMTVerifier(timeValue);
                } else if (controls[i].name === "end"){
                    dateConfig.endTime = DateHandlerService.timeProcessingHelper(timeValue);
                    GMTon = GMTon || DateHandlerService.GMTVerifier(timeValue);
                }
            }
        }
        dateConfig.gmt = GMTon;

        // process data for: metrics, annotations, options
        var processedData = ChartDataProcessingService.processMetricData(data, event, controls);

        // re-assign each list for: metrics, annotations, options
        // TODO: updatedMetricList is not defined sometimes
        var updatedMetricList = processedData.updatedMetricList;
        var updatedAnnotationList = processedData.updatedAnnotationList;
        var updatedOptionList = processedData.updatedOptionList;

        // define series first, then build list for each metric expression
        var series = [];
        var metricCount = updatedMetricList.length;

        scope.seriesDataLoaded = false; //used for load spinner
        angular.element("#" + newChartId).append( $compile('

')(scope) );

        for (var i = 0; i < updatedMetricList.length; i++) {
            var metricItem = updatedMetricList[i];
            // make api call to get data for each metric item
            Metrics.query({expression: metricItem.expression}, function (data) {
                var tempSeries;
                if (data && data.length > 0) {

                    // check to update statusIndicator with correct status color
                    if (smallChart) {
                        // get the last data point
                        var lastStatusVal = ChartDataProcessingService.getLastDataPoint(data[0].datapoints);

                        // update status indicator
                        ChartRenderingService.updateIndicatorStatus(attributes, lastStatusVal);

                        // add 'smallChart' flag to scope
                        scope.chartOptions = {smallChart: smallChart};
                    }

                    // metric item attributes are assigned to the data (i.e. name, color, etc.)
                    tempSeries = ChartDataProcessingService.copySeriesDataNSetOptions(data, metricItem);

                } else {
                    // growl.info('No data found for the metric expression: ' + JSON.stringify(metricItem.expression));
                    console.log('Empty result returned for the metric expression');
                    tempSeries = [{
                        noData: true,
                        errorMessage: 'Empty result returned for the metric expression',
                        name: JSON.stringify(metricItem.expression).slice(1, -1),
                        color: 'Maroon'
                    }];
                }
                Array.prototype.push.apply(series, tempSeries);
                // decrement metric count each time an expression is added to the series.
                metricCount = metricCount - 1;

                if (metricCount === 0) {
                    // check for Annotations
                    setupAnnotations(scope, newChartId, series, updatedAnnotationList, dateConfig);
                }

            }, function (error) {
                // growl.error(data.message);
                console.log('Metric expression does not exist in database');
                var tempSeries = [{
                    invalidMetric: true,
                    errorMessage: error.statusText + '(' + error.status + ') - ' + error.data.message.substring(0, 31),
                    name: error.config.params.expression,
                    color: 'Black'
                }];
                Array.prototype.push.apply(series, tempSeries);

                metricCount = metricCount - 1;

                if (metricCount === 0) {
                    // display chart with series data and populate annotations
                    // bindDataToChart(newChartId, series, updatedAnnotationList);
                    setupAnnotations(scope, newChartId, series, updatedAnnotationList, dateConfig);
                }
            });
        }
    }

    return {
        restrict: 'E',
        transclude: true,
        scope: {},
        require: '^agDashboard',
        controller: 'ViewElements',
        template: '

',
        compile: function (element, attrs, transclude) {
            return {
                post: function postLink(scope, element, attributes, dashboardCtrl, transcludeFn) {
                    scope.$on(dashboardCtrl.getSubmitBtnEventName(), function(event, controls) {
                        setupChart(scope, element, attributes, controls);
                    });
                }
            };
        }
    };
}]);
<|endoftext|>


<commit_before>include('io');

function createProcess(args) {
    var command = Array.map(args, String);
    return java.lang.Runtime.getRuntime().exec(command);
}

/**
 * executes a given command and returns the
 * standard output.  If the exit status is non-zero,
 * throws an Error.
 * @param {String} command and optional arguments as individual strings
 * @returns String the standard output of the command
 */
exports.command = function() {
    var process = createProcess(arguments);
    var status = process.waitFor();
    if (status != 0) {
        var error = new TextStream(new Stream(process.getErrorStream())).read();
        throw new Error("(" + status + ") " + error);
    }
    return new TextStream(new Stream(process.getInputStream())).read();
};

/**
 * executes a given command, attached to this process's
 * output and error streams, and returns the exit status.
 * @param {String} command and optional arguments as individual strings
 * @returns Number exit status
 */
exports.system = function() {
    var process = createProcess(arguments);
    try {
        return process.waitFor();
    } finally {
        new TextStream(new Stream(process.getInputStream())).copy(system.stdout);
        new TextStream(new Stream(process.getErrorStream())).copy(system.stderr);
    }
};
/**
 * executes a given command quietly and returns
 * the exit status.
 * @param {String} command and optional arguments as individual strings
 * @returns Number exit status
 */
exports.status = function() {
    var process = createProcess(arguments);
    return process.waitFor();
};

<commit_msg>Use dedicated threads to pump streams in ringo/subprocess.<commit_after>include('io');

function createProcess(args) {
    var command = Array.map(args, String);
    return java.lang.Runtime.getRuntime().exec(command);
}

function connect(process, output, errput) {
    spawn(function() {
        new TextStream(new Stream(process.inputStream)).copy(output);
    }).get();
    spawn(function() {
        new TextStream(new Stream(process.errorStream)).copy(errput);
    }).get();
}

/**
 * executes a given command and returns the
 * standard output.  If the exit status is non-zero,
 * throws an Error.
 * @param {String} command and optional arguments as individual strings
 * @returns String the standard output of the command
 */
exports.command = function() {
    var process = createProcess(arguments);
    var output = new TextStream(new MemoryStream());
    var error = new TextStream(new MemoryStream());
    connect(process, output, error);
    var status = process.waitFor();
    if (status != 0) {
        throw new Error("(" + status + ") " + error.content);
    }
    return output.content;
};

/**
 * executes a given command, attached to this process's
 * output and error streams, and returns the exit status.
 * @param {String} command and optional arguments as individual strings
 * @returns Number exit status
 */
exports.system = function() {
    var process = createProcess(arguments);
    connect(process, system.stdout, system.stderr);
    return process.waitFor();
};

/**
 * executes a given command quietly and returns
 * the exit status.
 * @param {String} command and optional arguments as individual strings
 * @returns Number exit status
 */
exports.status = function() {
    var process = createProcess(arguments);
    connect(process, dummyStream(), dummyStream());
    return process.waitFor();
};

function dummyStream() {
    return {
        writable: function() true,
        readable: function() false,
        seekable: function() false,
        write: function() this,
        flush: function() this,
        close: function() this
    }
}

<|endoftext|>


<commit_before>import _ from 'lodash';
import { qsString } from '../util/index';

var taginfo = {},
    endpoint = 'https://taginfo.openstreetmap.org/api/4/',
    tag_sorts = {
        point: 'count_nodes',
        vertex: 'count_nodes',
        area: 'count_ways',
        line: 'count_ways'
    },
    tag_filters = {
        point: 'nodes',
        vertex: 'nodes',
        area: 'ways',
        line: 'ways'
    };

function sets(parameters, n, o) {
    if (parameters.geometry && o[parameters.geometry]) {
        parameters[n] = o[parameters.geometry];
    }
    return parameters;
}

function setFilter(parameters) {
    return sets(parameters, 'filter', tag_filters);
}

function setSort(parameters) {
    return sets(parameters, 'sortname', tag_sorts);
}

function clean(parameters) {
    return _.omit(parameters, 'geometry', 'debounce');
}

function filterKeys(type) {
    var count_type = type ? 'count_' + type : 'count_all';
    return function(d) {
        return parseFloat(d[count_type]) > 2500 || d.in_wiki;
    };
}

function filterMultikeys() {
    return function(d) {
        return (d.key.match(/:/g) || []).length === 1;  // exactly one ':'
    };
}

function filterValues(allowUpperCase) {
    return function(d) {
        if (d.value.match(/[;,]/) !== null) return false;  // exclude some punctuation
        if (!allowUpperCase && d.value.match(/[A-Z*]/) !== null) return false;  // exclude uppercase letters
        return parseFloat(d.fraction) > 0.0 || d.in_wiki;
    };
}

function valKey(d) {
    return {
        value: d.key,
        title: d.key
    };
}

function valKeyDescription(d) {
    return {
        value: d.value,
        title: d.description || d.value
    };
}

// sort keys with ':' lower than keys without ':'
function sortKeys(a, b) {
    return (a.key.indexOf(':') === -1 && b.key.indexOf(':') !== -1) ? -1
        : (a.key.indexOf(':') !== -1 && b.key.indexOf(':') === -1) ? 1
        : 0;
}

var debounced = _.debounce(d3.json, 100, true);

function request(url, debounce, callback) {
    var cache = taginfo.cache;

    if (cache[url]) {
        callback(null, cache[url]);
    } else if (debounce) {
        debounced(url, done);
    } else {
        d3.json(url, done);
    }

    function done(err, data) {
        if (!err) cache[url] = data;
        callback(err, data);
    }
}

export function init() {
    taginfo.keys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 10,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterKeys(parameters.filter);
                callback(null, d.data.filter(f).sort(sortKeys).map(valKey));
            });
    };

    taginfo.multikeys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterMultikeys();
                callback(null, d.data.filter(f).map(valKey));
            });
    };

    taginfo.values = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(setFilter(parameters)));
        request(endpoint + 'key/values?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterValues(parameters.key === 'cycle_network' || parameters.key === 'network');
                callback(null, d.data.filter(f).map(valKeyDescription));
            });
    };

    taginfo.docs = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));

        var path = 'key/wiki_pages?';
        if (parameters.value) path = 'tag/wiki_pages?';
        else if (parameters.rtype) path = 'relation/wiki_pages?';

        request(endpoint + path + qsString(parameters), debounce, function(err, d) {
            if (err) return callback(err);
            callback(null, parameters.rtype ? d : d.data);
        });
    };

    taginfo.endpoint = function(_) {
        if (!arguments.length) return endpoint;
        endpoint = _;
        return taginfo;
    };

    taginfo.reset = function() {
        taginfo.cache = {};
        return taginfo;
    };

    if (!taginfo.cache) {
        taginfo.reset();
    }

    return taginfo;
}
<commit_msg>Removed workaround for joto/taginfo#179<commit_after>import _ from 'lodash';
import { qsString } from '../util/index';

var taginfo = {},
    endpoint = 'https://taginfo.openstreetmap.org/api/4/',
    tag_sorts = {
        point: 'count_nodes',
        vertex: 'count_nodes',
        area: 'count_ways',
        line: 'count_ways'
    },
    tag_filters = {
        point: 'nodes',
        vertex: 'nodes',
        area: 'ways',
        line: 'ways'
    };

function sets(parameters, n, o) {
    if (parameters.geometry && o[parameters.geometry]) {
        parameters[n] = o[parameters.geometry];
    }
    return parameters;
}

function setFilter(parameters) {
    return sets(parameters, 'filter', tag_filters);
}

function setSort(parameters) {
    return sets(parameters, 'sortname', tag_sorts);
}

function clean(parameters) {
    return _.omit(parameters, 'geometry', 'debounce');
}

function filterKeys(type) {
    var count_type = type ? 'count_' + type : 'count_all';
    return function(d) {
        return parseFloat(d[count_type]) > 2500 || d.in_wiki;
    };
}

function filterMultikeys() {
    return function(d) {
        return (d.key.match(/:/g) || []).length === 1;  // exactly one ':'
    };
}

function filterValues(allowUpperCase) {
    return function(d) {
        if (d.value.match(/[;,]/) !== null) return false;  // exclude some punctuation
        if (!allowUpperCase && d.value.match(/[A-Z*]/) !== null) return false;  // exclude uppercase letters
        return parseFloat(d.fraction) > 0.0 || d.in_wiki;
    };
}

function valKey(d) {
    return {
        value: d.key,
        title: d.key
    };
}

function valKeyDescription(d) {
    return {
        value: d.value,
        title: d.description || d.value
    };
}

// sort keys with ':' lower than keys without ':'
function sortKeys(a, b) {
    return (a.key.indexOf(':') === -1 && b.key.indexOf(':') !== -1) ? -1
        : (a.key.indexOf(':') !== -1 && b.key.indexOf(':') === -1) ? 1
        : 0;
}

var debounced = _.debounce(d3.json, 100, true);

function request(url, debounce, callback) {
    var cache = taginfo.cache;

    if (cache[url]) {
        callback(null, cache[url]);
    } else if (debounce) {
        debounced(url, done);
    } else {
        d3.json(url, done);
    }

    function done(err, data) {
        if (!err) cache[url] = data;
        callback(err, data);
    }
}

export function init() {
    taginfo.keys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 10,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterKeys(parameters.filter);
                callback(null, d.data.filter(f).sort(sortKeys).map(valKey));
            });
    };

    taginfo.multikeys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterMultikeys();
                callback(null, d.data.filter(f).map(valKey));
            });
    };

    taginfo.values = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(setFilter(parameters)));
        request(endpoint + 'key/values?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterValues(parameters.key === 'cycle_network' || parameters.key === 'network');
                callback(null, d.data.filter(f).map(valKeyDescription));
            });
    };

    taginfo.docs = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));

        var path = 'key/wiki_pages?';
        if (parameters.value) path = 'tag/wiki_pages?';
        else if (parameters.rtype) path = 'relation/wiki_pages?';

        request(endpoint + path + qsString(parameters), debounce, function(err, d) {
            if (err) return callback(err);
            callback(null, d.data);
        });
    };

    taginfo.endpoint = function(_) {
        if (!arguments.length) return endpoint;
        endpoint = _;
        return taginfo;
    };

    taginfo.reset = function() {
        taginfo.cache = {};
        return taginfo;
    };

    if (!taginfo.cache) {
        taginfo.reset();
    }

    return taginfo;
}
<|endoftext|>


<commit_before>var isString = require('lodash.isstring');
var isObject = require('lodash.isobject');
var strip = require('strip-loader');
var Clean = require('clean-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var path = require('path');
var webpack = require('webpack');

var filenameWithoutExt = require('./filenameWithoutExt.js');
var getPackage = require('./getPackage.js');
var WEBPACK_URL = '/';

var plugins = [
  // Extract all CSS to appropriately named css files
  new ExtractTextPlugin('[name].css', { allChunks: true }),

  // Minify
  new webpack.optimize.UglifyJsPlugin({
    sourceMap: false,

    // This matches nothing and thus removes all comments
    comments: /.^/,
    screw_ie8: true,

    // Options: http://lisperator.net/uglifyjs/compress
    compress: { warnings: false },
  }),

  // Dedupe
  new webpack.optimize.DedupePlugin(),

  // Don't reload on errors
  new webpack.NoErrorsPlugin(),

  // Define magic globals
  new webpack.DefinePlugin({
    __DEV__: false,
    __VERSION__: getPackage().version,
  }),
];

module.exports = function(spec) {
  var entry = {};

  if (isString(spec.in)) {
    entry[filenameWithoutExt(spec.in)] = spec.in;
  } else if (isObject(spec.in)) {
    entry = spec.in;
  }

  if (spec.cleanOnBuild) {
    plugins.unshift(new Clean(['public']));
  }

  return {

    entry: entry,

    output: {
      path: path.resolve(__dirname, spec.out || 'public/'),
      filename: '[name].js',
      publicPath: WEBPACK_URL,
    },

    /**
     * Note: css-lader is passed the no-restructuring param b/c it can sometimes
     * cause issues where selectors will be reordered and no longer get applied.
     */
    module: {
      loaders: [
        // See .babelrc for Babel config. Also note that all calls to console.log
        // and debug will be stripped from the code
        { test: /\.js?$/, loaders: [strip.loader('debug', 'console.log'), 'babel'], exclude: /node_modules/ },

        { test: /\.styl$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2!autoprefixer!stylus') },
        { test: /\.(png|jpg|gif)$/, loaders: ['file?name=[name].[ext]'] },
        { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loaders: ['url?limit=10000&minetype=application/font-woff'] },
        { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loaders: ['file'] },
      ],
    },

    plugins: plugins,

  };
};
<commit_msg>Add json support. Use file-loader for ico files<commit_after>var isString = require('lodash.isstring');
var isObject = require('lodash.isobject');
var strip = require('strip-loader');
var Clean = require('clean-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var path = require('path');
var webpack = require('webpack');

var filenameWithoutExt = require('./filenameWithoutExt.js');
var getPackage = require('./getPackage.js');
var WEBPACK_URL = '/';

var plugins = [
  // Extract all CSS to appropriately named css files
  new ExtractTextPlugin('[name].css', { allChunks: true }),

  // Minify
  new webpack.optimize.UglifyJsPlugin({
    sourceMap: false,

    // This matches nothing and thus removes all comments
    comments: /.^/,
    screw_ie8: true,

    // Options: http://lisperator.net/uglifyjs/compress
    compress: { warnings: false },
  }),

  // Dedupe
  new webpack.optimize.DedupePlugin(),

  // Don't reload on errors
  new webpack.NoErrorsPlugin(),

  // Define magic globals
  new webpack.DefinePlugin({
    __DEV__: false,
    __VERSION__: getPackage().version,
  }),
];

module.exports = function(spec) {
  var entry = {};

  if (isString(spec.in)) {
    entry[filenameWithoutExt(spec.in)] = spec.in;
  } else if (isObject(spec.in)) {
    entry = spec.in;
  }

  if (spec.cleanOnBuild) {
    plugins.unshift(new Clean(['public']));
  }

  return {

    entry: entry,

    output: {
      path: path.resolve(__dirname, spec.out || 'public/'),
      filename: '[name].js',
      publicPath: WEBPACK_URL,
    },

    /**
     * Note: css-lader is passed the no-restructuring param b/c it can sometimes
     * cause issues where selectors will be reordered and no longer get applied.
     */
    module: {
      loaders: [
        // See .babelrc for Babel config. Also note that all calls to console.log
        // and debug will be stripped from the code
        { test: /\.js?$/, loaders: [strip.loader('debug', 'console.log'), 'babel'], exclude: /node_modules/ },

        { test: /\.json$/, loaders: ['json'] },
        { test: /\.styl$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2!autoprefixer!stylus') },
        { test: /\.(png|jpg|gif)$/, loaders: ['file?name=[name].[ext]'] },
        { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loaders: ['url?limit=10000&minetype=application/font-woff'] },
        { test: /\.(ttf|eot|svg|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loaders: ['file'] },
      ],
    },

    plugins: plugins,

  };
};
<|endoftext|>


<commit_before>'use strict';
/* global d3:false */

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');

    if(this.graphInfo._context.displayModeBar === 'hover') {
        this.element.className = 'modebar modebar--hover';
    } else {
        this.element.className = 'modebar';
    }

    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config()[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    if (this.graphInfo._context.displaylogo) {
        this.element.appendChild(this.getLogo());
    }

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'modebar-group';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var _this = this,
        button = document.createElement('a');

    button.setAttribute('rel', 'tooltip');
    button.className = 'modebar-btn';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('data-title', config.title);
    button.setAttribute('data-gravity', config.gravity || 'n');
    button.addEventListener('click', function () {
            config.click.apply(_this, arguments);
        });

    button.appendChild(this.createIcon(this.Plotly.Icons[config.icon]));

    return button;
};

/**
 * Add an icon to a button
 * @Param {object} thisIcon
 * @Param {number} thisIcon.width
 * @Param {string} thisIcon.path
 * @Return {HTMLelement}
 */
ModeBar.prototype.createIcon = function (thisIcon) {
    var iconDef = this.Plotly.Icons,
        iconHeight = iconDef.ascent - iconDef.descent,
        svgNS = 'http://www.w3.org/2000/svg',
        icon = document.createElementNS(svgNS, 'svg'),
        path = document.createElementNS(svgNS, 'path');

    icon.setAttribute('height', '1em');
    icon.setAttribute('width', (thisIcon.width / iconHeight)+'em');
    icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));

    path.setAttribute('d', thisIcon.path);
    path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + iconDef.ascent + ')');
    icon.appendChild(path);

    return icon;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        d3.select(button).classed('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * @return {HTMLDivElement} The logo image wrapped in a group
 */
ModeBar.prototype.getLogo = function(){
    var group = this.createGroup(),
        a = document.createElement('a');

    a.href = 'https://plot.ly/';
    a.target = '_blank';
    a.setAttribute('data-title', 'Produced with Plotly');
    a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';

    a.appendChild(this.createIcon(this.Plotly.Icons.plotlylogo));

    group.appendChild(a);
    return group;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCartesian = function(ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        graphInfo = this.graphInfo,
        fullLayout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        aobj = {};

    if(astr === 'zoom') {
        var mag = (val === 'in') ? 0.5 : 2,
            r0 = (1 + mag) / 2,
            r1 = (1 - mag) / 2,
            axList = Plotly.Axes.list(graphInfo, null, true),
            i,
            axName,
            initialRange;

        for(i = 0; i < axList.length; i++) {
            if(!axList[i].fixedrange) {
                axName = axList[i]._name;
                if(val === 'auto') aobj[axName + '.autorange'] = true;
                else {
                    initialRange = axList[i].range;
                    aobj[axName + '.range'] = [
                        r0 * initialRange[0] + r1 * initialRange[1],
                        r0 * initialRange[1] + r1 * initialRange[0]
                    ];
                }
            }
        }
    } else {
        // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
        if (astr==='hovermode' && (val==='x' || val==='y')) {
            val = fullLayout._isHoriz ? 'y' : 'x';
            button.setAttribute('data-val', val);
        }

        aobj[astr] = val;
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                fullLayout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
            Plotly.Fx.supplyLayoutDefaults(graphInfo.layout, fullLayout,
                graphInfo._fullData);
        }
    });
};

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 */
ModeBar.prototype.handleHover3d = function(ev) {
    var button = ev.currentTarget,
        val = JSON.parse(button.getAttribute('data-val')) || false,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'),
        layoutUpdate = {},

        // initialize 'current spike' object to be stored in the DOM
        currentSpikes = {},
        axes = ['xaxis', 'yaxis', 'zaxis'],
        spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor'];

    var i, sceneId, sceneLayout, sceneSpikes;
    var j, axis, axisSpikes;
    var k, spikeAttr;

    if (val) {
        layoutUpdate = val;
        button.setAttribute('data-val', JSON.stringify(null));
    }
    else {
        layoutUpdate = {'allaxes.showspikes': false};

        for (i = 0;  i < sceneIds.length; i++) {
            sceneId = sceneIds[i];
            sceneLayout = fullLayout[sceneId];
            sceneSpikes = currentSpikes[sceneId] = {};

            // copy all the current spike attrs
            for (j = 0; j < 3; j++) {
                axis = axes[j];
                axisSpikes = sceneSpikes[axis] = {};
                for (k = 0; k < spikeAttrs.length; k++) {
                    spikeAttr = spikeAttrs[k];
                    axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr];
                }
            }
        }

        button.setAttribute('data-val', JSON.stringify(currentSpikes));
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });

};

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 */
ModeBar.prototype.handleDrag3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'),
        layoutUpdate = {};

    layoutUpdate[attr] = val;

    var i, scene, camera;

    for (i = 0;  i < sceneIds.length; i++) {
        scene = fullLayout[sceneIds[i]]._scene;
        camera = scene.camera;
        if (camera) {

            if (val === 'orbital') {
                camera.mode = 'orbital';
                camera.keyBindingMode = 'rotate';

            } else if (val === 'turntable') {
                camera.up = [0, 0, 1];
                camera.mode = 'turntable';
                camera.keyBindingMode = 'rotate';

            } else {

                // none rotation modes
                camera.keyBindingMode = val;
            }
        }
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCamera3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        Plotly = this.Plotly,
        layout = this.graphInfo.layout,
        fullLayout = this.graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');

    var i, sceneId, sceneLayout, scene, cameraposition;

    for (i = 0;  i < sceneIds.length; i++) {
        sceneId = sceneIds[i];
        sceneLayout = layout[sceneId];
        scene = fullLayout[sceneId]._scene;

        if (attr === 'resetDefault') scene.setCameraToDefault();
        else if (attr === 'resetLastSave') {
            cameraposition = sceneLayout ? sceneLayout.cameraposition : false;
            if (cameraposition) scene.setCameraPosition(cameraposition);
            else scene.setCameraToDefault();
        }
    }

    /* TODO have a sceneLastTouched in _fullLayout to only
     * update the camera of the scene last touched by the user
     */
};

ModeBar.prototype.cleanup = function(){
    this.element.innerHTML = '';
    var modebarParent = this.element.parentNode;
    if (modebarParent) modebarParent.removeChild(this.element);
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = function config() {
    return {
        zoom2d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleCartesian
        },
        pan2d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleCartesian
        },
        zoomIn2d: {
            title: 'Zoom in',
            attr: 'zoom',
            val: 'in',
            icon: 'zoom_plus',
            click: this.handleCartesian
        },
        zoomOut2d: {
            title: 'Zoom out',
            attr: 'zoom',
            val: 'out',
            icon: 'zoom_minus',
            click: this.handleCartesian
        },
        autoScale2d: {
            title: 'Autoscale',
            attr: 'zoom',
            val: 'auto',
            icon: 'autoscale',
            click: this.handleCartesian
        },
        hoverClosest2d: {
            title: 'Show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleCartesian
        },
        hoverCompare2d: {
            title: 'Compare data on hover',
            attr: 'hovermode',
            val: this.graphInfo._fullLayout._isHoriz ? 'y' : 'x',
            icon: 'tooltip_compare',
            gravity: 'ne',
            click: this.handleCartesian
        },
        zoom3d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleDrag3d
        },
        pan3d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleDrag3d
        },
        orbitalRotation: {
            title: 'orbital rotation',
            attr: 'dragmode',
            val: 'orbital',
            icon: 'undo',
            click: this.handleDrag3d
        },
        tableRotation: {
            title: 'turn table rotation',
            attr: 'dragmode',
            val: 'turntable',
            icon: 'undo',
            click: this.handleDrag3d
        },
        resetCameraDefault3d: {
            title: 'Reset camera to default',
            attr: 'resetDefault',
            icon: 'home',
            click: this.handleCamera3d
        },
        resetCameraLastSave3d: {
            title: 'Reset camera to last save',
            attr: 'resetLastSave',
            icon: 'camera-retro',
            click: this.handleCamera3d
        },
        hoverClosest3d: {
            title: 'Toggle show closest data on hover',
            attr: 'hovermode',
            val: null,
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleHover3d
        }
    };
};

module.exports = ModeBar;
<commit_msg>add 'camera' / 'cameraposition' logic in modebar<commit_after>'use strict';
/* global d3:false */

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');

    if(this.graphInfo._context.displayModeBar === 'hover') {
        this.element.className = 'modebar modebar--hover';
    } else {
        this.element.className = 'modebar';
    }

    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config()[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    if (this.graphInfo._context.displaylogo) {
        this.element.appendChild(this.getLogo());
    }

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'modebar-group';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var _this = this,
        button = document.createElement('a');

    button.setAttribute('rel', 'tooltip');
    button.className = 'modebar-btn';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('data-title', config.title);
    button.setAttribute('data-gravity', config.gravity || 'n');
    button.addEventListener('click', function () {
            config.click.apply(_this, arguments);
        });

    button.appendChild(this.createIcon(this.Plotly.Icons[config.icon]));

    return button;
};

/**
 * Add an icon to a button
 * @Param {object} thisIcon
 * @Param {number} thisIcon.width
 * @Param {string} thisIcon.path
 * @Return {HTMLelement}
 */
ModeBar.prototype.createIcon = function (thisIcon) {
    var iconDef = this.Plotly.Icons,
        iconHeight = iconDef.ascent - iconDef.descent,
        svgNS = 'http://www.w3.org/2000/svg',
        icon = document.createElementNS(svgNS, 'svg'),
        path = document.createElementNS(svgNS, 'path');

    icon.setAttribute('height', '1em');
    icon.setAttribute('width', (thisIcon.width / iconHeight)+'em');
    icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));

    path.setAttribute('d', thisIcon.path);
    path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + iconDef.ascent + ')');
    icon.appendChild(path);

    return icon;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        d3.select(button).classed('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * @return {HTMLDivElement} The logo image wrapped in a group
 */
ModeBar.prototype.getLogo = function(){
    var group = this.createGroup(),
        a = document.createElement('a');

    a.href = 'https://plot.ly/';
    a.target = '_blank';
    a.setAttribute('data-title', 'Produced with Plotly');
    a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';

    a.appendChild(this.createIcon(this.Plotly.Icons.plotlylogo));

    group.appendChild(a);
    return group;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCartesian = function(ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        graphInfo = this.graphInfo,
        fullLayout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        aobj = {};

    if(astr === 'zoom') {
        var mag = (val === 'in') ? 0.5 : 2,
            r0 = (1 + mag) / 2,
            r1 = (1 - mag) / 2,
            axList = Plotly.Axes.list(graphInfo, null, true),
            i,
            axName,
            initialRange;

        for(i = 0; i < axList.length; i++) {
            if(!axList[i].fixedrange) {
                axName = axList[i]._name;
                if(val === 'auto') aobj[axName + '.autorange'] = true;
                else {
                    initialRange = axList[i].range;
                    aobj[axName + '.range'] = [
                        r0 * initialRange[0] + r1 * initialRange[1],
                        r0 * initialRange[1] + r1 * initialRange[0]
                    ];
                }
            }
        }
    } else {
        // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
        if (astr==='hovermode' && (val==='x' || val==='y')) {
            val = fullLayout._isHoriz ? 'y' : 'x';
            button.setAttribute('data-val', val);
        }

        aobj[astr] = val;
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                fullLayout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
            Plotly.Fx.supplyLayoutDefaults(graphInfo.layout, fullLayout,
                graphInfo._fullData);
        }
    });
};

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 */
ModeBar.prototype.handleHover3d = function(ev) {
    var button = ev.currentTarget,
        val = JSON.parse(button.getAttribute('data-val')) || false,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'),
        layoutUpdate = {},

        // initialize 'current spike' object to be stored in the DOM
        currentSpikes = {},
        axes = ['xaxis', 'yaxis', 'zaxis'],
        spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor'];

    var i, sceneId, sceneLayout, sceneSpikes;
    var j, axis, axisSpikes;
    var k, spikeAttr;

    if (val) {
        layoutUpdate = val;
        button.setAttribute('data-val', JSON.stringify(null));
    }
    else {
        layoutUpdate = {'allaxes.showspikes': false};

        for (i = 0;  i < sceneIds.length; i++) {
            sceneId = sceneIds[i];
            sceneLayout = fullLayout[sceneId];
            sceneSpikes = currentSpikes[sceneId] = {};

            // copy all the current spike attrs
            for (j = 0; j < 3; j++) {
                axis = axes[j];
                axisSpikes = sceneSpikes[axis] = {};
                for (k = 0; k < spikeAttrs.length; k++) {
                    spikeAttr = spikeAttrs[k];
                    axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr];
                }
            }
        }

        button.setAttribute('data-val', JSON.stringify(currentSpikes));
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });

};

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 */
ModeBar.prototype.handleDrag3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'),
        layoutUpdate = {};

    layoutUpdate[attr] = val;

    var i, scene, camera;

    for (i = 0;  i < sceneIds.length; i++) {
        scene = fullLayout[sceneIds[i]]._scene;
        camera = scene.camera;
        if (camera) {

            if (val === 'orbital') {
                camera.mode = 'orbital';
                camera.keyBindingMode = 'rotate';

            } else if (val === 'turntable') {
                camera.up = [0, 0, 1];
                camera.mode = 'turntable';
                camera.keyBindingMode = 'rotate';

            } else {

                // none rotation modes
                camera.keyBindingMode = val;
            }
        }
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCamera3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        Plotly = this.Plotly,
        layout = this.graphInfo.layout,
        fullLayout = this.graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');

    var i, sceneId, sceneLayout, fullSceneLayout, scene, cameraPos;

    for (i = 0;  i < sceneIds.length; i++) {
        sceneId = sceneIds[i];
        sceneLayout = layout[sceneId];
        fullSceneLayout = fullLayout[sceneId];
        scene = fullSceneLayout._scene;

        if (!sceneLayout || attr==='resetDefault') scene.setCameraToDefault();
        else if (attr === 'resetLastSave') {

            if (sceneLayout.cameraposition !== undefined) {
                cameraPos = scene.cleanCamera(sceneLayout.cameraposition);
            }
            else cameraPos = sceneLayout.camera;

            if (cameraPos) scene.setCameraPosition(cameraPos);
            else scene.setCameraToDefault();
        }
    }

    /* TODO have a sceneLastTouched in _fullLayout to only
     * update the camera of the scene last touched by the user
     */
};

ModeBar.prototype.cleanup = function(){
    this.element.innerHTML = '';
    var modebarParent = this.element.parentNode;
    if (modebarParent) modebarParent.removeChild(this.element);
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = function config() {
    return {
        zoom2d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleCartesian
        },
        pan2d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleCartesian
        },
        zoomIn2d: {
            title: 'Zoom in',
            attr: 'zoom',
            val: 'in',
            icon: 'zoom_plus',
            click: this.handleCartesian
        },
        zoomOut2d: {
            title: 'Zoom out',
            attr: 'zoom',
            val: 'out',
            icon: 'zoom_minus',
            click: this.handleCartesian
        },
        autoScale2d: {
            title: 'Autoscale',
            attr: 'zoom',
            val: 'auto',
            icon: 'autoscale',
            click: this.handleCartesian
        },
        hoverClosest2d: {
            title: 'Show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleCartesian
        },
        hoverCompare2d: {
            title: 'Compare data on hover',
            attr: 'hovermode',
            val: this.graphInfo._fullLayout._isHoriz ? 'y' : 'x',
            icon: 'tooltip_compare',
            gravity: 'ne',
            click: this.handleCartesian
        },
        zoom3d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleDrag3d
        },
        pan3d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleDrag3d
        },
        orbitalRotation: {
            title: 'orbital rotation',
            attr: 'dragmode',
            val: 'orbital',
            icon: 'undo',
            click: this.handleDrag3d
        },
        tableRotation: {
            title: 'turn table rotation',
            attr: 'dragmode',
            val: 'turntable',
            icon: 'undo',
            click: this.handleDrag3d
        },
        resetCameraDefault3d: {
            title: 'Reset camera to default',
            attr: 'resetDefault',
            icon: 'home',
            click: this.handleCamera3d
        },
        resetCameraLastSave3d: {
            title: 'Reset camera to last save',
            attr: 'resetLastSave',
            icon: 'camera-retro',
            click: this.handleCamera3d
        },
        hoverClosest3d: {
            title: 'Toggle show closest data on hover',
            attr: 'hovermode',
            val: null,
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleHover3d
        }
    };
};

module.exports = ModeBar;
<|endoftext|>


<commit_before>import mongoose from "mongoose"
import mockgoose from "mockgoose"

export function setupMocha() {
  before("Mock mongoose", async() => {
		await mockgoose(mongoose)
    mongoose.connect('mongodb://localhost/projects')
	})

	after("Restore mongoose", done => {
  	mongoose.unmock(done);
	})

	afterEach("Reset mock mongo database", done => {
	  mockgoose.reset(done);
	})
}
<commit_msg>Parametrized mongoose db url for tests<commit_after>import mongoose from "mongoose"
import mockgoose from "mockgoose"

export function setupMocha() {
    before("Mock mongoose", async () => {
        await mockgoose(mongoose)
        mongoose.connect(process.env.MONGO_URL || 'mongodb://localhost/projects')
    })

    after("Restore mongoose", done => {
        mongoose.unmock(done);
    })

    afterEach("Reset mock mongo database", done => {
        mockgoose.reset(done);
    })
}
<|endoftext|>