conveyal/transitive.js

View on GitHub
lib/point/multipoint.js

Summary

Maintainability
C
1 day
Test Coverage
import { forEach } from 'lodash'

import Point from './point'

/**
 *  MultiPoint: a Point subclass representing a collection of multiple points
 *  that have been merged into one for display purposes.
 */

export default class MultiPoint extends Point {
  constructor(pointArray) {
    super()
    this.points = []
    if (pointArray) {
      forEach(pointArray, (point) => {
        this.addPoint(point)
      })
    }
    this.renderData = []
    this.id = 'multi'
    this.toPoint = this.fromPoint = null

    this.patternStylerKey = 'multipoints_pattern'
  }

  /**
   * Get id
   */

  getId() {
    return this.id
  }

  /**
   * Get type
   */

  getType() {
    return 'MULTI'
  }

  getName() {
    if (this.fromPoint) return this.fromPoint.getName()
    if (this.toPoint) return this.toPoint.getName()
    let shortest = null
    forEach(this.points, (point) => {
      if (point.getType() === 'TURN') return
      if (!shortest || point.getName().length < shortest.length) {
        shortest = point.getName()
      }
    })

    return shortest
  }

  containsSegmentEndPoint() {
    for (let i = 0; i < this.points.length; i++) {
      if (this.points[i].containsSegmentEndPoint()) return true
    }
    return false
  }

  containsBoardPoint() {
    for (let i = 0; i < this.points.length; i++) {
      if (this.points[i].containsBoardPoint()) return true
    }
    return false
  }

  containsAlightPoint() {
    for (let i = 0; i < this.points.length; i++) {
      if (this.points[i].containsAlightPoint()) return true
    }
    return false
  }

  containsTransferPoint() {
    for (let i = 0; i < this.points.length; i++) {
      if (this.points[i].containsTransferPoint()) return true
    }
    return false
  }

  containsFromPoint() {
    return this.fromPoint !== null
  }

  containsToPoint() {
    return this.toPoint !== null
  }

  getPatterns() {
    const patterns = []

    forEach(this.points, (point) => {
      if (!point.patterns) return
      forEach(point.patterns, (pattern) => {
        if (patterns.indexOf(pattern) === -1) patterns.push(pattern)
      })
    })

    return patterns
  }

  addPoint(point) {
    if (this.points.indexOf(point) !== -1) return
    this.points.push(point)
    this.id += '-' + point.getId()
    if (point.containsFromPoint()) {
      // getType() === 'PLACE' && point.getId() === 'from') {
      this.fromPoint = point
    }
    if (point.containsToPoint()) {
      // getType() === 'PLACE' && point.getId() === 'to') {
      this.toPoint = point
    }
    this.calcWorldCoords()
  }

  calcWorldCoords() {
    let tx = 0
    let ty = 0
    forEach(this.points, (point) => {
      tx += point.worldX
      ty += point.worldY
    })

    this.worldX = tx / this.points.length
    this.worldY = ty / this.points.length
  }

  /**
   * Add render data
   *
   * @param {Object} stopInfo
   */

  addRenderData(pointInfo) {
    if (pointInfo.offsetX !== 0 || pointInfo.offsetY !== 0) {
      this.hasOffsetPoints = true
    }
    this.renderData.push(pointInfo)
  }

  clearRenderData() {
    this.hasOffsetPoints = false
    this.renderData = []
  }

  /**
   * Draw a multipoint
   *
   * @param {Display} display
   */

  render(display) {
    super.render(display)

    if (!this.renderData) return

    // Compute the bounds of the merged marker
    const xArr = this.renderData.map((d) => d.x)
    const yArr = this.renderData.map((d) => d.y)
    const xMin = Math.min(...xArr)
    const xMax = Math.max(...xArr)
    const yMin = Math.min(...yArr)
    const yMax = Math.max(...yArr)

    const r = 6
    const x = xMin - r
    const y = yMin - r
    const width = xMax - xMin + r * 2
    const height = yMax - yMin + r * 2

    // Draw the merged marker
    display.drawRect(
      { x, y },
      {
        fill: '#fff',
        height,
        rx: r,
        ry: r,
        stroke: '#000',
        'stroke-width': 2,
        width
      }
    )

    // Store marker bounding box
    this.markerBBox = { height, width, x, y }

    // TODO: support pattern-specific markers
  }

  initMergedMarker(display) {
    // set up the merged marker
    if (this.fromPoint || this.toPoint) {
      this.mergedMarker = this.markerSvg
        .append('g')
        .append('circle')
        .datum({
          owner: this
        })
        .attr('class', 'transitive-multipoint-marker-merged')
    } else if (this.hasOffsetPoints || this.renderData.length > 1) {
      this.mergedMarker = this.markerSvg
        .append('g')
        .append('rect')
        .datum({
          owner: this
        })
        .attr('class', 'transitive-multipoint-marker-merged')
    }
  }

  getRenderDataArray() {
    return this.renderData
  }

  setFocused(focused) {
    this.focused = focused
    forEach(this.points, (point) => {
      point.setFocused(focused)
    })
  }

  runFocusTransition(display, callback) {
    if (this.mergedMarker) {
      const newStrokeColor = display.styler.compute(
        display.styler.multipoints_merged.stroke,
        display,
        {
          owner: this
        }
      )
      this.mergedMarker
        .transition()
        .style('stroke', newStrokeColor)
        .call(callback)
    }
    if (this.label) this.label.runFocusTransition(display, callback)
  }
}