conveyal/transitive.js

View on GitHub
lib/renderer/default-renderer.js

Summary

Maintainability
A
2 hrs
Test Coverage
import { forEach } from 'lodash'

import Renderer from './renderer'

/**
 * A Renderer subclass for the default network rendering engine.
 *
 * @param {Object} the main Transitive object
 */

export default class DefaultRenderer extends Renderer {
  render() {
    super.render()

    const display = this.transitive.display
    const network = this.transitive.network
    display.styler = this.transitive.styler

    // TODO: restore legend?

    // Process vertices/points
    const focusedPoints = []
    const unfocusedPoints = []
    forEach(network.graph.vertices, (vertex) => {
      vertex.point.clearRenderData()
      if (!vertex.point.isFocused()) unfocusedPoints.push(vertex.point)
      else focusedPoints.push(vertex.point)
    })

    forEach(network.renderedEdges, (rEdge) => {
      rEdge.refreshRenderData(display)
    })

    // Create arrays of focused, unfocused RenderedSegments
    const focusedSegments = []
    const unfocusedSegments = []
    forEach(network.paths, (path) => {
      forEach(path.segments, (pathSegment) => {
        forEach(pathSegment.renderedSegments, (renderedSegment) => {
          if (renderedSegment.isFocused()) focusedSegments.push(renderedSegment)
          else unfocusedSegments.push(renderedSegment)
        })
      })
    })

    // Render elements in the following order:

    // 1. Unfocused segments
    unfocusedSegments.forEach((rs) => rs.render(display))

    // 2. Unfocused points
    forEach(unfocusedPoints, (pt) => pt.render(display))

    // 3. Focused segments
    focusedSegments.forEach((rs) => rs.render(display))

    // 4. Focused points
    forEach(focusedPoints, (pt) => pt.render(display))

    // TODO: draw the edge-based points
    /* forEach(network.graph.edges, edge => {
      forEach(edge.pointArray, point => {
        point.render(display)
      })
    }) */

    // 5. Labels
    const labels = this.transitive.labeler.doLayout()
    forEach(labels.pointLabels, (label) => label.render(display))
    forEach(labels.segmentLabels, (label) => label.render(display))

    // Keep internal collection of rendered segments
    this.renderedSegments = focusedSegments.concat(unfocusedSegments)
  }

  refreshSegmentRenderData() {
    forEach(this.transitive.network.renderedEdges, (rEdge) => {
      rEdge.refreshRenderData(this.transitive.display)
    })

    // try intersecting adjacent rendered edges to create a smooth transition

    const isectKeys = [] // keep track of edge-edge intersections we've already computed
    forEach(this.transitive.network.paths, (path) => {
      forEach(path.segments, (pathSegment) => {
        forEach(pathSegment.renderedSegments, (rSegment) => {
          for (let s = 0; s < rSegment.renderedEdges.length - 1; s++) {
            const rEdge1 = rSegment.renderedEdges[s]
            const rEdge2 = rSegment.renderedEdges[s + 1]
            const key = rEdge1.getId() + '_' + rEdge2.getId()
            if (isectKeys.indexOf(key) !== -1) continue
            if (rEdge1.graphEdge.isInternal && rEdge2.graphEdge.isInternal) {
              rEdge1.intersect(rEdge2)
            }
            isectKeys.push(key)
          }
        })
      })
    })
  }

  /**
   * sortElements
   */

  sortElements() {
    this.renderedSegments.sort(function (a, b) {
      return a.compareTo(b)
    })

    const focusBaseZIndex = 100000

    forEach(this.renderedSegments, (rSegment, index) => {
      rSegment.zIndex =
        index * 10 + (rSegment.isFocused() ? focusBaseZIndex : 0)
    })

    forEach(this.transitive.network.graph.vertices, (vertex) => {
      const point = vertex.point
      point.zIndex = point.zIndex + (point.isFocused() ? focusBaseZIndex : 0)
    })

    this.transitive.display.svg
      .selectAll('.transitive-sortable')
      .sort(function (a, b) {
        const aIndex =
          typeof a.getZIndex === 'function'
            ? a.getZIndex()
            : a.owner.getZIndex()
        const bIndex =
          typeof b.getZIndex === 'function'
            ? b.getZIndex()
            : b.owner.getZIndex()
        return aIndex - bIndex
      })
  }

  /**
   * focusPath
   */

  focusPath(path) {
    let pathRenderedSegments = []
    const graph = this.transitive.network.graph

    if (path) {
      // if we're focusing a specific path
      pathRenderedSegments = path.getRenderedSegments()

      // un-focus all internal points
      forEach(graph.edges, (edge) => {
        edge.pointArray.forEach(function (point, i) {
          point.setAllPatternsFocused(false)
        })
      })
    } else {
      // if we're returning to 'all-focused' mode
      // re-focus all internal points
      forEach(graph.edges, (edge) => {
        forEach(edge.pointArray, (point, i) => {
          point.setAllPatternsFocused(true)
        })
      })
    }

    // Keep track of changed segments / points for transition animation (currently disabled)
    const focusChangeSegments = []
    const focusedVertexPoints = []

    forEach(this.renderedSegments, (rSegment) => {
      if (path && pathRenderedSegments.indexOf(rSegment) === -1) {
        if (rSegment.isFocused()) focusChangeSegments.push(rSegment)
        rSegment.setFocused(false)
      } else {
        if (!rSegment.isFocused()) focusChangeSegments.push(rSegment)
        rSegment.setFocused(true)
        focusedVertexPoints.push(rSegment.pathSegment.startVertex().point)
        focusedVertexPoints.push(rSegment.pathSegment.endVertex().point)
      }
    })

    const focusChangePoints = []
    forEach(graph.vertices, (vertex) => {
      const point = vertex.point
      if (focusedVertexPoints.indexOf(point) !== -1) {
        if (!point.isFocused()) focusChangePoints.push(point)
        point.setFocused(true)
      } else {
        if (point.isFocused()) focusChangePoints.push(point)
        point.setFocused(false)
      }
    })

    // bring the focused elements to the front for the transition
    // if (path) this.sortElements();

    // TODO: restore transitions
    /*
    // create a transition callback function that invokes refresh() after all transitions complete
    var n = 0
    var refreshOnEnd = (transition, callback) => {
      transition
        .each(() => { ++n })
        .on('end', () => { if (!--n) this.transitive.refresh() })
    }

    // run the transitions on the affected elements
    forEach(focusChangeSegments, segment => {
      segment.runFocusTransition(this.transitive.display, refreshOnEnd)
    })

    forEach(focusChangePoints, point => {
      point.runFocusTransition(this.transitive.display, refreshOnEnd)
    })
    */
    this.transitive.render()
  }
}