conveyal/transitive.js

View on GitHub
lib/renderer/renderedsegment.js

Summary

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

import { distance, normalizeVector, rotateVector } from '../util'

/**
 * RenderedSegment
 */

let rSegmentId = 0

export default class RenderedSegment {
  constructor(pathSegment) {
    this.id = rSegmentId++
    this.renderedEdges = []
    this.pathSegment = pathSegment
    if (pathSegment) this.type = pathSegment.type
    this.focused = true
  }

  getId() {
    return this.id
  }

  getType() {
    return this.type
  }

  addRenderedEdge(rEdge) {
    this.renderedEdges.push(rEdge)
  }

  render(display) {
    const styler = display.styler

    this.renderData = []
    forEach(this.renderedEdges, (rEdge) => {
      this.renderData = this.renderData.concat(rEdge.renderData)
    })

    // Check if this segment is to be drawn as an arc; if so replace render data
    if (
      this.pathSegment.journeySegment &&
      this.pathSegment.journeySegment.arc
    ) {
      const first = this.renderData[0]
      const last = this.renderData[this.renderData.length - 1]
      const v = {
        x: last.x - first.x,
        y: last.y - first.y
      }
      const vp = rotateVector(normalizeVector(v), -Math.PI / 2)
      const dist = distance(first.x, first.y, last.x, last.y)
      const arc = {
        arc: -45,
        ex: (last.x + first.x) / 2 + vp.x * (dist / 4),
        ey: (last.y + first.y) / 2 + vp.y * (dist / 4),
        radius: dist * 0.75,
        x: last.x,
        y: last.y
      }
      this.renderData = [first, arc, last]
    }

    display.drawPath(this.renderData, {
      fill: 'none',
      stroke: styler.compute2('segments', 'stroke', this),
      'stroke-dasharray': styler.compute2('segments', 'stroke-dasharray', this),
      'stroke-linecap': styler.compute2('segments', 'stroke-linecap', this),
      'stroke-width': styler.compute2('segments', 'stroke-width', this)
    })
  }

  setFocused(focused) {
    this.focused = focused
  }

  isFocused() {
    return this.focused
  }

  runFocusTransition(display, callback) {
    const newColor = display.styler.compute(
      display.styler.segments.stroke,
      display,
      this
    )
    this.lineGraph.transition().style('stroke', newColor).call(callback)
  }

  getZIndex() {
    return this.zIndex
  }

  computeLineWidth(display, includeEnvelope) {
    const styler = display.styler
    if (styler && display) {
      // compute the line width
      const env = styler.compute(styler.segments.envelope, display, this)
      if (env && includeEnvelope) {
        return parseFloat(env.substring(0, env.length - 2), 10) - 2
      } else {
        const lw = styler.compute(
          styler.segments['stroke-width'],
          display,
          this
        )
        return parseFloat(lw.substring(0, lw.length - 2), 10) - 2
      }
    }
  }

  compareTo(other) {
    // show transit segments in front of other types
    if (this.type === 'TRANSIT' && other.type !== 'TRANSIT') return -1
    if (other.type === 'TRANSIT' && this.type !== 'TRANSIT') return 1

    if (this.type === 'TRANSIT' && other.type === 'TRANSIT') {
      // for two transit segments, try sorting transit mode first
      if (this.mode && other.mode && this.mode !== other.mode) {
        return this.mode > other.mode
      }

      // for two transit segments of the same mode, sort by id (for display consistency)
      return this.getId() < other.getId()
    }
  }

  getLabelTextArray() {
    const textArray = []
    forEach(this.patterns, (pattern) => {
      // TODO: Should we attempt to extract part of the long name if short name
      // is missing?
      const shortName = pattern.route.route_short_name
      if (textArray.indexOf(shortName) === -1) textArray.push(shortName)
    })
    return textArray
  }

  getLabelAnchors(display, spacing) {
    const labelAnchors = []
    this.computeRenderLength(display)
    const anchorCount = Math.floor(this.renderLength / spacing)
    const pctSpacing = spacing / this.renderLength

    for (let i = 0; i < anchorCount; i++) {
      const t =
        i % 2 === 0
          ? 0.5 + (i / 2) * pctSpacing
          : 0.5 - ((i + 1) / 2) * pctSpacing
      const coord = this.coordAlongRenderedPath(t, display)
      if (coord) labelAnchors.push(coord)
    }

    return labelAnchors
  }

  coordAlongRenderedPath(t, display) {
    const loc = t * this.renderLength

    let cur = 0
    for (let i = 0; i < this.renderedEdges.length; i++) {
      const rEdge = this.renderedEdges[i]
      const edgeRenderLen = rEdge.graphEdge.getRenderLength(display)
      if (loc <= cur + edgeRenderLen) {
        const t2 = (loc - cur) / edgeRenderLen
        return rEdge.graphEdge.coordAlongEdge(t2, rEdge.renderData, display)
      }
      cur += edgeRenderLen
    }
  }

  computeRenderLength(display) {
    this.renderLength = 0
    forEach(this.renderedEdges, (rEdge) => {
      this.renderLength += rEdge.graphEdge.getRenderLength(display)
    })
  }

  toString() {
    return `RenderedSegment ${this.id} on ${
      this.pathSegment ? this.pathSegment.toString() : ' (null segment)'
    }`
  }
}