conveyal/modeify

View on GitHub
client/components/conveyal/transitive.js/master/lib/styler/styles.js

Summary

Maintainability
A
45 mins
Test Coverage
var d3 = require('d3');
var clone = require('clone');

/**
 * Scales for utility functions to use
 */

var zoomScale = d3.scale.linear().domain([0.25, 1, 4]);
var strokeScale = d3.scale.linear().domain([0.25, 1, 4]).range([5, 12, 19]);
var fontScale = d3.scale.linear().domain([0.25, 1, 4]).range([10, 14, 18]);

/**
 * Scales for utility functions to use
 */

var notFocusedColor = '#e0e0e0';

/**
 * Expose `utils` for the style functions to use
 */

exports.utils = {
  pixels: function(zoom, min, normal, max) {
    return zoomScale.range([min, normal, max])(zoom);
  },
  strokeWidth: function(display) {
    return strokeScale(display.zoom.scale());
  },
  fontSize: function(display, data) {
    return Math.floor(fontScale(display.zoom.scale()));
  },
  defineSegmentCircleMarker: function(display, segment, radius, fillColor) {
    var markerId = 'circleMarker-' + segment.getId();
    display.svg.append('defs').append('svg:marker')
      .attr('id', markerId)
      .attr('refX', radius)
      .attr('refY', radius)
      .attr('markerWidth', radius * 2)
      .attr('markerHeight', radius * 2)
      .attr('markerUnits', 'userSpaceOnUse')
      .append('svg:circle')
      .attr('cx', radius)
      .attr('cy', radius)
      .attr('r', radius)
      .attr('fill', segment.focused ? fillColor : notFocusedColor);

    return 'url(#' + markerId + ')';
  }
};

/**
 * Default Wireframe Edge/Vertex Rules
 */

exports.wireframe_vertices = {
  cx: 0,
  cy: 0,
  r: 3,
  fill: '#000'
};

exports.wireframe_edges = {
  stroke: '#444',
  'stroke-width': 2,
  'stroke-dasharray': '3px 2px',
  fill: 'none'
};

/**
 * Default Merged Stops Rules
 */

var stops_merged = exports.stops_merged = {
  fill: function(display, data, index, utils) {
    return '#fff';
  },
  r: function(display, data, index, utils) {
    return utils.pixels(display.zoom.scale(), 8, 12, 16);
  },
  stroke: function(display, data, index, utils) {
    var point = data.owner;
    if (!point.isFocused()) return notFocusedColor;
    return '#000';
  },
  'stroke-width': function(display, data, index, utils) {
    return 2;
  },

  /**
   *  Transitive-specific attribute specifying the shape of the main stop marker.
   *  Can be 'roundedrect', 'rectangle' or 'circle'
   */

  'marker-type': [
    'circle',
    function(display, data, index, utils) {
      var point = data.owner;
      if ((point.containsBoardPoint() || point.containsAlightPoint()) && !
        point.containsTransferPoint()) return 'circle';
    }
  ],

  /**
   *  Transitive-specific attribute specifying any additional padding, in pixels,
   *  to apply to main stop marker. A value of zero (default) results in a that
   *  marker is flush to the edges of the pattern segment(s) the point is set against.
   *  A value greater than zero creates a marker that is larger than the width of
   *  the segments(s).
   */

  'marker-padding': 3,

  visibility: function(display, data) {
    if (!data.owner.containsSegmentEndPoint()) return 'hidden';
  }
};

/**
 * Stops Along a Pattern
 */

var stops_pattern = exports.stops_pattern = {
  cx: 0,
  cy: 0,
  r: [
    4,
    function(display, data, index, utils) {
      return utils.pixels(display.zoom.scale(), 1, 2, 4);
    },
    function(display, data, index, utils) {
      var point = data.owner;
      var busOnly = true;
      point.getPatterns().forEach(function(pattern) {
        if (pattern.route && pattern.route.route_type !== 3) busOnly =
          false;
      });
      if (busOnly && !point.containsSegmentEndPoint()) {
        return 0.5 * utils.pixels(display.zoom.scale(), 2, 4, 6.5);
      }
    }
  ],
  stroke: 'none',
  visibility: function(display, data) {
    if (display.zoom.scale() < 1.5) return 'hidden';
    if (data.owner.containsSegmentEndPoint()) return 'hidden';
  }
};

/**
 * Default place rules
 */

exports.places = {
  cx: 0,
  cy: 0,
  r: 14,
  stroke: '0px',
  fill: '#fff'
};

/**
 * Default MultiPoint rules -- based on Stop rules
 */

var multipoints_merged = exports.multipoints_merged = clone(stops_merged);

multipoints_merged.visibility = true;

/**
 * Default Multipoint Stops along a pattern
 */

exports.multipoints_pattern = clone(stops_pattern);

/**
 * Default label rules
 */

var labels = exports.labels = {
  'font-size': function(display, data, index, utils) {
    return utils.fontSize(display, data) + 'px';
  },
  'font-weight': function(display, data, index, utils) {
    var point = data.owner.parent;
    if (point.containsBoardPoint() || point.containsAlightPoint())
      return 'bold';
  },

  /**
   * 'orientations' is a transitive-specific attribute used to specify allowable
   * label placement orientations expressed as one of eight compass directions
   * relative to the point being labeled:
   *
   *        'N'
   *    'NW' |  'NE'
   *       \ | /
   *  'W' -- O -- 'E'
   *       / | \
   *    'SW' | 'SE'
   *        'S
   *
   * Labels oriented 'E' or 'W' are rendered horizontally, 'N' and 'S' vertically,
   * and all others at a 45-degree angle.
   *
   * Returns an array of allowed orientation codes in the order that they will be
   * tried by the labeler.
   */

  orientations: [
    ['E', 'W']
  ]
};

/**
 * All path segments
 * TODO: update old route-pattern-specific code below
 */

exports.segments = {
  stroke: [
    '#008',
    function(display, data) {
      var segment = data;
      if (!segment.focused) return notFocusedColor;
      if (segment.type === 'TRANSIT') {
        if (segment.patterns) {
          if (segment.patterns[0].route.route_short_name.toLowerCase().substring(
            0,
            2) === 'dc') return '#f00';
          return segment.patterns[0].route.getColor();
        }
      } else if (segment.type === 'CAR') {
        return '#888';
      }
    }
  ],
  'stroke-dasharray': [
    false,
    function(display, data) {
      var segment = data;
      if (segment.frequency && segment.frequency.average < 12) {
        if (segment.frequency.average > 6) return '12px, 12px';
        return '12px, 2px';
      }
    }
  ],
  'stroke-width': [
    '12px',
    function(display, data, index, utils) {
      var segment = data;

      if (segment.mode === 3) {
        return utils.pixels(display.zoom.scale(), 2, 4, 8) + 'px';
      }
      return utils.pixels(display.zoom.scale(), 4, 8, 12) + 'px';
    }
  ],
  envelope: [

    function(display, data, index, utils) {
      var segment = data;
      if (segment.type !== 'TRANSIT') {
        return '8px';
      }
      if (segment.mode === 3) {
        return utils.pixels(display.zoom.scale(), 4, 6, 10) + 'px';
      }
      return utils.pixels(display.zoom.scale(), 6, 10, 14) + 'px';
    }
  ]
};

/**
 * Segments Front
 */

exports.segments_front = {
  stroke: '#008',
  'stroke-width': function(display, data, index, utils) {
    return utils.pixels(display.zoom.scale(), 3, 6, 10) / 2 + 'px';
  },
  fill: 'none',
  display: [
    'none',
    function(display, data, index, utils) {
      if (data.pattern && data.pattern.route && data.pattern.route.route_type ===
        3 &&
        data.pattern.route.route_short_name.toLowerCase().substring(0, 2) ===
        'dc') {
        return 'inline';
      }
    }
  ]
};

/**
 * Segments Halo
 */

exports.segments_halo = {
  stroke: '#fff',
  'stroke-width': function(display, data, index, utils) {
    return data.computeLineWidth(display) + 8;
  },
  'stroke-linecap': 'round',
  fill: 'none'
};

/**
 * Label Containers
 */

exports.segment_label_containers = {
  fill: function(display, data) {
    if (!data.isFocused()) return notFocusedColor;
  },
  'stroke-width': function(display, data) {
    if (data.parent.pattern && data.parent.pattern.route.route_short_name.toLowerCase()
      .substring(0, 2) === 'dc') return 1;
    return 0;
  },
  rx: 3,
  ry: 3
};