Highlighted Net Chart on Geo Chart
Shows shortest distance, least flight segments
or both.
On links shows
HTML
HTML
<script src="https://cdn.zoomcharts-cloud.com/1/nightly/zoomcharts.js"></script>
Shows <span style="border-bottom: 2px solid rgba(0,153,204,0.9)">shortest distance</span>, <span style="border-bottom: 2px solid rgba(213,66,155,1)">least flight segments</span>
or <span style="border-bottom: 2px solid rgba(119,114,181,1)">both</span>.
On links shows <select id="link_chooser">
<option value="distance" selected="selected">distance</option>
<option value="duration">duration</option>
</select>
<div id="demo"></div>
CSS
CSS
//No CSS for this example
JavaScript
JavaScript
var pathColorDistance = "rgba(0,153,204,0.9)";
var pathColorSegments = "rgba(213,66,155,1)";
var pathColorBoth = "rgba(119,114,181,1)";
// the current selected path, contains both the node and link IDs
var startNodeId = "BWI"; //null;
var endNodeId = "GOT"; //null;
var pathData = null;
// to improve performance, cache the last calculated path
var lastEndNodeId = "BWI"; //null;
var lastPathData = "GOT"; //null;
// first click enables hover functionality:
var initialClick = null;
function computePath() {
// BFS implementation to find the shortest paths
var from = chart.getNode(startNodeId);
var back = {};
back[from.id] = { distance: 0, segments: 0, segmentDistance: 0, previousDistance: null, previousSegments: null };
var queue = [from];
var link, next, cur;
while (queue.length > 0) {
cur = queue.pop();
for (var i = 0; i < cur.links.length; i++) {
link = cur.links[i];
next = link.otherEnd(cur);
var curResult = back[cur.id];
var nextResult = back[next.id];
var visitNext = false;
var targetDistance = curResult.distance + link.data.distance_km;
var targetSegments = curResult.segments + 1;
// do not got too deep
if (targetSegments === 6)
continue;
if (nextResult === void 0) {
back[next.id] = {
distance: targetDistance,
segments: targetSegments,
segmentDistance: targetDistance,
previousDistance: link,
previousSegments: link
};
visitNext = true;
} else {
if (nextResult.distance > targetDistance) {
nextResult.distance = targetDistance;
nextResult.previousDistance = link;
visitNext = true;
}
if (nextResult.segments > targetSegments || (nextResult.segments === targetSegments && nextResult.segmentDistance > targetDistance)) {
nextResult.segments = targetSegments;
nextResult.segmentDistance = targetDistance;
nextResult.previousSegments = link;
visitNext = true;
}
}
if (visitNext)
queue.push(next);
}
}
// walk back from the target node to the start
var pathData = back[endNodeId];
if (pathData === void 0) {
return null;
}
var resultDistance = {};
var cur = chart.getNode(endNodeId);
while (true) {
var link = pathData.previousDistance;
// found the first node
if (link === null)
break;
cur = link.otherEnd(cur);
resultDistance[link.id] = true;
resultDistance[cur.id] = true;
pathData = back[cur.id];
}
var resultSegments = {};
var pathData = back[endNodeId];
var cur = chart.getNode(endNodeId);
while (true) {
var link = pathData.previousSegments;
// found the first node
if (link === null)
break;
cur = link.otherEnd(cur);
resultSegments[link.id] = true;
resultSegments[cur.id] = true;
pathData = back[cur.id];
}
return { distance: resultDistance, segments: resultSegments };
}
function recalculatePathStyle() {
if (!pathData)
return;
var idArray = [startNodeId, endNodeId];
for (var key in pathData.distance)
idArray.push(key);
for (var key in pathData.segments)
idArray.push(key);
// only recalculate the style for the affected nodes and links to improve performance
chart.updateStyle(idArray);
}
var link_chooser = document.getElementById("link_chooser");
var options = {
area: { height: null },
container: "demo",
data: { url: "/dvsl/data/geo-chart/airports.json" },
layers: [
{
name: "Points",
type: "items",
style: {
scaleObjectsWithZoom: true,
scaleLinksWithZoom: false,
link: {
invisible: true,
radius: 4
},
linkLabel: {
padding: 1,
borderRadius: 3,
textStyle: { font: "10px Arial", fillColor: "#fff" }
},
node: {
radius: 0.7,
fillColor: "rgba(0, 0, 0, 0.5)",
lineColor: "rgba(255, 255, 255, 0)"
},
nodeLabel: {
textStyle: { font: "11px Arial", fillColor: "#fff" }
},
nodeStyleFunction: function (node) {
node.label = "";
if (node.id == startNodeId || node.id == endNodeId) {
node.label = node.data.name;
node.radius = 1;
node.fillColor = "rgba(0, 0, 0, 0.9)";
node.labelStyle.backgroundStyle.fillColor = "rgba(0, 0, 0, 0.9)";
node.labelStyle.padding = 2;
node.labelStyle.borderRadius = 3;
} else if (pathData) {
var isPartOfShortestDistance = pathData.distance[node.id];
var isPartOfShortestSegments = pathData.segments[node.id];
if (isPartOfShortestDistance || isPartOfShortestSegments) {
node.label = node.data.name;
node.labelStyle.backgroundStyle.fillColor = isPartOfShortestDistance ? isPartOfShortestSegments ? pathColorBoth : pathColorDistance : pathColorSegments;
}
}
},
linkStyleFunction: function (link) {
var isPartOfShortestDistance = pathData && pathData.distance[link.id];
var isPartOfShortestSegments = pathData && pathData.segments[link.id];
if (isPartOfShortestDistance || isPartOfShortestSegments) {
link.invisible = false;
var fillColor = isPartOfShortestDistance ? isPartOfShortestSegments ? pathColorBoth : pathColorDistance : pathColorSegments;
link.fillColor = fillColor;
link.labelStyle.backgroundStyle.fillColor = fillColor;
if (link_chooser.selectedIndex === 1 /*duration*/) {
var duration = parseFloat(link.data.duration_km);
var duration_hours = Math.floor(duration);
var duration_minutes = (duration - duration_hours) * 60;
link.label = duration_hours > 0 ? duration_hours + 'h ' : '';
link.label += duration_minutes > 0 ? Math.ceil(duration_minutes) + 'min' : '';
} else {
link.label = link.data.distance_km + "\u00A0km";
}
}
}
}
}
],
events: {
onHoverChange: function (event) {
//if at least one click has been made, enable hover functionality
if(!initialClick) {
return;
}
if (startNodeId && event.hoverNode && startNodeId !== event.hoverNode.id) {
// the style for the currently selected nodes and links have to be recalculated
recalculatePathStyle();
endNodeId = event.hoverNode.id;
if (lastEndNodeId !== endNodeId) {
pathData = computePath();
// the hoverChange event might be called even if the node does not change (for example the label is hovered)
// and also it is likely that the user might hover the same node multiple times.
// thus the calculated path is cached.
lastEndNodeId = endNodeId;
lastPathData = pathData;
} else {
pathData = lastPathData;
}
// the style for the newly selected nodes and links have to be recalculated
recalculatePathStyle();
} else if (endNodeId) {
// the style for the currently selected nodes and links have to be recalculated
recalculatePathStyle();
endNodeId = null;
pathData = null;
}
},
onClick: function (event) {
initialClick = true;
if (event.clickNode) {
// the style for the start node must be recalculated
chart.updateStyle([startNodeId, event.clickNode.id]);
startNodeId = event.clickNode.id;
}
}
},
};
chart = new GeoChart(options);
setTimeout(function() {
pathData = computePath();
recalculatePathStyle();
},3000);