Public
Edited
Nov 18, 2021
Insert cell
# Network: Where do narrow/wide body aircraft fly?
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// mapC(data2, "Narrow")
Insert cell
// aircraft = mapC(data2)
Insert cell
Insert cell
// map(data)
Insert cell
Insert cell
Insert cell
Insert cell
forceGraph(data2)
Insert cell
forceGraph = (data) => {
const w2 = width,
h2 = height,
nodeRadius = 5;

const ctx = DOM.context2d(width, height*2);
const canvas = ctx.canvas;

let lExtents = []
let rExtents = []
let linkExtents = []
let setExtents = []
let ordinal = ['louvain', 'region']
let continuous = ['pagerank', 'degree', 'between', "occurence"]
let log = ["pagerank"];
let radiusFxn;
let colors;
data.nodes.forEach((row) => {
rExtents.push(row.volume);
});
data.links.forEach((row) => {
linkExtents.push(row.volume);
});
let extentDomains = ({'occurence': rExtents})

const linkWidth = d3.scaleLinear().domain(d3.extent(linkExtents)).range([1, lWidth]);
colors = d3.scaleOrdinal().domain(lExtents).range(d3[colorScales[colorMetric]])

if (log.includes(radiusMetric)) {
radiusFxn = d3.scaleSequentialLog().domain(d3.extent(extentDomains[radiusMetric])).range(rNode)
} else {
radiusFxn = d3.scaleLinear().domain(d3.extent(extentDomains[radiusMetric])).range(rNode)
}
let linkDistanceFxn = d3.scaleLinear().domain(d3.extent(linkExtents)).range([2, 1000
])

function forceSimulation(width, height) {
return d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-150))
.force('collision', d3.forceCollide().radius(function(d) {
return d3.scalePow().domain(extentDomains[radiusMetric]).range(rNode)(d[radiusMetric]);
}))
.force("link", d3.forceLink().id(d => d.id).distance(d => {return linkDistanceFxn(d.volume)}))
.velocityDecay(0.3);
}
const simulationDurationInMs = 20000;
let startTime = Date.now();
let endTime = startTime + simulationDurationInMs;

const simulation = forceSimulation(width/2, height/2);
let transform = d3.zoomIdentity;
// The simulation will alter the input data objects so make
// copies to protect the originals.
let nodes = data.nodes.map(d => Object.assign({}, d));
let edges = data.links.map(d => Object.assign({}, d));
console.log(nodes.length, edges.length);
d3.select(canvas)
.call(d3.drag()
// Must set this in order to drag nodes. New in v5?
.container(canvas)
.subject(dragSubject)
.on('start', dragStarted)
.on('drag', dragged)
.on('end', dragEnded))
.call(d3.zoom()
.scaleExtent([1/100000, 1])
.on("zoom", ({transform}) => zoomed(transform)));
simulation.nodes(nodes)
.on("tick",simulationUpdate);
simulation.force("link")
.links(edges);

function zoomed(newTransform) {
transform = newTransform;
simulationUpdate();
}
zoomed(d3.zoomIdentity);
/** Find the node that was clicked, if any, and return it. */
function dragSubject() {
const x = transform.invertX(d3.event.x),
y = transform.invertY(d3.event.y);
const node = findNode(nodes, x, y, nodeRadius);
if (node) {
node.x = transform.applyX(node.x);
node.y = transform.applyY(node.y);
}
// else: No node selected, drag container
return node;
}

function dragStarted() {
if (!d3.event.active) {
simulation.alphaTarget(0.3).restart();
}
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}

function dragged() {
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}

function dragEnded() {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}

function simulationUpdate() {
ctx.save();
ctx.clearRect(0, 0, width, height*2);
ctx.translate(transform.x, transform.y);
ctx.scale(transform.k, transform.k);
let linkColor = aircraftColors[flightMetric];

// Draw edges
edges.forEach(function(d) {
if (curves) {
const l = Math.sqrt(Math.pow(d.target.x - d.source.x, 2) + Math.pow(d.target.y - d.source.y, 2)); // line length

const a = Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x); // line angle
const e = l * 0.5; // control point distance

const cp = { // control point
x: (d.source.x + d.target.x) / 2 + e * Math.cos(a - Math.PI / 2),
y: (d.source.y + d.target.y) / 2 + e * Math.sin(a - Math.PI / 2)
};
ctx.beginPath();
ctx.moveTo(d.source.x, d.source.y);
ctx.quadraticCurveTo(cp.x, cp.y, d.target.x, d.target.y);
} else {
ctx.beginPath();
ctx.moveTo(d.source.x, d.source.y);
ctx.lineTo(d.target.x, d.target.y);
}
ctx.lineWidth = linkWidth(d.occurence);
ctx.strokeStyle = `rgba(${linkColor[0]}, ${linkColor[1]}, ${linkColor[2]}, ${opacity})`;
ctx.stroke();
});

// Draw nodes
nodes.forEach(function(d, i) {
ctx.beginPath();
// Node fill
console.log('+++', radiusFxn(d['volume']));
ctx.moveTo(d.x + radiusFxn(d['volume']), d.y);
ctx.arc(d.x, d.y, radiusFxn(d['volume']), 0, 2 * Math.PI);
console.log('+++', d[colorMetric]);
ctx.fillStyle = colors(d[colorMetric])
//ctx.fillStyle = 'green'
ctx.fill();
// Node outline
ctx.strokeStyle = 'white'
ctx.lineWidth = '3'
ctx.stroke();

});
// Draw nodes
nodes.forEach(function(d, i) {
if (d.volume > threshold) {
ctx.fillStyle = 'black';
ctx.font = 12 + 'px sans-serif';
ctx.fillText(d.id, d.x, d.y);
ctx.strokeStyle = 'white'
}
});
ctx.restore();
}
return canvas;
}
Insert cell
linkDistanceFxn = d3.scaleLinear().domain([20,1]).range([2, 10
])
Insert cell
function findNode(nodes, x, y, radius) {
const rSq = radius * radius;
let i;
for (i = nodes.length - 1; i >= 0; --i) {
const node = nodes[i],
dx = x - node.x,
dy = y - node.y,
distSq = (dx * dx) + (dy * dy);
if (distSq < rSq) {
return node;
}
}
// No node selected
return undefined;
}
Insert cell
# Data
Insert cell
data.links[0]
Insert cell
data = FileAttachment("2016_intl_airports_network_pruned_community.json").json()
Insert cell
data2 = FileAttachment("aircraft-network.json").json()
Insert cell
colorScales = ({'louvain': ["#01c472", "#c44a89", "#2cf52b", "#b859e4", "#9dea19", "#501bbf", "#749d3c", "#e313ee", "#1c5f1e", "#f6b0ec", "#7f2b04", "#34daea", "#e52740", "#3e8e9d", "#613b4f", "#b5e0a4", "#1f4196", "#e49c4f", "#5281e3", "#d6dc44", "#fb0998", "#074d65", "#dad2de", "#694e0b", "#a67495", '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000'],
'pagerank': 'interpolateBrBG',
'degree': ['steelblue', 'coral'],
'between': ['goldenrod', 'green'],
'region': 'schemePaired'})
Insert cell
aircraftTypes = ({"Narrow": "n", "Wide": "w", "Jumbo": "j"})
Insert cell
aircraftColors = ({"Narrow": [254,197,187], "Wide": [254,200,154], "Jumbo": [216,226,220]})
Insert cell
Insert cell
height = 600
Insert cell
width=1200
Insert cell
import {p5} from "@tmcw/p5";
Insert cell
import { zoom } from "@d3/versor-zooming"
Insert cell
import {projectionInput} from "@d3/projection-comparison"
Insert cell
projections = require("d3-geo@1", "d3-geo-projection@2")
Insert cell
trueProjection = projections.geoNaturalEarth()
.scale(width / 1.85 / Math.PI)
.translate([width / 2, height / 2])
.center([0, 0])
Insert cell
trueProjection([data.nodes[0].long, data.nodes[0].lat]);
Insert cell
geoPath = d3.geoPath()
.projection(trueProjection);
Insert cell
{// Map and projection
var projection = d3.geoMercator()
.scale(85)
.translate([width/2, height/2*1.3])
var geoPath = d3.geoPath()
.projection(trueProjection);
var link = {type: "LineString", coordinates: [[25.1802997589, 35.3396987915], [26.101299285888672, 35.21609878540039]]} // Change these data to see ho the great circle reacts
// A path generator
return geoPath(link);
}
Insert cell
graticule = d3.geoGraticule10()
Insert cell
color_background = "#white"//"rgb(73, 99, 44)"//"#CC071E"
Insert cell
color_land = "#f4f0ec"
Insert cell
color_outline = "#e8e8e8"
Insert cell
import {world} from "@d3/world-airports";
Insert cell
import {land} from "@d3/world-airports";
Insert cell
outline = ({type: "Sphere"})
Insert cell
import {slider} from "@jashkenas/inputs"

Insert cell
import {rangeSlider} from '@mootari/range-slider'
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more