Html map and svg

Создание интерактивной карты SVG Map

Интерактивная карта (SVG Map) так же, как и карта изображений (Image Map), позволяет привязывать ссылки и события к фрагментам изображения. Но, в отличие от Image Map, SVG карта не теряет четкости при изменении размера, а создание участка сложной формы в SVG не настолько трудоёмко.

Для построения SVG карты можно использовать любой векторный редактор. Например, Adobe Illustrator или CorelDRAW.

Применение SVG карт

SVG карты применяются для привязки событий или ссылок к различным произвольным участкам изображения. Это могут быть карты стран, регионов, городов, а также планы зданий.

В качестве примера приведена карта Краснодарского края с тремя выделенными районами, в которых расположены сахарные заводы концерна Покровский. При наведении указателя мышки на определенный район у его изображения меняется цвет заливки с помощью CSS и псевдокласса hover, а с помощью скрипта, срабатывающего по возникновению события onmouseover, появляется поясняющая надпись.

Пример использования SVG карты
 .b-map-svg < position: relative; >#kanev, #kurgan, #timash 
g .fil2 g:hover .fil2 .fil3 g:hover .fil3 .fil0 .fil4 g:hover .fil4 .fil5 .fil1 .fil6
ПАО «КАНЕВСКСАХАР»
Каневской район
ООО «ТИМАШЕВСКИЙ САХАРНЫЙ ЗАВОД»
Тимашевский район
ЗАО «САХАРНЫЙ КОМБИНАТ «КУРГАНИНСКИЙ»
Курганинский район

Источник

Creating an interactive SVG map on the web with D3

I was recently approached with a task I was unfamiliar with. It involved painting a SVG map on a webpage with clickable points of interest. The problem was that the user should be able to zoom and pan across the map. I found the information on the internet on this topic rather scarce. Hence I am here, telling you how I solved my problem. If you are the type of person looking for the complete code, see Conclusion. For the tutorial I recommend using CodePen where the complete solution is hosted or a similar service since we are going to make a very small iterative project. We are not going to be using any frameworks or languages other than Babel to transpile the code.

Читайте также:  Python json none to null

Basic zooming and panning

 class="container">  id="map" width="100%" height="100%" style="background-color:red">  id="image" href="https://upload.wikimedia.org/wikipedia/commons/1/1e/Alexander_III_conquest_from_Issos_to_Babylon-fr.svg">   
*  margin: 0; padding: 0; > .container  width: 100%; height: calc(100vh); overflow: hidden; > 

What we see is a map of Alexander III conquest from Issos to Babylon in French. However we see only a part of the map and on larger screens on the side we get a large portion of red SVG background sticking out. What we want is an experience where the user is able to pan and zoom across the map. However they should not be allowed to get out of the map bounds. In order to achieve this we need to use D3 library. It includes a lot of utility methods and modules that help you create charts, tables, maps and other interactive experiences on the web using JavaScript and HTML. Many higher level libraries use it as the groundwork. I will not go into detail about library API but rather leave relevant links where possible.

import * as d3 from "https://cdn.skypack.dev/d3@7.6.1"; // create a D3 selection of the SVG itself - https://github.com/d3/d3-selection const svg = d3.select("#map"); // create a D3 selection of the image element const image = svg.selectChild("#image"); // create and configure an instance of zoom behaviour - https://github.com/d3/d3-zoom const zoom = d3.zoom().on("zoom", zoomed); // apply configured zoom behaviour to our svg svg.call(zoom); function zoomed(event)  const  transform > = event; // apply calculated transform to the image image.attr("transform", transform.toString()); > 

Now when we run the code we should be able to pan across the image. But at the moment we are not bound to the image limits and may go outside of the bounds infinitely. To fix this we should use extent configuration for the zoom behaviour.

Scrolling to zoom inside the CodePen iframe seems to not be working on dev.to. Please open the CodePen in a different tab to see it properly.

const  width, height > = image.node().getBoundingClientRect(); const  width: svgWidth, height: svgHeight > = svg .node() .getBoundingClientRect(); const zoom = d3 .zoom() // scale extent is how much you can zoom into or out of the image - https://github.com/d3/d3-zoom#zoom_scaleExtent .scaleExtent([1, 8]) // extent is mostly used to calculate things and make them smooth during zooming and panning - https://github.com/d3/d3-zoom#zoom_extent // by default it is the viewbox or width and height of the nearest SVG ancestor - this works for us .extent([ [0, 0], [svgWidth, svgHeight], ]) // translate extent is optional and is used to bound the viewport to the image - https://github.com/d3/d3-zoom#zoom_translateExtent .translateExtent([ [0, 0], [width, height], ]) .on("zoom", zoomed); 

Now we are unable to pan outside the image. However we still see red background on the sides. This is clearly due to the fact that we are zoomed out too far. To fix this we need to make our scaleExtent more accurate.

You may experiment and see that if we remove scaleExtent we would be able to zoom out far enough to fit the whole image in the viewport. In this case translateExtent would not allow us to pan at all since the actual extent of the translate is already reached.

// minimum scale is the largest ratio between the container dimension and the image dimension const minScale = Math.max(svgWidth / width, svgHeight / height); const zoom = d3 .zoom() // scale extent is how much you can zoom into or out of the image .scaleExtent([minScale, 8]) .extent([ [0, 0], [svgWidth, svgHeight], ]) .translateExtent([ [0, 0], [width, height], ]) .on("zoom", zoomed); // apply calculated default scale zoom.scaleTo(svg, minScale); 

At this point it may become clear that I chose a fairly poor image as an example since it too small to really justify the ability to zoom. But you get the point nevertheless!

Congrats! We got what we wanted to achieve. However there is still a thing we need check. What if we want to zoom and pan across multiple elements? Like an image with a few circles over it?

Groups and interactive elements

 class="container">  id="map" width="100%" height="100%" style="background-color:red">  id="image" href="https://upload.wikimedia.org/wikipedia/commons/1/1e/Alexander_III_conquest_from_Issos_to_Babylon-fr.svg" />  fill="red" cx="50" cy="50" r="25" />   

Now when we zoom and pan the image the circle always stays at the same place. In our case this circle may mark a place of a significant battle and we want it to be on the map. To fix this we should wrap our image with the circle inside a group g .

 class="container">  id="map" width="100%" height="100%" style="background-color:red">  id="image">  href="https://upload.wikimedia.org/wikipedia/commons/1/1e/Alexander_III_conquest_from_Issos_to_Babylon-fr.svg" />  fill="red" cx="50" cy="50" r="25" />    

Window resizes

Still, there is a bug left. When you resize the window our scaling and panning brakes! To fix this we need to listen to resize events on window and update our extent properties. this is how our code would look like:

import * as d3 from "https://cdn.skypack.dev/d3@7.6.1"; // create a D3 selection of the SVG itself const svg = d3.select("#map"); // create a D3 selection of the image element const image = svg.selectChild("#image"); // calculate image dimensions once - they should not change const  width, height > = image.node().getBoundingClientRect(); // =========================================== const zoom = d3.zoom().on("zoom", zoomed); function updateExtents()  const  width: svgWidth, height: svgHeight > = svg .node() .getBoundingClientRect(); const minScale = Math.max(svgWidth / width, svgHeight / height); zoom // scale extent is how much you can zoom into or out of the image .scaleExtent([minScale, 8]) // extent is mostly used to calculate things and make them smooth during zooming and panning // by default it is the viewbox or width and height of the nearest SVG ancestor - this works for us .extent([ [0, 0], [svgWidth, svgHeight], ]) // translate extent is optional and is used to bound the viewport to the image .translateExtent([ [0, 0], [width, height], ]); // apply calculated default scale zoom.scaleTo(svg, minScale); > // =========================================== // apply configured zoom behaviour to our svg svg.call(zoom); updateExtents(); window.addEventListener("resize", updateExtents); function zoomed(event)  const  transform > = event; // apply calculated transform to the image image.attr("transform", transform.toString()); > 

Now resize your window and see that after a resize our translate and scale bounds still work properly.

Minimap

I think it would be quite interesting to implement a read-only minimap using the knowledge we just gained. Let’s duplicate our image into a preview group outside the image (so that id does not scale or pan).

 class="container">  id="map" width="100%" height="100%" style="background-color:red">  id="image">  href="https://upload.wikimedia.org/wikipedia/commons/1/1e/Alexander_III_conquest_from_Issos_to_Babylon-fr.svg" />  fill="red" cx="50" cy="50" r="25" />   id="preview" transform="translate(20,20) scale(0.1)">  href="https://upload.wikimedia.org/wikipedia/commons/1/1e/Alexander_III_conquest_from_Issos_to_Babylon-fr.svg" />  fill="red" cx="50" cy="50" r="25" />   
const preview = svg.select("#preview"); const rect = preview .append("rect") .style("fill-opacity", "0") .style("stroke", "red") .style("stroke-width", "3px"); function updateViewport()  const  width: svgWidth, height: svgHeight > = svg .node() .getBoundingClientRect(); rect.attr("width", svgWidth).attr("height", svgHeight); > // =========================================== // apply configured zoom behaviour to our svg svg.call(zoom); updateExtents(); updateViewport(); window.addEventListener("resize", updateExtents); window.addEventListener("resize", updateViewport); 

Next update the zoomed function to transform both the image and the viewport. It is important to note that the transform we apply in this function is destined for the image. So it does not scale the viewport but the content. The trick is to invert the scale since the amount that the viewport scales is inversely proportial to how the content scales.

function zoomed(event)  const  transform > = event; // apply calculated transform to the image image.attr("transform", transform.toString()); const scale = 1 / transform.k; const inverseTransform = new d3.ZoomTransform( scale, -transform.x * scale, -transform.y * scale ); rect.attr("transform", inverseTransform.toString()); > 

Profit! Now you have a minimap for your SVG map. Still we can go event further! How about allowing the user to click on a location on the minimap to jump to it?

Clickable minimap

To achieve that add a listener to click event on preview group. When clicked, calculate the position from the top left corner of the minimap. Then divide it by the scale of the minimap to get the position on the actual map. Finally, use this position to move the viewport.

Since we use the translateTo method it would not allow the user to clip out of bounds using the minimap since this method respects extent properties.

preview.on("click", handleMinimapClick); const previewRect = preview.node().getBoundingClientRect(); // this scale is taken from HTML transform property above const PREVIEW_SCALE = 0.1; function handleMinimapClick(event)  const dx = event.clientX - previewRect.x, dy = event.clientY - previewRect.y; const position = [dx / PREVIEW_SCALE, dy / PREVIEW_SCALE]; zoom.translateTo(svg, . position); > 

Check it out! And you can still use the minimap pan around the actual map. If you do not want that call image.call(zoom) instead of svg.call(zoom) . Then zoom will only listen to clicking and dragging over the image but not the minimap.

Источник

Оцените статью