lib/renderer/renderededge.js
import { forEach } from 'lodash'
import { isOutwardVector, lineIntersection } from '../util'
let rEdgeId = 0
/**
* RenderedEdge
*/
export default class RenderedEdge {
constructor(graphEdge, forward, type, useGeographicRendering) {
this.id = rEdgeId++
this.graphEdge = graphEdge
this.forward = forward
this.type = type
this.points = []
this.clearOffsets()
this.focused = true
this.sortableType = 'SEGMENT'
this.useGeographicRendering = useGeographicRendering
}
clearGraphData() {
this.graphEdge = null
this.edgeFromOffset = 0
this.edgeToOffset = 0
}
addPattern(pattern) {
if (!this.patterns) this.patterns = []
if (this.patterns.indexOf(pattern) !== -1) return
this.patterns.push(pattern)
// generate the patternIds field
this.patternIds = constuctIdListString(this.patterns)
}
addPathSegment(pathSegment) {
if (!this.pathSegments) this.pathSegments = []
if (this.pathSegments.indexOf(pathSegment) !== -1) return
this.pathSegments.push(pathSegment)
// generate the pathSegmentIds field
this.pathSegmentIds = constuctIdListString(this.pathSegments)
}
getId() {
return this.id
}
getType() {
return this.type
}
setFromOffset(offset) {
this.fromOffset = offset
}
setToOffset(offset) {
this.toOffset = offset
}
clearOffsets() {
this.fromOffset = 0
this.toOffset = 0
}
getAlignmentVector(alignmentId) {
if (this.graphEdge.getFromAlignmentId() === alignmentId) {
return this.graphEdge.fromVector
}
if (this.graphEdge.getToAlignmentId() === alignmentId) {
return this.graphEdge.toVector
}
return null
}
offsetAlignment(alignmentId, offset) {
// If from/to alignment IDs match, set respective offset.
if (this.graphEdge.getFromAlignmentId() === alignmentId) {
this.setFromOffset(
isOutwardVector(this.graphEdge.fromVector) ? offset : -offset
)
}
if (this.graphEdge.getToAlignmentId() === alignmentId) {
this.setToOffset(
isOutwardVector(this.graphEdge.toVector) ? offset : -offset
)
}
}
setFocused(focused) {
this.focused = focused
}
refreshRenderData(display) {
if (
this.graphEdge.fromVertex.x === this.graphEdge.toVertex.x &&
this.graphEdge.fromVertex.y === this.graphEdge.toVertex.y
) {
this.renderData = []
return
}
this.lineWidth = this.computeLineWidth(display, true)
const fromOffsetPx = this.fromOffset * this.lineWidth
const toOffsetPx = this.toOffset * this.lineWidth
if (this.useGeographicRendering && this.graphEdge.geomCoords) {
this.renderData = this.graphEdge.getGeometricCoords(
fromOffsetPx,
toOffsetPx,
display,
this.forward
)
} else {
this.renderData = this.graphEdge.getRenderCoords(
fromOffsetPx,
toOffsetPx,
display,
this.forward
)
}
const firstRenderPoint = this.renderData[0]
const lastRenderPoint = this.renderData[this.renderData.length - 1]
let pt
if (!this.graphEdge.fromVertex.isInternal) {
pt = this.forward ? firstRenderPoint : lastRenderPoint
if (pt) {
this.graphEdge.fromVertex.point.addRenderData({
rEdge: this,
x: pt.x,
y: pt.y
})
}
}
pt = this.forward ? lastRenderPoint : firstRenderPoint
if (pt) {
this.graphEdge.toVertex.point.addRenderData({
rEdge: this,
x: pt.x,
y: pt.y
})
}
forEach(this.graphEdge.pointArray, (point, i) => {
if (point.getType() === 'TURN') return
const t = (i + 1) / (this.graphEdge.pointArray.length + 1)
const coord = this.graphEdge.coordAlongEdge(
this.forward ? t : 1 - t,
this.renderData,
display
)
if (coord) {
point.addRenderData({
rEdge: this,
x: coord.x,
y: coord.y
})
}
})
}
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
}
}
}
isFocused() {
return this.focused === true
}
getZIndex() {
return 10000
}
/**
* Computes the point of intersection between two adjacent, offset RenderedEdges (the
* edge the function is called on and a second edge passed as a parameter)
* by "extending" the adjacent edges and finding the point of intersection. If
* such a point exists, the existing renderData arrays for the edges are
* adjusted accordingly, as are any associated stops.
*/
intersect(rEdge) {
// do no intersect adjacent edges of unequal bundle size
if (
this.graphEdge.renderedEdges.length !==
rEdge.graphEdge.renderedEdges.length
) {
return
}
const commonVertex = this.graphEdge.commonVertex(rEdge.graphEdge)
if (!commonVertex || commonVertex.point.isSegmentEndPoint) return
const thisCheck =
(commonVertex === this.graphEdge.fromVertex && this.forward) ||
(commonVertex === this.graphEdge.toVertex && !this.forward)
const otherCheck =
(commonVertex === rEdge.graphEdge.fromVertex && rEdge.forward) ||
(commonVertex === rEdge.graphEdge.toVertex && !rEdge.forward)
const p1 = thisCheck
? this.renderData[0]
: this.renderData[this.renderData.length - 1]
const v1 = this.graphEdge.getVector(commonVertex)
const p2 = otherCheck
? rEdge.renderData[0]
: rEdge.renderData[rEdge.renderData.length - 1]
const v2 = rEdge.graphEdge.getVector(commonVertex)
if (!p1 || !p2 || !v1 || !v2 || (p1.x === p2.x && p1.y === p2.y)) return
const isect = lineIntersection(
p1.x,
p1.y,
p1.x + v1.x,
p1.y - v1.y,
p2.x,
p2.y,
p2.x + v2.x,
p2.y - v2.y
)
if (!isect.intersect) return
// adjust the endpoint of the first edge
if (thisCheck) {
this.renderData[0].x = isect.x
this.renderData[0].y = isect.y
} else {
this.renderData[this.renderData.length - 1].x = isect.x
this.renderData[this.renderData.length - 1].y = isect.y
}
// adjust the endpoint of the second edge
if (otherCheck) {
rEdge.renderData[0].x = isect.x
rEdge.renderData[0].y = isect.y
} else {
rEdge.renderData[rEdge.renderData.length - 1].x = isect.x
rEdge.renderData[rEdge.renderData.length - 1].y = isect.y
}
// update the point renderData
commonVertex.point.addRenderData({
rEdge: this,
x: isect.x,
y: isect.y
})
}
findExtension(vertex) {
const incidentEdges = vertex.incidentEdges(this.graphEdge)
const bundlerId = this.patternIds || this.pathSegmentIds
for (let e = 0; e < incidentEdges.length; e++) {
const edgeSegments = incidentEdges[e].renderedEdges
for (let s = 0; s < edgeSegments.length; s++) {
const segment = edgeSegments[s]
const otherId = segment.patternIds || segment.pathSegmentIds
if (bundlerId === otherId) {
return segment
}
}
}
}
toString() {
return `RenderedEdge ${this.id} type=${
this.type
} on ${this.graphEdge.toString()} w/ patterns ${this.patternIds} fwd=${
this.forward
}`
}
}
/**
* Helper method to construct a merged ID string from a list of items with
* their own IDs
*/
function constuctIdListString(items) {
const idArr = []
forEach(items, (item) => {
idArr.push(item.getId())
})
idArr.sort()
return idArr.join(',')
}