import * as d3 from "d3"; import { h, Fragment } from 'start-dom-jsx' // const projection = d3.geoAitoff(); function degreesToRadians(degrees) { return (degrees * Math.PI) / 180; } function latLonToOffsets(latitude, longitude, mapWidth, mapHeight) { const FE = 180; // false easting const radius = mapWidth / (2 * Math.PI); const latRad = degreesToRadians(latitude); const lonRad = degreesToRadians(longitude + FE); const x = lonRad * radius; const yFromEquator = radius * Math.log(Math.tan(Math.PI / 4 + latRad / 2)); const y = mapHeight / 2 - yFromEquator; return { x, y }; } export function viz(vizEl) { console.log(d3) // vizEl.appendChild(

Hello

); fetch("data/parsed_requests.json") .then((response) => response.json()) .then(start) .catch((error) => { console.error(error); // alert("Data could not be loaded.") }); // d3.queue() // .defer(d3.json, "data/parsed_requests.json") // World shape // .await(start) } function lerp(X, Y, t) { return X * t + Y * (1 - t) } function inverse_lerp(a, b, x) { return (x - a) / (b - a); } function start(parsed_requests) { const nodes = parsed_requests.nodes; const edges = parsed_requests.edges; const svg = d3.select('svg'); const g_libraries = svg.append("g").attr("id", "libraries"); const g_movements = svg.append("g").attr("id", "movements"); const width = +svg.attr("width"), height = +svg.attr("height"); // let libraries = {} // console.log(nodes, edges) const valid_nodes = nodes.filter((n) => n.lat && n.lon).map((node) => { const { x: x1, y: y1 } = latLonToOffsets(node.lat, node.lon, 100000, 100000) console.log(x1, y1) const x2 = inverse_lerp(1358, 1380, x1); const y2 = inverse_lerp(32862, 32900, y1); const margin = 200; const x = x2 * (width - 2 * margin) + margin; const y = y2 * (height - 2 * margin) + margin; // console.log(x2, y2) // console.log(node) node['x'] = x; node['y'] = y; return node }) const nodeMap = Object.fromEntries(valid_nodes.map(d => [d['name'], d])); const links = edges.filter(l => nodeMap[l['Owning Library Name']] && nodeMap[l['Pickup Location']]).map((l, idx) => { l.source = nodeMap[l['Owning Library Name']]; l.target = nodeMap[l['Pickup Location']]; l.nr = idx; return l; }); // .filter((link, index, self) => // // remove incidental duplicates // index === self.findIndex((l) => ( // l.source.id === link.source.id && l.target.id === link.target.id // )) // ); // Reformat the list of link. Note that columns in csv file are called long1, long2, lat1, lat2 // var link = [] // edges.forEach(function (row) { // source = [+row.long1, +row.lat1] // target = [+row.long2, +row.lat2] // topush = { type: "LineString", coordinates: [source, target] } // link.push(topush) // }) // Add the path const libraries = g_libraries.selectAll(".library"); libraries.data(valid_nodes, d => d.code) .join((enter) => { let group = enter.append("g") // .attr("class", getClasses) // .attr("id", (n) => getIdForTitle(n.fulltext)); .attr("id", (n) => n.code) .attr("transform", (n) => `translate(${n.x}, ${n.y})`); // group.on("click", (evt, n) => { // evt.stopPropagation(); this.selectNode(n); // }); // group.on("mouseover", (evt, n) => { // this.hoverNode(evt, n); // }); // group.on("mouseout", (evt, n) => { // this.endHoverNode(n); // }); group.append('circle').attr("r", 5 /*this.nodeSize*/); // group.append('path') // .attr('d', (n) => { // return getSymbolForNode(n)(n); // }) var nodeTitle = group.append('text').attr("class", "nodeTitle").attr("y", "4").attr('x', 5).text((n) => n.name); return group }) const movements_a = g_movements.selectAll(".movement"); const movements = movements_a .data(links) .join( enter => { let group = enter.append("g") .attr("class", (l) => "link " /* TODO type of movement */) .attr("id", (l) => `link${l.nr}`); group.append("path") // .attr("marker-end", "url(#arrowHead)") .attr('id', (d, i) => 'linkpath_' + i) // .on("mouseover", (ev, link) => { // d3.select(ev.target).classed('hover', true); // const nodes = document.getElementsByClassName('node'); // for (let n of nodes) { // const d = d3.select(n).datum(); // if (d == link.target || d == link.source) { // n.classList.add('linkHover'); // } // } // this.showRelationTooltip(link, ev); // }).on("mouseout", (ev, link) => { // this.hideTooltip(); // d3.select(ev.target).classed('hover', false); // const nodes = document.getElementsByClassName('linkHover'); // while (nodes.length) { // nodes[0].classList.remove('linkHover'); // } // }).on("click", (ev, link) => { // ev.stopPropagation(); // this.selectNode(link.source); // }) ; // group.filter((l) => l.name != "City").append("text").attr("class", "labelText").text(function (l) { // return l.name; // }); // group.append("text") // .attr("class", "labelText") // .attr("dx", 20) // .attr("dy", 0) // .style("fill", "red") // .append("textPath") // .attr("xlink:href", function (d, i) { return "#linkid_" + i; }) // .attr("startOffset","50%") // .text((d,i) => d.name ); return group; } ) ; movements.each(function (l) { let sourceX, targetX, midX, dx, dy, angle; // This mess makes the arrows exactly perfect. // thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4 if (l.source.x < l.target.x) { sourceX = l.source.x; targetX = l.target.x; } else if (l.target.x < l.source.x) { targetX = l.target.x; sourceX = l.source.x; } else if (l.target.isCircle) { targetX = sourceX = l.target.x; } else if (l.source.isCircle) { targetX = sourceX = l.source.x; } else { midX = (l.source.x + l.target.x) / 2; if (midX > l.target.x) { midX = l.target.x; } else if (midX > l.source.x) { midX = l.source.x; } else if (midX < l.target.x) { midX = l.target.x; } else if (midX < l.source.x) { midX = l.source.x; } targetX = sourceX = midX; } dx = targetX - sourceX; dy = l.target.y - l.source.y; angle = Math.atan2(dx, dy); var srcSize = 5; //_mapGraph.getSizeForNode(l.source); var tgtSize = 5; //_mapGraph.getSizeForNode(l.target); // Compute the line endpoint such that the arrow // it not in the center, but rather slightly out of it // use a small ofset for the angle to compensate roughly for the curve l.sourceX = sourceX + Math.sin(angle+.5) * srcSize; l.targetX = targetX - Math.sin(angle-.5) * tgtSize; l.sourceY = l.source.y + Math.cos(angle+.5) * srcSize; l.targetY = l.target.y - Math.cos(angle-.5) * tgtSize; // find radius of arc based on distance between points // add a jitter to spread out the lines when links are stacked const dr = Math.sqrt(dx * dx + dy * dy) * (.7 + Math.random()*.6); // "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y let rel = d3.select(this); rel.select("path") .attr('d', `M ${l.sourceX},${l.sourceY} A ${dr},${dr} 0 0,1 ${l.targetX},${l.targetY}`) }) console.log(libraries) }