#html #angular #typescript #d3.js #topojson
#HTML #угловой #машинописный текст #d3.js #топойсон
Вопрос:
Я боролся с созданием функциональной карты choropleth в D3 на Angular, и до сих пор мне удавалось собрать воедино всплывающую подсказку и функцию масштабирования по округу при нажатии. В настоящее время я пытаюсь выяснить:
- Как сбросить масштаб при втором щелчке, но в большинстве примеров в Интернете, похоже, используется совершенно другая функция масштабирования.
- Как включить панорамирование, которое может повлиять на общий масштаб, как указано в:
- Как включить кнопку масштабирования для выполнения общего масштабирования в центре текущего представления
Я прокомментировал свой машинописный текст ниже вопросами, связанными с этим.
map.component.html
lt;div id="map"gt; lt;div id="button-row"gt; lt;button id="zoom-in"gt; lt;/buttongt; lt;button id="zoom-out"gt;-lt;/buttongt; lt;button id="zoom-reset"gt;$lt;/buttongt; lt;/divgt; lt;/divgt;
map.component.ts
import { Component, OnInit } from '@angular/core'; import {FetchTopoService} from '../fetch-topo.service'; // Service that simply returns the following topo json: 'https://cdn.jsdelivr.net/npm/us-atlas@3/counties-albers-10m.json' import * as d3 from 'd3'; import * as topojson from 'topojson-client'; // Is this the current best approach? import { GeometryObject, GeometryCollection, Topology, Objects } from 'topojson-specification'; // Is this different from the topojson-client? How? @Component({ selector: 'app-map', templateUrl: './map.component.html', styleUrls: ['./map.component.css'] }) export class MapComponent implements OnInit { // Note: ideally the map initialization would be reusable (in a directive?) and I would not have to specify any features related to it within the component. topo: any = null svg: any = null path: any = d3.geoPath() nation: any = null states: any = null counties: any = null tooltip: any = null zoom: any = null zoomIn: any = null constructor( private topoService: FetchTopoService ) { } async ngOnInit(): Promiselt;voidgt; { this.topo = await this.topoService.getData() this.initMap("#map"). // Initializing to the #map id element. } initMap(divId:any) { // Instantiate tooltip. this.tooltip = d3.select(divId) .append('div') .attr('class', 'map-tooltip') .style('visibility', 'hidden') .style('background-color', 'white') .style('padding', '5px') .style('position', 'absolute') .style('opacity', '0.5') .on('mouseover', (event) =gt; { this.tooltip.style('visibility', 'hidden'); }); // Instantiate svg. this.svg = d3.select(divId).append('svg') .attr("preserveAspectRatio", "xMidYMid meet") .attr("viewBox", "0 0 960 600") .attr("height", "98%") .attr("width", "100%") // Draw U.S. nation object. this.nation = this.svg.append("path") .datum(topojson.feature(this.topo, this.topo["objects"]["nation"])) .attr("fill", "#f5f5f5") .attr("class", "nation") .attr("d", this.path) // Draw county objects. this.counties = this.svg.selectAll("path.county") .data(topojson.feature(this.topo, this.topo["objects"]["counties"] as GeometryCollection)["features"]) .join("path") .attr("id", function(d:any) {return d["id"]}) .attr("class", "county") .attr("fill", "#E7E7E8") .attr("stroke", "#ffffff") .attr("stroke-linejoin", "round") .attr("stroke-width", "0.25") .attr("d", this.path) // Show tooltip on mouseover. .on("mouseover", (event:any, d:any) =gt; { this.tooltip.style('visibility', 'visible') d3.select(`[id="${d['id']}"]`) .attr("stroke-width", "2.0") }) // Hide tooltip on mouse leave. .on("mouseleave", (event:any, d:any) =gt; { this.tooltip.style('visibility', 'hidden') d3.select(`[id="${d['id']}"]`) .attr("stroke-width", "0.25") }) // Have tooltip follow mouse movement. .on('mousemove', (event:any, d:any) =gt; { this.tooltip .html(d["id"] 'lt;brgt;' d["properties"]["name"]) .style('left', (event.x 10) 'px') .style('top', (event.y 10) 'px') .style('z-index', '2') }) // On county click, zoom to county. .on('click', (event:any, d:any) =gt; { this.clicked(d) }) // Overlay state objects (no fill) to get thicker borders — this causes issues in clicked. this.states = this.svg.selectAll("path.state") .data(topojson.feature(this.topo, this.topo["objects"]["states"] as GeometryCollection)["features"]) .join("path") .attr("id", function(d:any) {return d["id"]}) .attr("class", "state") .attr("fill", "none") .attr("stroke", "#ffffff") .attr("stroke-linejoin", "round") .attr("stroke-width", "1.25") .attr("d", this.path) // This does not work — ideally zooms on click zoom button. this.zoomIn = d3.select('#zoom-in').on('click', (event:any, d:any) =gt; { d3.zoom() .scaleExtent([1, 8]) .scaleBy(this.svg.transition().duration(750), 1.3) }) // On click county, remove states, then translate — there must be an easier way to do this. clicked(d:any) { this.states .attr("stroke", "none") let bounds = this.path.bounds(d) let dx = bounds[1][0] - bounds[0][0] let dy = bounds[1][1] - bounds[0][1] let x = (bounds[0][0] bounds[1][0]) / 2 let y = (bounds[0][1] bounds[1][1]) / 2 let scale = .9 / Math.max(dx / 960, dy / 600) let translate = [960 / 2 - scale * x, 600 / 2 - scale * y] this.counties.transition() .duration(1000) .style("stroke-width", 1.5 / scale "px") .attr("transform", "translate(" translate ")scale(" scale ")") d3.select(`[id="${d['id']}"]`) .attr("stroke-width", "2.0") } }