export function svgPath(projection, graph, isArea) { // Explanation of magic numbers: // "padding" here allows space for strokes to extend beyond the viewport, // so that the stroke isn't drawn along the edge of the viewport when // the shape is clipped. // // When drawing lines, pad viewport by 5px. // When drawing areas, pad viewport by 65px in each direction to allow // for 60px area fill stroke (see ".fill-partial path.fill" css rule) var cache = {}; var padding = isArea ? 65 : 5; var viewport = projection.clipExtent(); var paddedExtent = [ [viewport[0][0] - padding, viewport[0][1] - padding], [viewport[1][0] + padding, viewport[1][1] + padding] ]; var clip = d3_geoIdentity().clipExtent(paddedExtent).stream; var project = projection.stream; var path = d3_geoPath() .projection({stream: function(output) { return project(clip(output)); }}); var svgpath = function(entity) { if (entity.id in cache) { return cache[entity.id]; } else { return cache[entity.id] = path(entity.asGeoJSON(graph)); } }; svgpath.geojson = path; return svgpath; }
function drawLabels(selection, textClass, data) { var labelPath = d3_geoPath(projection); var labelData = data.filter(function(d) { return _showLabels && d.properties && (d.properties.desc || d.properties.name); }); var labels = selection.selectAll('text.' + textClass) .data(labelData, featureKey); // exit labels.exit() .remove(); // enter/update labels = labels.enter() .append('text') .attr('class', function(d) { return textClass + ' ' + featureClasses(d); }) .merge(labels) .text(function(d) { return d.properties.desc || d.properties.name; }) .attr('x', function(d) { var centroid = labelPath.centroid(d); return centroid[0] + 11; }) .attr('y', function(d) { var centroid = labelPath.centroid(d); return centroid[1]; }); }
componentWillMount() { const projection = geoMercator(); const pathGenerator = geoPath().projection(projection) var dimensionsArray = pathGenerator.bounds(this.state.geojson); var xAspect = Math.abs(dimensionsArray[0][0] - dimensionsArray[1][0]); var yAspect = Math.abs(dimensionsArray[0][1] - dimensionsArray[1][1]); var aspectRatio = xAspect / yAspect; console.log('aspect ratio' + aspectRatio); var height = 60 / aspectRatio * 1.2; this.setState({ ...this.state, svgHeight: height }) console.log(pathGenerator(this.state.geojson)); console.log(pathGenerator.bounds(this.state.geojson)) }
function drawCollisionBoxes(selection, rtree, which) { var classes = 'debug ' + which + ' ' + (which === 'debug-skipped' ? 'orange' : 'yellow'); var gj = []; if (context.getDebug('collision')) { gj = rtree.all().map(function(d) { return { type: 'Polygon', coordinates: [[ [d.minX, d.minY], [d.maxX, d.minY], [d.maxX, d.maxY], [d.minX, d.maxY], [d.minX, d.minY] ]]}; }); } var boxes = selection.selectAll('.' + which) .data(gj); // exit boxes.exit() .remove(); // enter/update boxes.enter() .append('path') .attr('class', classes) .merge(boxes) .attr('d', d3_geoPath()); }
constructor(el, { area = Country.County } = {}) { mixin(new Events, this); this.area = area; this._areas = []; const [x, y] = [400, 560]; const svgEl= select(el) .append('svg:svg') .attr('viewBox', `0 0 ${x} ${y}`) .attr('preserveAspectRatio', 'xMidYMid') .attr('width', '100%') .attr('height', '100%'); const projection = geoMercator() .scale(1000) .translate([-110, 1800]); this.path = geoPath(projection); this.svg = svgEl .append('svg:g') .attr('id', 'areas'); setTimeout(() => this.load(this.area.data)); }
const renderPath = (cacheId, geography, projection, round, precision) => { if (pathCache[cacheId]) return pathCache[cacheId] const pathString = cacheId ? pathCache[cacheId] ? pathCache[cacheId] : round ? roundPath(geoPath().projection(projection)(geography), precision) : geoPath().projection(projection)(geography) : round ? roundPath(geoPath().projection(projection)(geography), precision) : geoPath().projection(projection)(geography) if (cacheId) pathCache[cacheId] = pathString return pathString }
export default (className, { topoJSONPath, topoJSONRoot, getPolygonClassName, showTooltipCallback, hideTooltipCallback, useRobinsonProjection }) => { const d3Container = d3_select(className); d3Container.node().classList.remove('-with-legend'); const containerComputedStyle = window.getComputedStyle(d3Container.node()); const width = parseInt(containerComputedStyle.width); const height = parseInt(containerComputedStyle.height); const svg = d3Container.append('svg') .attr('width', width) .attr('height', height); const geoParent = svg.append('g'); const container = geoParent.append('g'); const projection = (useRobinsonProjection === true) ? d3_geoRobinson() : d3_geoMercator(); const path = d3_geoPath() .projection(projection); d3_json(topoJSONPath, function(error, topoJSON) { const features = topojson.feature(topoJSON, topoJSON.objects[topoJSONRoot]); const polygons = container.selectAll('path') .data(features.features) .enter() .append('path') .attr('class', d => { return `polygon ${getPolygonClassName(d)}`; }) .attr('d', path); if (showTooltipCallback !== undefined) { polygons.on('mousemove', function(d) { showTooltipCallback(d, d3_event.clientX + 10, d3_event.clientY + window.scrollY + 10); } ) .on('mouseout', function() { hideTooltipCallback(); }); } const collection = { 'type': 'FeatureCollection', 'features' : features.features }; const featureBounds = path.bounds(collection); const { scale, trans } = fitGeoInside(featureBounds, width, height); container.attr('transform', [ 'translate(' + trans + ')', 'scale(' + scale + ')' ].join(' ')); container.selectAll('path').style('stroke-width', .5 / scale); }); };
return function projection() { var p = constructor(); p.type = type; p.path = geoPath().projection(p); p.copy = p.copy || function() { var c = projection(); projectionProperties.forEach(function(prop) { if (p.hasOwnProperty(prop)) c[prop](p[prop]()); }); c.path.pointRadius(p.path.pointRadius()); return c; }; return p; };
_redraw() { const pixelRatio = window.devicePixelRatio; const canvas = this.refs.overlay; const ctx = canvas.getContext('2d'); const mercator = ViewportMercator(this.props); ctx.save(); ctx.scale(pixelRatio, pixelRatio); ctx.clearRect(0, 0, this.props.width, this.props.height); function projectPoint(lon, lat) { const point = mercator.project([lon, lat]); /* eslint-disable no-invalid-this */ this.stream.point(point[0], point[1]); /* eslint-enable no-invalid-this */ } if (this.props.renderWhileDragging || !this.props.isDragging) { const transform = geoTransform({point: projectPoint}); const path = geoPath().projection(transform).context(ctx); this._drawFeatures(ctx, path); } ctx.restore(); }
export function svgLabels(projection, context) { var path = d3_geoPath(projection); var detected = utilDetect(); var baselineHack = (detected.ie || detected.browser.toLowerCase() === 'edge'); var _rdrawn = rbush(); var _rskipped = rbush(); var _textWidthCache = {}; var _entitybboxes = {}; // Listed from highest to lowest priority var labelStack = [ ['line', 'aeroway', '*', 12], ['line', 'highway', 'motorway', 12], ['line', 'highway', 'trunk', 12], ['line', 'highway', 'primary', 12], ['line', 'highway', 'secondary', 12], ['line', 'highway', 'tertiary', 12], ['line', 'highway', '*', 12], ['line', 'railway', '*', 12], ['line', 'waterway', '*', 12], ['area', 'aeroway', '*', 12], ['area', 'amenity', '*', 12], ['area', 'building', '*', 12], ['area', 'historic', '*', 12], ['area', 'leisure', '*', 12], ['area', 'man_made', '*', 12], ['area', 'natural', '*', 12], ['area', 'shop', '*', 12], ['area', 'tourism', '*', 12], ['area', 'camp_site', '*', 12], ['point', 'aeroway', '*', 10], ['point', 'amenity', '*', 10], ['point', 'building', '*', 10], ['point', 'historic', '*', 10], ['point', 'leisure', '*', 10], ['point', 'man_made', '*', 10], ['point', 'natural', '*', 10], ['point', 'shop', '*', 10], ['point', 'tourism', '*', 10], ['point', 'camp_site', '*', 10], ['line', 'name', '*', 12], ['area', 'name', '*', 12], ['point', 'name', '*', 10] ]; function blacklisted(preset) { var noIcons = ['building', 'landuse', 'natural']; return noIcons.some(function(s) { return preset.id.indexOf(s) >= 0; }); } function get(array, prop) { return function(d, i) { return array[i][prop]; }; } function textWidth(text, size, elem) { var c = _textWidthCache[size]; if (!c) c = _textWidthCache[size] = {}; if (c[text]) { return c[text]; } else if (elem) { c[text] = elem.getComputedTextLength(); return c[text]; } else { var str = encodeURIComponent(text).match(/%[CDEFcdef]/g); if (str === null) { return size / 3 * 2 * text.length; } else { return size / 3 * (2 * text.length + str.length); } } } function drawLinePaths(selection, entities, filter, classes, labels) { var paths = selection.selectAll('path') .filter(filter) .data(entities, osmEntity.key); // exit paths.exit() .remove(); // enter/update paths.enter() .append('path') .style('stroke-width', get(labels, 'font-size')) .attr('id', function(d) { return 'labelpath-' + d.id; }) .attr('class', classes) .merge(paths) .attr('d', get(labels, 'lineString')); } function drawLineLabels(selection, entities, filter, classes, labels) { var texts = selection.selectAll('text.' + classes) .filter(filter) .data(entities, osmEntity.key); // exit texts.exit() .remove(); // enter texts.enter() .append('text') .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; }) .attr('dy', baselineHack ? '0.35em' : null) .append('textPath') .attr('class', 'textpath'); // update selection.selectAll('text.' + classes).selectAll('.textpath') .filter(filter) .data(entities, osmEntity.key) .attr('startOffset', '50%') .attr('xlink:href', function(d) { return '#labelpath-' + d.id; }) .text(utilDisplayNameForPath); } function drawPointLabels(selection, entities, filter, classes, labels) { var texts = selection.selectAll('text.' + classes) .filter(filter) .data(entities, osmEntity.key); // exit texts.exit() .remove(); // enter/update texts.enter() .append('text') .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; }) .merge(texts) .attr('x', get(labels, 'x')) .attr('y', get(labels, 'y')) .style('text-anchor', get(labels, 'textAnchor')) .text(utilDisplayName) .each(function(d, i) { textWidth(utilDisplayName(d), labels[i].height, this); }); } function drawAreaLabels(selection, entities, filter, classes, labels) { entities = entities.filter(hasText); labels = labels.filter(hasText); drawPointLabels(selection, entities, filter, classes, labels); function hasText(d, i) { return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y'); } } function drawAreaIcons(selection, entities, filter, classes, labels) { var icons = selection.selectAll('use.' + classes) .filter(filter) .data(entities, osmEntity.key); // exit icons.exit() .remove(); // enter/update icons.enter() .append('use') .attr('class', 'icon ' + classes) .attr('width', '17px') .attr('height', '17px') .merge(icons) .attr('transform', get(labels, 'transform')) .attr('xlink:href', function(d) { var preset = context.presets().match(d, context.graph()); var picon = preset && preset.icon; if (!picon) { return ''; } else { var isMaki = /^maki-/.test(picon); return '#' + picon + (isMaki ? '-15' : ''); } }); } function drawCollisionBoxes(selection, rtree, which) { var classes = 'debug ' + which + ' ' + (which === 'debug-skipped' ? 'orange' : 'yellow'); var gj = []; if (context.getDebug('collision')) { gj = rtree.all().map(function(d) { return { type: 'Polygon', coordinates: [[ [d.minX, d.minY], [d.maxX, d.minY], [d.maxX, d.maxY], [d.minX, d.maxY], [d.minX, d.minY] ]]}; }); } var boxes = selection.selectAll('.' + which) .data(gj); // exit boxes.exit() .remove(); // enter/update boxes.enter() .append('path') .attr('class', classes) .merge(boxes) .attr('d', d3_geoPath()); } function drawLabels(selection, graph, entities, filter, dimensions, fullRedraw) { var wireframe = context.surface().classed('fill-wireframe'); var zoom = geoScaleToZoom(projection.scale()); var labelable = []; var renderNodeAs = {}; var i, j, k, entity, geometry; for (i = 0; i < labelStack.length; i++) { labelable.push([]); } if (fullRedraw) { _rdrawn.clear(); _rskipped.clear(); _entitybboxes = {}; } else { for (i = 0; i < entities.length; i++) { entity = entities[i]; var toRemove = [] .concat(_entitybboxes[entity.id] || []) .concat(_entitybboxes[entity.id + 'I'] || []); for (j = 0; j < toRemove.length; j++) { _rdrawn.remove(toRemove[j]); _rskipped.remove(toRemove[j]); } } } // Loop through all the entities to do some preprocessing for (i = 0; i < entities.length; i++) { entity = entities[i]; geometry = entity.geometry(graph); // Insert collision boxes around interesting points/vertices if (geometry === 'point' || (geometry === 'vertex' && isInterestingVertex(entity))) { var hasDirections = entity.directions(graph, projection).length; var markerPadding; if (!wireframe && geometry === 'point' && !(zoom >= 18 && hasDirections)) { renderNodeAs[entity.id] = 'point'; markerPadding = 20; // extra y for marker height } else { renderNodeAs[entity.id] = 'vertex'; markerPadding = 0; } var coord = projection(entity.loc); var nodePadding = 10; var bbox = { minX: coord[0] - nodePadding, minY: coord[1] - nodePadding - markerPadding, maxX: coord[0] + nodePadding, maxY: coord[1] + nodePadding }; doInsert(bbox, entity.id + 'P'); } // From here on, treat vertices like points if (geometry === 'vertex') { geometry = 'point'; } // Determine which entities are label-able var preset = geometry === 'area' && context.presets().match(entity, graph); var icon = preset && !blacklisted(preset) && preset.icon; if (!icon && !utilDisplayName(entity)) continue; for (k = 0; k < labelStack.length; k++) { var matchGeom = labelStack[k][0]; var matchKey = labelStack[k][1]; var matchVal = labelStack[k][2]; var hasVal = entity.tags[matchKey]; if (geometry === matchGeom && hasVal && (matchVal === '*' || matchVal === hasVal)) { labelable[k].push(entity); break; } } } var positions = { point: [], line: [], area: [] }; var labelled = { point: [], line: [], area: [] }; // Try and find a valid label for labellable entities for (k = 0; k < labelable.length; k++) { var fontSize = labelStack[k][3]; for (i = 0; i < labelable[k].length; i++) { entity = labelable[k][i]; geometry = entity.geometry(graph); var getName = (geometry === 'line') ? utilDisplayNameForPath : utilDisplayName; var name = getName(entity); var width = name && textWidth(name, fontSize); var p = null; if (geometry === 'point' || geometry === 'vertex') { // no point or vertex labels in wireframe mode // no vertex labels at low zooms (vertices have no icons) if (wireframe) continue; var renderAs = renderNodeAs[entity.id]; if (renderAs === 'vertex' && zoom < 17) continue; p = getPointLabel(entity, width, fontSize, renderAs); } else if (geometry === 'line') { p = getLineLabel(entity, width, fontSize); } else if (geometry === 'area') { p = getAreaLabel(entity, width, fontSize); } if (p) { if (geometry === 'vertex') { geometry = 'point'; } // treat vertex like point p.classes = geometry + ' tag-' + labelStack[k][1]; positions[geometry].push(p); labelled[geometry].push(entity); } } } function isInterestingVertex(entity) { var selectedIDs = context.selectedIDs(); return entity.hasInterestingTags() || entity.isEndpoint(graph) || entity.isConnected(graph) || selectedIDs.indexOf(entity.id) !== -1 || graph.parentWays(entity).some(function(parent) { return selectedIDs.indexOf(parent.id) !== -1; }); } function getPointLabel(entity, width, height, geometry) { var y = (geometry === 'point' ? -12 : 0); var pointOffsets = { ltr: [15, y, 'start'], rtl: [-15, y, 'end'] }; var coord = projection(entity.loc); var textPadding = 2; var offset = pointOffsets[textDirection]; var p = { height: height, width: width, x: coord[0] + offset[0], y: coord[1] + offset[1], textAnchor: offset[2] }; // insert a collision box for the text label.. var bbox; if (textDirection === 'rtl') { bbox = { minX: p.x - width - textPadding, minY: p.y - (height / 2) - textPadding, maxX: p.x + textPadding, maxY: p.y + (height / 2) + textPadding }; } else { bbox = { minX: p.x - textPadding, minY: p.y - (height / 2) - textPadding, maxX: p.x + width + textPadding, maxY: p.y + (height / 2) + textPadding }; } if (tryInsert([bbox], entity.id, true)) { return p; } } function getLineLabel(entity, width, height) { var viewport = geoExtent(context.projection.clipExtent()).polygon(); var points = graph.childNodes(entity) .map(function(node) { return projection(node.loc); }); var length = geoPathLength(points); if (length < width + 20) return; // todo: properly clip points to viewport // % along the line to attempt to place the label var lineOffsets = [50, 45, 55, 40, 60, 35, 65, 30, 70, 25, 75, 20, 80, 15, 95, 10, 90, 5, 95]; var padding = 3; for (var i = 0; i < lineOffsets.length; i++) { var offset = lineOffsets[i]; var middle = offset / 100 * length; var start = middle - width / 2; if (start < 0 || start + width > length) continue; // generate subpath and ignore paths that are invalid or don't cross viewport. var sub = subpath(points, start, start + width); if (!sub || !geoPolygonIntersectsPolygon(viewport, sub, true)) { continue; } var isReverse = reverse(sub); if (isReverse) { sub = sub.reverse(); } var bboxes = []; var boxsize = (height + 2) / 2; for (var j = 0; j < sub.length - 1; j++) { var a = sub[j]; var b = sub[j + 1]; // split up the text into small collision boxes var num = Math.max(1, Math.floor(geoVecLength(a, b) / boxsize / 2)); for (var box = 0; box < num; box++) { var p = geoVecInterp(a, b, box / num); var x0 = p[0] - boxsize - padding; var y0 = p[1] - boxsize - padding; var x1 = p[0] + boxsize + padding; var y1 = p[1] + boxsize + padding; bboxes.push({ minX: Math.min(x0, x1), minY: Math.min(y0, y1), maxX: Math.max(x0, x1), maxY: Math.max(y0, y1) }); } } if (tryInsert(bboxes, entity.id, false)) { // accept this one return { 'font-size': height + 2, lineString: lineString(sub), startOffset: offset + '%' }; } } function reverse(p) { var angle = Math.atan2(p[1][1] - p[0][1], p[1][0] - p[0][0]); return !(p[0][0] < p[p.length - 1][0] && angle < Math.PI/2 && angle > -Math.PI/2); } function lineString(points) { return 'M' + points.join('L'); } function subpath(points, from, to) { var sofar = 0; var start, end, i0, i1; for (var i = 0; i < points.length - 1; i++) { var a = points[i]; var b = points[i + 1]; var current = geoVecLength(a, b); var portion; if (!start && sofar + current >= from) { portion = (from - sofar) / current; start = [ a[0] + portion * (b[0] - a[0]), a[1] + portion * (b[1] - a[1]) ]; i0 = i + 1; } if (!end && sofar + current >= to) { portion = (to - sofar) / current; end = [ a[0] + portion * (b[0] - a[0]), a[1] + portion * (b[1] - a[1]) ]; i1 = i + 1; } sofar += current; } var result = points.slice(i0, i1); result.unshift(start); result.push(end); return result; } } function getAreaLabel(entity, width, height) { var centroid = path.centroid(entity.asGeoJSON(graph, true)); var extent = entity.extent(graph); var areaWidth = projection(extent[1])[0] - projection(extent[0])[0]; if (isNaN(centroid[0]) || areaWidth < 20) return; var preset = context.presets().match(entity, context.graph()); var picon = preset && preset.icon; var iconSize = 17; var padding = 2; var p = {}; if (picon) { // icon and label.. if (addIcon()) { addLabel(iconSize + padding); return p; } } else { // label only.. if (addLabel(0)) { return p; } } function addIcon() { var iconX = centroid[0] - (iconSize / 2); var iconY = centroid[1] - (iconSize / 2); var bbox = { minX: iconX, minY: iconY, maxX: iconX + iconSize, maxY: iconY + iconSize }; if (tryInsert([bbox], entity.id + 'I', true)) { p.transform = 'translate(' + iconX + ',' + iconY + ')'; return true; } return false; } function addLabel(yOffset) { if (width && areaWidth >= width + 20) { var labelX = centroid[0]; var labelY = centroid[1] + yOffset; var bbox = { minX: labelX - (width / 2) - padding, minY: labelY - (height / 2) - padding, maxX: labelX + (width / 2) + padding, maxY: labelY + (height / 2) + padding }; if (tryInsert([bbox], entity.id, true)) { p.x = labelX; p.y = labelY; p.textAnchor = 'middle'; p.height = height; return true; } } return false; } } // force insert a singular bounding box // singular box only, no array, id better be unique function doInsert(bbox, id) { bbox.id = id; var oldbox = _entitybboxes[id]; if (oldbox) { _rdrawn.remove(oldbox); } _entitybboxes[id] = bbox; _rdrawn.insert(bbox); } function tryInsert(bboxes, id, saveSkipped) { var skipped = false; for (var i = 0; i < bboxes.length; i++) { var bbox = bboxes[i]; bbox.id = id; // Check that label is visible if (bbox.minX < 0 || bbox.minY < 0 || bbox.maxX > dimensions[0] || bbox.maxY > dimensions[1]) { skipped = true; break; } if (_rdrawn.collides(bbox)) { skipped = true; break; } } _entitybboxes[id] = bboxes; if (skipped) { if (saveSkipped) { _rskipped.load(bboxes); } } else { _rdrawn.load(bboxes); } return !skipped; } var layer = selection.selectAll('.layer-osm.labels'); layer.selectAll('.labels-group') .data(['halo', 'label', 'debug']) .enter() .append('g') .attr('class', function(d) { return 'labels-group ' + d; }); var halo = layer.selectAll('.labels-group.halo'); var label = layer.selectAll('.labels-group.label'); var debug = layer.selectAll('.labels-group.debug'); // points drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point); drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point); // lines drawLinePaths(layer, labelled.line, filter, '', positions.line); drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line); drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line); // areas drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area); drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area); drawAreaIcons(label, labelled.area, filter, 'areaicon', positions.area); drawAreaIcons(halo, labelled.area, filter, 'areaicon-halo', positions.area); // debug drawCollisionBoxes(debug, _rskipped, 'debug-skipped'); drawCollisionBoxes(debug, _rdrawn, 'debug-drawn'); layer.call(filterLabels); } function filterLabels(selection) { var drawLayer = selection.selectAll('.layer-osm.labels'); var layers = drawLayer.selectAll('.labels-group.halo, .labels-group.label'); layers.selectAll('.nolabel') .classed('nolabel', false); var mouse = context.mouse(); var graph = context.graph(); var selectedIDs = context.selectedIDs(); var ids = []; var pad, bbox; // hide labels near the mouse if (mouse) { pad = 20; bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad }; var nearMouse = _rdrawn.search(bbox).map(function(entity) { return entity.id; }); ids.push.apply(ids, nearMouse); } // hide labels on selected nodes (they look weird when dragging / haloed) for (var i = 0; i < selectedIDs.length; i++) { var entity = graph.hasEntity(selectedIDs[i]); if (entity && entity.type === 'node') { ids.push(selectedIDs[i]); } } layers.selectAll(utilEntitySelector(ids)) .classed('nolabel', true); // draw the mouse bbox if debugging is on.. var debug = selection.selectAll('.labels-group.debug'); var gj = []; if (context.getDebug('collision')) { gj = bbox ? [{ type: 'Polygon', coordinates: [[ [bbox.minX, bbox.minY], [bbox.maxX, bbox.minY], [bbox.maxX, bbox.maxY], [bbox.minX, bbox.maxY], [bbox.minX, bbox.minY] ]] }] : []; } var box = debug.selectAll('.debug-mouse') .data(gj); // exit box.exit() .remove(); // enter/update box.enter() .append('path') .attr('class', 'debug debug-mouse yellow') .merge(box) .attr('d', d3_geoPath()); } var throttleFilterLabels = _throttle(utilCallWhenIdle(filterLabels), 100); drawLabels.observe = function(selection) { var listener = function() { throttleFilterLabels(selection); }; selection.on('mousemove.hidelabels', listener); context.on('enter.hidelabels', listener); }; drawLabels.off = function(selection) { throttleFilterLabels.cancel(); selection.on('mousemove.hidelabels', null); context.on('enter.hidelabels', null); }; return drawLabels; }
const computeGraticule = (projection, step) => geoPath().projection(projection)(geoGraticule().step(step)())
const computeOutline = (projection) => geoPath().projection(projection)(geoGraticule().outline())
/** * Component for all projections. */ export default function Projection({ data, projection = 'mercator', projectionFunc, clipAngle, clipExtent, scale, translate, center, rotate, precision, fitExtent, fitSize, centroid, graticule, graticuleLines, graticuleOutline, className, innerRef, pointRadius, ...restProps }) { const currProjection = projectionMapping[projection](); if (clipAngle) currProjection.clipAngle(clipAngle); if (clipExtent) currProjection.clipExtent(clipExtent); if (scale) currProjection.scale(scale); if (translate) currProjection.translate(translate); if (center) currProjection.center(center); if (rotate) currProjection.rotate(rotate); if (precision) currProjection.rotate(precision); if (fitExtent) currProjection.fitExtent(...fitExtent); if (fitSize) currProjection.fitSize(...fitSize); const path = geoPath().projection(currProjection); if (pointRadius) path.pointRadius(pointRadius); return ( <Group className={`vx-geo`}> {graticule && !graticule.foreground && <Graticule graticule={g => path(g)} {...graticule} />} {graticuleLines && !graticuleLines.foreground && <Graticule lines={g => path(g)} {...graticuleLines} />} {graticuleOutline && !graticuleOutline.foreground && <Graticule outline={g => path(g)} {...graticuleOutline} />} {data.map((feature, i) => { let c; if (centroid) c = path.centroid(feature); return ( <g key={`${projection}-${i}`}> <path className={cx(`vx-geo-${projection}`, className)} d={path(feature)} ref={innerRef && innerRef(feature, i)} {...additionalProps(restProps, { ...feature, index: i, centroid: c, })} /> {centroid && centroid(c, feature)} </g> ); })} {/* TODO: Maybe find a different way to pass projection function to use for example invert */} {projectionFunc && projectionFunc(currProjection)} {graticule && graticule.foreground && <Graticule graticule={g => path(g)} {...graticule} />} {graticuleLines && graticuleLines.foreground && <Graticule lines={g => path(g)} {...graticuleLines} />} {graticuleOutline && graticuleOutline.foreground && <Graticule outline={g => path(g)} {...graticuleOutline} />} </Group> ); }
const app = (data) => { const $link = $('#link'); const $stateMap = $('#state_map'); const max = {}; //// maximum values for primary areas const min = {}; const rgbs = {}; const scales = {}; //// scaling functions by area const colorcode = { //// color multipliers r: 0.2, g: 1, b: 0.1 }; var w = 400; const windowWidth = $(window).width(); if (windowWidth < w) { w = windowWidth * 0.9; } var h = w * 0.625; renderOptions(); $stateMap.html('<strong>Loading state map '+LOADING_ICON+'</strong>'); //// build max map Object.keys(data).forEach(state => { Object.keys(data[state]).forEach(type => { if (!(type in max) || parseInt(data[state][type]) > parseInt(max[type])) { max[type] = data[state][type]; } if (!(type in min) || parseInt(data[state][type]) < parseInt(min[type])) { min[type] = data[state][type]; } }); }); //// Build basic scaling functions Object.keys(max).forEach(type => { scales[type] = scaleLinear() .domain([0, max[type]]) .range([255, 0]) }); const getAreas = () => $('input[name=option]:checked').map(function() { return $(this).val(); }).get(); const areas = getAreas(); //// set up tooltip const tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0); const showTooltip = d => { tooltip.transition() .duration(100) .style("opacity", .9); tooltip.html(function() { const areas = getAreas(); const name = d.properties.NAME; return getTooltip(name, areas); }) .style("left", (currentEvent.pageX) + "px") .style("top", (currentEvent.pageY - 28) + "px"); }; const hideTooltip = d => { tooltip.transition() .duration(500) .style("opacity", 0); } //Define map projection const projection = geoAlbersUsa() .translate([w/2, h/2]) .scale([500]); //Define path generator const path = geoPath() .projection(projection); const updateLink = areas => { if (!areas || !areas.length) { $link.html(`Select at least one checkbox to see state tax comparisons.`); return; } const $container = $('#options-container'); let query = $container.serialize(); if (areas.includes('total')) { areas = ['total']; query = 'option=total'; } $link.html(`Showing results for <a href="/statetax?${query}">${areas.join('+')}</a>`); }; //Load in GeoJSON data json('/data/statetax/states.min.json', function(json) { $stateMap.html(''); const areas = getAreas(); updateLink(areas); //Create SVG element const svg = select("#state_map") .append("svg") .attr("width", w) .attr("height", h); // Bind data and create one path per GeoJSON feature svg.selectAll("path") .data(json.features) .enter() .append("path") .attr("d", path) .attr('class', 'state') .attr('id', function(d) { return d.properties.NAME; }) .style('fill', datum => { return getRgb(datum.properties.NAME, areas); }) .on('click', function(data, i) { const key = data.properties.NAME; showTooltip(data); }) .on('mouseover', showTooltip) .on('mouseout', hideTooltip) }); //// Change option $('#interface-container').on('click', 'input[name=option]', function(e) { const areas = getAreas(); updateLink(areas); const $states = $states || $('.state'); $states.each(function(i, elem) { const name = $(this).attr('id'); $(this).css('fill', getRgb(name, areas)); }); }); const getScale = (areas, key) => { if (!scales[key]) { var mx = areas.reduce(function(p, c) { return p + parseInt(max[c]); }, 0); var mn = areas.reduce(function(p, c) { return p + parseInt(min[c]); }, 0); scales[key] = scaleLinear() .domain([mn, mx]) .range([255, 0]); } return scales[key]; } const getRgb = (name, area) => { if (area.includes('total')) { area = ['total']; } const key = area.join(':'); if (!(name in data)) return {}; const rgb = {}; const areas = getAreas(); //// create area in scales if it doesn't exist const scale = getScale(areas, key); //// rgb if (name in rgbs && key in rgbs[name]) { return rgbs[name][key]; } else { var value = areas.reduce(function(p, c) { return p + parseInt(data[name][c]); }, 0); for (var color in colorcode) { rgb[color] = Math.floor(scale(value)*colorcode[color]); } var rgbstr = 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'; if (!rgbs[name]) { rgbs[name] = {}; } rgbs[name][key] = rgbstr; return rgbstr; } }; const getTooltip = (name, areas) => { let value = name; if (areas.includes('total')) { areas = ['total']; } if (name in data) { const total = areas.reduce(function(p, c) { return p + parseInt(data[name][c]); }, 0); value += ' - ' + dollars(total); } return value; }; }
function filterLabels(selection) { var drawLayer = selection.selectAll('.layer-osm.labels'); var layers = drawLayer.selectAll('.labels-group.halo, .labels-group.label'); layers.selectAll('.nolabel') .classed('nolabel', false); var mouse = context.mouse(); var graph = context.graph(); var selectedIDs = context.selectedIDs(); var ids = []; var pad, bbox; // hide labels near the mouse if (mouse) { pad = 20; bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad }; var nearMouse = _rdrawn.search(bbox).map(function(entity) { return entity.id; }); ids.push.apply(ids, nearMouse); } // hide labels on selected nodes (they look weird when dragging / haloed) for (var i = 0; i < selectedIDs.length; i++) { var entity = graph.hasEntity(selectedIDs[i]); if (entity && entity.type === 'node') { ids.push(selectedIDs[i]); } } layers.selectAll(utilEntitySelector(ids)) .classed('nolabel', true); // draw the mouse bbox if debugging is on.. var debug = selection.selectAll('.labels-group.debug'); var gj = []; if (context.getDebug('collision')) { gj = bbox ? [{ type: 'Polygon', coordinates: [[ [bbox.minX, bbox.minY], [bbox.maxX, bbox.minY], [bbox.maxX, bbox.maxY], [bbox.minX, bbox.maxY], [bbox.minX, bbox.minY] ]] }] : []; } var box = debug.selectAll('.debug-mouse') .data(gj); // exit box.exit() .remove(); // enter/update box.enter() .append('path') .attr('class', 'debug debug-mouse yellow') .merge(box) .attr('d', d3_geoPath()); }
function drawDebug(selection) { var showsTile = context.getDebug('tile'), showsCollision = context.getDebug('collision'), showsImagery = context.getDebug('imagery'), showsImperial = context.getDebug('imperial'), showsDriveLeft = context.getDebug('driveLeft'), path = d3_geoPath(projection); var debugData = []; if (showsTile) { debugData.push({ class: 'red', label: 'tile' }); } if (showsCollision) { debugData.push({ class: 'yellow', label: 'collision' }); } if (showsImagery) { debugData.push({ class: 'orange', label: 'imagery' }); } if (showsImperial) { debugData.push({ class: 'cyan', label: 'imperial' }); } if (showsDriveLeft) { debugData.push({ class: 'green', label: 'driveLeft' }); } var legend = d3_select('#content') .selectAll('.debug-legend') .data(debugData.length ? [0] : []); legend.exit() .remove(); legend = legend.enter() .append('div') .attr('class', 'fillD debug-legend') .merge(legend); var legendItems = legend.selectAll('.debug-legend-item') .data(debugData, function(d) { return d.label; }); legendItems.exit() .remove(); legendItems.enter() .append('span') .attr('class', function(d) { return 'debug-legend-item ' + d.class; }) .text(function(d) { return d.label; }); var layer = selection.selectAll('.layer-debug') .data(showsImagery || showsImperial || showsDriveLeft ? [0] : []); layer.exit() .remove(); layer = layer.enter() .append('g') .attr('class', 'layer-debug') .merge(layer); var extent = context.map().extent(), dataImagery = data.imagery || [], availableImagery = showsImagery && multipolygons(dataImagery.filter(function(source) { if (!source.polygon) return false; return source.polygon.some(function(polygon) { return geoPolygonIntersectsPolygon(polygon, extent, true); }); })); var imagery = layer.selectAll('path.debug-imagery') .data(showsImagery ? availableImagery : []); imagery.exit() .remove(); imagery.enter() .append('path') .attr('class', 'debug-imagery debug orange'); var imperial = layer .selectAll('path.debug-imperial') .data(showsImperial ? [dataImperial] : []); imperial.exit() .remove(); imperial.enter() .append('path') .attr('class', 'debug-imperial debug cyan'); var driveLeft = layer .selectAll('path.debug-drive-left') .data(showsDriveLeft ? [dataDriveLeft] : []); driveLeft.exit() .remove(); driveLeft.enter() .append('path') .attr('class', 'debug-drive-left debug green'); // update layer.selectAll('path') .attr('d', path); }
function redraw() { clearTimeout(timeoutId); if (isHidden) return; updateProjection(); var dMini = utilGetDimensions(wrap); var zMini = geoScaleToZoom(projection.scale()); // setup tile container tiles = wrap .selectAll('.map-in-map-tiles') .data([0]); tiles = tiles.enter() .append('div') .attr('class', 'map-in-map-tiles') .merge(tiles); // redraw background backgroundLayer .source(context.background().baseLayerSource()) .projection(projection) .dimensions(dMini); var background = tiles .selectAll('.map-in-map-background') .data([0]); background.enter() .append('div') .attr('class', 'map-in-map-background') .merge(background) .call(backgroundLayer); // redraw overlay var overlaySources = context.background().overlayLayerSources(); var activeOverlayLayers = []; for (var i = 0; i < overlaySources.length; i++) { if (overlaySources[i].validZoom(zMini)) { if (!overlayLayers[i]) overlayLayers[i] = rendererTileLayer(context); activeOverlayLayers.push(overlayLayers[i] .source(overlaySources[i]) .projection(projection) .dimensions(dMini)); } } var overlay = tiles .selectAll('.map-in-map-overlay') .data([0]); overlay = overlay.enter() .append('div') .attr('class', 'map-in-map-overlay') .merge(overlay); var overlays = overlay .selectAll('div') .data(activeOverlayLayers, function(d) { return d.source().name(); }); overlays.exit() .remove(); overlays = overlays.enter() .append('div') .merge(overlays) .each(function(layer) { d3_select(this).call(layer); }); var dataLayers = tiles .selectAll('.map-in-map-data') .data([0]); dataLayers.exit() .remove(); dataLayers = dataLayers.enter() .append('svg') .attr('class', 'map-in-map-data') .merge(dataLayers) .call(dataLayer) .call(debugLayer); // redraw viewport bounding box if (gesture !== 'pan') { var getPath = d3_geoPath(projection); var bbox = { type: 'Polygon', coordinates: [context.map().extent().polygon()] }; viewport = wrap.selectAll('.map-in-map-viewport') .data([0]); viewport = viewport.enter() .append('svg') .attr('class', 'map-in-map-viewport') .merge(viewport); var path = viewport.selectAll('.map-in-map-bbox') .data([bbox]); path.enter() .append('path') .attr('class', 'map-in-map-bbox') .merge(path) .attr('d', getPath) .classed('thick', function(d) { return getPath.area(d) < 30; }); } }
render() { const { animation, bandwidth, className, colorRange, data, innerHeight, innerWidth, marginLeft, marginTop, style } = this.props; if (!data || !innerWidth || !innerHeight) { return null; } if (animation) { return ( <Animation {...this.props} animatedProps={ANIMATED_SERIES_PROPS}> <ContourSeries {...this.props} animation={null} /> </Animation> ); } const x = this._getAttributeFunctor('x'); const y = this._getAttributeFunctor('y'); const contouredData = contourDensity() .x(d => x(d)) .y(d => y(d)) .size([innerWidth, innerHeight]) .bandwidth(bandwidth)(data); const geo = geoPath(); const {min, max} = getDomain(contouredData); const colorScale = scaleLinear() .domain([min, max]) .range(colorRange || CONTINUOUS_COLOR_RANGE); return ( <g className={`${predefinedClassName} ${className}`} transform={`translate(${marginLeft},${marginTop})`} > {contouredData.map((polygon, index) => { return ( <path className="rv-xy-plot__series--contour-line" key={`rv-xy-plot__series--contour-line-${index}`} d={geo(polygon)} style={{ fill: colorScale(polygon.value), ...style }} /> ); })} </g> ); }
const getPointAtLength = require('point-at-length'); const cloneDeep = require('lodash/cloneDeep'); const { geoPath } = require('d3-geo'); const { GEO, registerConnector } = require('../data-set'); const geoPathGenerator = geoPath(); function GeoJSONConnector(data, options, dataView) { dataView.dataType = GEO; const features = cloneDeep(data.features); // pre-process features.forEach(feature => { feature.name = feature.properties.name; feature.longitude = []; feature.latitude = []; const pathData = feature.pathData = geoPathGenerator(feature); const points = getPointAtLength(pathData); points._path.forEach(point => { feature.longitude.push(point[1]); feature.latitude.push(point[2]); }); const centroid = geoPathGenerator.centroid(feature); feature.centroidX = centroid[0]; feature.centroidY = centroid[1]; });
function render(mapEl, snapData, world) { const width = mapEl.property("clientWidth"); const height = width * 0.5; // some offset position center of the map properly const offset = width * 0.1; const projection = geoNaturalEarth1() .scale(width * 0.2) .translate([width / 2, (height + offset) / 2]) .precision(0.1); // rotate not to split Asia projection.rotate([-10, 0]); const path = geoPath().projection(projection); // clean up HTML before rendering map mapEl.html(""); const svg = mapEl .append("svg") .attr("width", width) .attr("height", height); const tooltip = mapEl .append("div") .attr("class", "snapcraft-territories__tooltip u-no-margin"); const tooltipMsg = tooltip .append("div") .attr("class", "p-tooltip__message"); const countries = feature(world, world.objects.countries).features; const g = svg.append("g"); const country = g .selectAll(".snapcraft-territories__country") .data(countries); country .enter() .insert("path") .attr("class", countryData => { const countrySnapData = snapData[countryData.id]; if (countrySnapData) { return `snapcraft-territories__country snapcraft-territories__country-default`; } return "snapcraft-territories__country"; }) .attr("style", countryData => { const countrySnapData = snapData[countryData.id]; if (countrySnapData) { if (countrySnapData.color_rgb) { return ( "fill: rgb(" + countrySnapData.color_rgb[0] + "," + countrySnapData.color_rgb[1] + "," + countrySnapData.color_rgb[2] + ")" ); } } }) .attr("d", path) .attr("id", function(d) { return d.id; }) .attr("title", function(d) { return d.properties.name; }) .on("mousemove", countryData => { const pos = mouse(mapEl.node()); const countrySnapData = snapData[countryData.id]; if (countrySnapData) { tooltip .style("top", pos[1] + "px") .style("left", pos[0] + "px") .style("display", "block"); let content = [ '<span class="u-no-margin--top">', countrySnapData.name ]; if (countrySnapData["number_of_users"] !== undefined) { content.push(`<br />${countrySnapData["number_of_users"]} active`); } content.push("</span>"); tooltipMsg.html( `<span class="snapcraft-territories__swatch" style="background-color: rgb(${countrySnapData.color_rgb[0]}, ${ countrySnapData.color_rgb[1] }, ${countrySnapData.color_rgb[2]})"></span> ${content.join(" ")}` ); } }) .on("mouseout", function() { tooltip.style("display", "none"); }); g.append("path") .datum( mesh(world, world.objects.countries, function(a, b) { return a !== b; }) ) .attr("class", "snapcraft-territories__boundary") .attr("d", path); }
geoAzimuthalEquidistant, geoConicConformal, geoConicEqualArea, geoConicEquidistant, geoEquirectangular, geoGnomonic, geoIdentity, geoMercator, geoNaturalEarth1, geoOrthographic, geoStereographic, geoTransverseMercator, geoPath } from 'd3-geo'; var defaultPath = geoPath(); export var projectionProperties = [ // standard properties in d3-geo 'clipAngle', 'clipExtent', 'scale', 'translate', 'center', 'rotate', 'parallels', 'precision', 'reflectX', 'reflectY', // extended properties in d3-geo-projections
render() { console.log('svgHeight: ' + this.state.svgHeight); const projection = geoMercator().fitExtent([[0, 0], [60, this.state.svgHeight]], this.state.geojson); const pathGenerator = geoPath().projection(projection) return ( <View style={styles.cardContainer}> <TouchableOpacity style={styles.cardContainer}> <View style={styles.cardContainer}> <Image style={{width: width, height: this.state.imgHeight}} source={{uri: 'http://www.cocokelley.com/wp-content/uploads/2017/06/the-candy-colored-streets-in-Tallin-Estonia-visual-travel-diary-on-coco-kelley.4.jpg'}}/> <View style={{ width: width, height: this.state.imgHeight, position: 'absolute', backgroundColor: 'rgba(0,0,0,0.5)', }}> </View> <View style={{ flexDirection: 'column', flexWrap: 'wrap', width: width, justifyContent: 'flex-start', position: 'absolute', top: 40, left: 20 }}> <Text style={styles.subStoryText}> /{this.props.substory.toUpperCase()} </Text> <View> <Text style={styles.titleText}> {this.props.name.toUpperCase()} </Text> </View> </View> <View style={{ position: 'absolute', alignItems: 'center', flexDirection: 'column', bottom: 0, width: '100%', }}> <View style={{ flexDirection: 'row', backgroundColor: 'rgba(0,0,0,0.4)',width: '100%', alignItems: 'center' }}> <View style={{flexDirection: 'column', alignItems: 'center'}}> <Icon name="ios-photos-outline" style={{color: 'white', marginTop: 10}} size={25}/> <Text style={{color: 'white', marginHorizontal: 25, marginBottom: 5}}>25</Text> </View> <View style={{flexDirection: 'column', alignItems: 'center'}}> <Svg style={{marginTop: 10, }} height={this.state.svgHeight} width={60} > <Path d={pathGenerator(this.state.geojson)} fill="none" stroke="white" strokeWidth={2} /> </Svg> <Text style={{color: 'white', marginVertical: 5,}}>Madrid, Spain</Text> </View> </View> <CommentBarV2 color={'white'} likes={10} comments={20} style={{marginHorizontal: 15}}/> </View> </View> </TouchableOpacity> </View> ); }
const pi = Math.PI; const radians = pi / 180; const degrees = 180 / pi; const circle = geoCircle() .radius(90); const projection = geoAzimuthalEquidistant() .scale(85) .translate([width / 2, height / 2]) .clipAngle(180 - 1e-3) .rotate([0, 90]) .precision(.1); const path = geoPath() .projection(projection); const graticule = geoGraticule(); export default function World() { const now = new Date(); const today = utcDay(now); const sun = antipode(solarPosition(now)); const angle = 180 - sun[0]; const translate1 = `translate(${width / 2}, ${height / 2})`; const rotate = `rotate(${angle})`; const translate2 = `translate(${-width / 2}, ${ -height / 2})`; const transform = translate1 + rotate + translate2; return (