D3区域地图+悬停工具提示+浏览器放置问题

时间:2018-10-01 00:41:15

标签: javascript java d3.js visualization choropleth

提前感谢您解决此问题。我已经创建了一个D3波形图,但我很难尝试弄清楚为什么悬停工具提示在地图在浏览器中的位置方面存在一些问题(例如,上下滚动页面以及上下移动地图) 。首先,这是我正在使用的代码“ choropleth.tag”:

<%@ include file="/WEB-INF/jsp/site/taglib.jsp"%>
<%@ attribute name="baseDataUrl" required="true" %>
<%@ attribute name="domain" required="true" %>
<%@ attribute name="id" required="true" %>
<%@ attribute name="dataInputLabel" required="false" %>

<div class="container">
	<div class="row">
		<div class="chropleth-map">
			<svg id="${id}-d3-choroplethmap" viewBox="-120 -150 1200 700" class="d3-map-animation" preserveAspectRatio="xMidYMid meet"></svg>
			<!--<svg id="${id}-svgkey" viewBox="650 -25 150 50" class="fade-in-slow-2 svgkey"></svg>-->
		</div>
		<div class="col-xs-6 tooltip" style="opacity: 0;"></div>
	</div>
</div>

<script src="<c:url value="/js/libs/d3v4/d3.v4.min.js"/>"></script>
<script src="<c:url value="/js/libs/d3v4/d3-scale-chromatic.v1.min.js"/>"></script>
<script src="<c:url value="/js/libs/d3v4/topojson.v2.min.js"/>"></script>
<script src="https://d3js.org/d3-axis.v1.min.js"></script>

<script type="text/javascript">

    var panZoomMaps;
    var drawMapFunctions;
    
	panZoomMaps = ( typeof panZoomMaps !== 'undefined' && panZoomMaps instanceof Array ) ? panZoomMaps : [];
	drawMapFunctions = ( typeof drawMapFunctions !== 'undefined' && drawMapFunctions instanceof Array ) ? drawMapFunctions : [];
   		
	$.EventBus("currentOrganizationChange").subscribe(function(){
		/*panZoomMaps["${id}"].destroy();*/
		$("#${id}-d3-choroplethmap").empty();
        drawMapFunctions["${id}"]();
    });

    drawMapFunctions["${id}"] = function (){
        var svgMap = d3.select("#${id}-d3-choroplethmap");
			width = +svgMap
				.attr("width");
			height = +svgMap
				.attr("height");
		var cbsaMap = d3.map();
		var color = d3.scaleLinear().domain(${domain}).range(d3.schemeBlues[${domain}.length])
		var orgId = sessionStorage.getItem("currentOrganizationId");

		if (!orgId){
				orgId = -1;
			}
			d3.queue()
			.defer(d3.json, "${contextRoot}/2013_us.topojson.json")
			.defer(d3.json, "${baseDataUrl}/"+orgId)
			.await(ready);

		function ready(error, cbsa, d) {

			var width = 960,
		    		height = 500,
		    		centered;			
			var projection = d3.geoAlbersUsa()
			    .scale(1070)
			    .translate([width / 2, height / 2]);			
			var path = d3.geoPath().projection(projection);
			var svg = d3.select("#${id}-d3-choroplethmap");
						width = +svg
						.attr("width");
						height = +svg
						.attr("height");		
			svg.append("rect")
			    .attr("class", "d3MapBackground")
			    .attr("width", width)
			    .attr("height", height)
			    .on("click", clicked);	
			
			var g = svg.append("g");			
			
			d3.json("${contextRoot}/2013_us.topojson.json", function(error, us) {
				  if (error) throw error;

			});		

			function clicked(d) {
				  var x, y, k;

				  if (d && centered !== d) {
				    var centroid = path.centroid(d);
				    x = centroid[0];
				    y = centroid[1];
				    k = 4;
				    centered = d;
				    $('#btn').click(function(){
				        var btn = $(this);
				        $.post(/*...*/).complete(function(){
				            btn.prop('disabled', false);
				        });
				        btn.prop('disabled', true);

				    });
				    $("#dialogBox, #mobileDialogBox").css("display", "block").css("visibility", "visible").css("height", "104");
				    
				  } else {
				    x = width / 2;
				    y = height / 2;
				    k = 1;
				    centered = null;
				    $("#dialogBoxCloseButton, #mobileDialogBoxCloseButton").click();
				    $("#dialogBox, #mobileDialogBox").css("display", "table-column").css("visibility", "hidden").css("height", "0");
				  }
				d3.select("#${id}-d3MapCbsa").selectAll("path")
					.classed("active", centered && function(d) { return d === centered; });
					  
				d3.select("#${id}-d3MapState").transition()
					.duration(750)
					.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
					.attr("transform-origin", "-160px -60px 10px")
					.style("stroke-width", 1.5 / k + "px");
							
				d3.select("#${id}-d3MapCbsa").transition()
					.duration(750)
					.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
					.attr("transform-origin", "-160px -60px 10px")
					.style("stroke-width", 1.5 / k + "px");
							
				d3.select("#${id}-d3MapStateBorder").transition()
					.duration(750)
					.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
					.attr("transform-origin", "-160px -60px 10px")
					.style("stroke-width", 1.5 / k + "px");
			}	
					
		    var tooltip = d3.select("body").append("div") 
	        		.attr("class", "tooltip")       
	        		.style("opacity", 0);
			var us = cbsa;
			var svgMap = d3.select("#${id}-d3-choroplethmap");
			var mouseDownTime=new Date().getTime();
			var prevMouseUp=new Date().getTime();
			var clicks=0;
			var timeout;
			var path = d3.geoPath().projection(projection);
			var stateAndMicroColor= "#cccccc";

			if (error)
				throw error;

			for (var i=0; i<d.length; i++){
			    cbsaMap.set(d[i].cbsaId, +d[i].num);
			}				
			
			svgMap.append("g")
				.attr("class", "state")
				.attr("id", "${id}-d3MapState")
				.selectAll("path").data(topojson.feature(us, us.objects.states).features)
				.enter()
				.append("path")
				.attr("fill", function(){
					return stateAndMicroColor;
				})
				.attr("d", path)
				.attr("pointer-events", "none")
				.on("click", clicked);

			svgMap.append("g")
				.attr("class", "cbsa")
				.attr("id", "${id}-d3MapCbsa")
				.selectAll("path")
				.data(topojson.feature(us, us.objects.cbsas).features)
				.enter().append("path")
				.attr("fill", function(d){
						if(cbsaMap.has(d.properties.id)){
							return color(cbsaMap.get(d.properties.id));
						}
						return stateAndMicroColor;
					})
				.attr("stroke", function(){
					return "#ffffff";
				})
				.attr("stroke-width", 0.5)
				.attr("d", path)
				.on("click", clicked)
				.on("mouseover", function(d) {    
					var data = cbsaMap.get(d.properties.id)
		            tooltip.transition()    
		            .duration(200)    
		            .style("opacity", .9);
					tooltip.html("${dataInputLabel}: "+ data)
		            .style("left", (d3.event.pageX + 20) + "px")
		            .style("top", (d3.event.pageY - 45) + "px");
		          })          
				.on("mouseout", function(d) {   
		            tooltip.transition()    
		            .duration(500)    
		            .style("opacity", 0); 
				})
				.on("mousedown", function(){
					mouseDownTime=new Date().getTime();
					clearTimeout(timeout);
				})
				.on("mouseup", function(d) {
					clicks++;
					
						if (clicks === 1){
							var x = d3.event.pageX - document.getElementById("${id}-d3-choroplethmap").getBoundingClientRect().x + 700;
							var y = d3.event.pageY - document.getElementById("${id}-d3-choroplethmap").getBoundingClientRect().y + 380;
							timeout = setTimeout(function () {
	
								
	 							if(new Date().getTime() - mouseDownTime < 250 && cbsaMap.has(d.properties.id)){
									$('#dialogText, #mobileDialogText').text("Header");
									$('#dialogText, #mobileDialogText').html("The ID of this Core Based Statistical Area (CBSA) is <b>" + d.properties.id + "</b>.<br/> There are a total of <b>" + cbsaMap.get(d.properties.id) + "</b> ${dataInputLabel}. ");
	
									/* NOTE: The following function works on smaller devices and viewports with the help of the "#dialogBox" media query.  The dialog box is stuck to the bottom of the map and is updated on CBSA clicking.  Space is made or reduced to fit the dialog box. */
									/* $('#dialogBox').show().draggable(); */
									
									/* NOTE: The following jQuery funtion works on desktop screensizes and not on mobile or tablet.  How can the .offset({}) be adjusted for different screensizes like CSS media queries? */
									$('#dialogBox, #mobileDialogBox').show().offset({top: y,left: x}).draggable();							
									
									$('#dialogBoxCloseButton, #mobileDialogBoxCloseButton').click(function() {
										$('#dialogBox, #mobileDialogBox').hide();
									});
									
								}						
								clicks=0;
							}, 100);
						} else if (clicks === 2) {
							clearTimeout(timeout);
							$("#dialogBoxCloseButton, #mobileDialogBoxCloseButton").click();
							clicks = 0;
						}
					prevMouseUp=new Date().getTime();
				})
			;

			svgMap.append("g")
				.attr("class", "stateBorder")
				.attr("id", "${id}-d3MapStateBorder")
				.selectAll("path").data(topojson.feature(us, us.objects.states).features)			
				.enter().append("path")
				.attr("fill", function(){
					return "transparent";
			})
				.attr("stroke", "#fff")
				.attr("stroke-width", 0.8)
				.attr("d", path)
				.attr("pointer-events", "none")
				.on("click", clicked);			
		}
	}
    drawMapFunctions["${id}"]();

	/* Closing of the D3 map dialog box upon clicking each of the four CTA's. */
		$('#cardholdersCTA, #transactionsCTA, #ordersCTA, #pointsCTA' ).click(function(){
		   	$("#dialogBoxCloseButton, #mobileDialogBoxCloseButton").click();
		});  

</script>

因此,首先,在初始页面加载时,将生成地图,如果将鼠标悬停在美国境内的任何CBSA /地理区域,则将正确生成DIV工具提示并将其放置在鼠标光标附近。当用户向下滚动页面时,问题开始。此时,如果用户将鼠标悬停在同一地图区域上。工具提示位于浏览器窗口中的同一区域,而地图中没有日志记录器。几乎就像不是基于鼠标光标放置工具提示,而是基于Bowser窗口的尺寸。我相信问题出在代码中:

.style("left", (d3.event.pageX + 20) + "px")
.style("top", (d3.event.pageY - 45) + "px");

除此问题外,整个地图均具有完全响应能力。以下是鼠标悬停在科罗拉多州丹佛市附近的CBSA区域上并向下滚动页面的一些屏幕截图:

初始页面加载和地图展示;在科罗拉多州丹佛市上空徘徊:

Hovering and Scrolling no. 1

我向下滚动页面,然后再次悬停在科罗拉多州的丹佛上;工具提示位于浏览器中的同一位置,但应位于鼠标所在的科罗拉多州丹佛附近:

Hovering and Scrolling no. 2

我进一步滚动,然后再次悬停在科罗拉多州的丹佛上

Hovering and Scrolling no. 3

...以及向下滚动页面然后将鼠标悬停在丹佛上的另一个屏幕截图。

Hovering and Scrolling no. 4

因此,基本上,似乎是根据浏览器而不是地图的尺寸​​来放置工具提示。我想念什么?我怎样才能解决这个问题?有什么建议么?非常感谢您的帮助和见解。非常感谢。

更新:这是我对鼠标悬停功能的更改:

var scrollNumber = window.pageYOffset;
var data = cbsaMap.get(d.properties.id)
  tooltip.transition()    
    .duration(200)    
    .style("opacity", .9);
  tooltip.html("${dataInputLabel}: "+ data)
    .style("left", (d3.event.pageX + 20) + "px")
    .style("top", (d3.event.pageY - scrollNumber - 45) + "px");	

0 个答案:

没有答案