3
$\begingroup$

I want to produce an SVG map by Mathematica that preserves tooltips that are in Mathematica so that they are also in the SVG. The problem is that the tooltips do not export.

Here is an example:

This is a picture of Guyana with shipping location of items a specific region.

That was produced with the code:

itemNames = {"Batista", "Batwing", "Batwing41", "Batwing57", 
 "BatwingPseudo", "Box+-|+", "Box+-+|-", "Box+-+|+", "Box++|+-a", 
 "Box++|+-b", "BoxIA2", "BoxIA3", "BoxIB3", "BoxIIA1", "BoxIIA2", 
 "BoxIIB1", "BoxIIB2", "BoxIIB3", "CD", "CH", "CLP", "CP", "CP21", 
 "CP27", "CP33", "CP39", "CP45", "CrPD", "CS4", "CY", "D", 
 "DeformedFRD", "DeformedHT", "DeformedSS", "Disphenoid", 
 "Disphenoid35", "Disphenoid43", "Disphenoid51", "Disphenoid55", 
 "Disphenoid67", "FRD", "FRDr", "G", "GW", "H", "Hexplane2", 
 "Hexplane3", "Hexplane4", "Hexplane5", "HR", "HRTR", "HT", "HTHR", 
 "I6", "I8", "I9", "IWP", "IWPr", "Lidinoid", "Manta", "Manta35", 
 "Manta51", "N", "N14", "N26", "N38", "OCTO", "oD", "oH", "oP", 
 "oSP", "P", "P3a", "PFRD", "R2", "R3", "rPD", "RTW", "S", "SS", 
 "SS4", "SSP", "Starfish", "Starfish43", "Starfish47", "Starfish55",
 "Starfish59", "Starfish63", "Starfish75", "Starfish87", "tD", 
 "tDHandles", "TR", "TRHT", "TriCosta5", "TriCosta7", "Triplane1", 
 "Triplane2", "Triplane3", "Triplane4", "Triplane5", "Wei"};
divisionNameAtPoint[pt_, divisions_] :=
 Module[{p = GeoPosition[pt]},
 With[{inside = 
 Pick[divisions, 
 GeoWithinQ[#, p] & /@ EntityValue[divisions, "Polygon"]]},
 If[inside =!= {},
 EntityValue[First[inside], "Name"],
 EntityValue[First@GeoNearest[divisions, p, 1], "Name"]
 ]
 ]
 ];
drawCountryWithLabeledPins[country_String, nMarkers_Integer : 10] :=
 Module[{ctry, divisions, mypin, pts, mePos},
 ctry = Entity["Country", country];
 divisions = ctry["AdministrativeDivisions"];
 
 mypin = Graphics[
 GraphicsGroup[{
 FaceForm[Orange],
 FilledCurve[
 {
 {Line@Join[
 {{0, 0}},
 ({Cos[#], 3 + Sin[#]} & /@ 
 Range[-(2 Pi)/20, Pi + (2 Pi)/20, Pi/20]),
 {{0, 0}}]},
 {Line[(0.5 {Cos[#], 6 + Sin[#]} & /@ Range[0, 2 Pi, Pi/20])]}
 }
 ]
 }],
 PlotRange -> All,
 Background -> None,
 ImageSize -> 24];
 
 pts = RandomGeoPosition[ctry, nMarkers][[1]];
 mePos = RandomGeoPosition[ctry];
 
 GeoGraphics[
 {
 GeoStyling[White], EdgeForm[{Black, Thick}], Polygon[divisions],
 
 Table[
 Tooltip[
 GeoMarker[pts[[i]], mypin, "Alignment" -> Bottom],
 Column[
 {
 RandomChoice[itemNames],
 "",
 ToString[pts[[i]]],
 ToString[
 divisionNameAtPoint[pts[[i]], divisions]
 ]
 }
 ]
 ],
 {i, 1, nMarkers}],
 Tooltip[
 GeoMarker[mePos,
 Graphics[{Black, Disk[]}],
 "Alignment" -> Center, "Scale" -> Offset[15]],
 "Home"],
 Text[
 Style["Me", Bold, Black, 12], mePos, {0, -2.5}]
 },
 GeoRange -> ctry,
 GeoRangePadding -> None,
 GeoBackground -> None,
 AspectRatio -> Automatic,
 ImageSize -> {400, 400}]
 ];
drawCountryWithLabeledPins["Guyana", 20]

But when I export it as an SVG the tooltips are lost.

Disclaimer: The above code I used worked for Guyana, Chile, Venezuela, and Laos. It did not work properly for Azerbaijan. I assume that there are more exceptions.

This question is related to:

and

asked 2 days ago
$\endgroup$
2
  • 1
    $\begingroup$ Would it be possible for you to please make a shorter minimal working example? Currently, your code is very complex and it has way too many things for a "simple" problem of preserving tooltips in SVG. $\endgroup$ Commented yesterday
  • $\begingroup$ I cut the OP in half because I solved it. But this is my use case so I want to get the same gist of the example. I think keeping the tooltip problem with country maps is important, because some countries this won't work for as of time of writing. $\endgroup$ Commented yesterday

1 Answer 1

1
$\begingroup$

I found out that the administrative regions are placed in the SVG in the same order as how Mathematica orders the divisions. The pins stay in same order as well.

Errata:

  • There are a couple of things that are not ideal. Because I used <title> for the tooltips I cannot modify the style. I didn't make an alternative with CSS to keep the solution simple.

  • The pins aren't perfect in how they bring up the tooltips when covered over as in the range that the mouse can trigger it isn't very wide, but for me it was simple enough. There are work arounds to improve that.

  • Lastly, you would have to specify where you want the HTML file to be stored. The SVG is placed in the middle of the HTML file. (baseSvgFile and outputHtmlFile)

  • I assume that 'Me' has an item at their house.

Below is a solution that can be called like drawCountrySVGWithLabeledPins["Guyana", 10, itemNames];:

itemNames = {"Batista", "Batwing", "Batwing41", "Batwing57", 
 "BatwingPseudo", "Box+-|+", "Box+-+|-", "Box+-+|+", "Box++|+-a", 
 "Box++|+-b", "BoxIA2", "BoxIA3", "BoxIB3", "BoxIIA1", "BoxIIA2", 
 "BoxIIB1", "BoxIIB2", "BoxIIB3", "CD", "CH", "CLP", "CP", "CP21", 
 "CP27", "CP33", "CP39", "CP45", "CrPD", "CS4", "CY", "D", 
 "DeformedFRD", "DeformedHT", "DeformedSS", "Disphenoid", 
 "Disphenoid35", "Disphenoid43", "Disphenoid51", "Disphenoid55", 
 "Disphenoid67", "FRD", "FRDr", "G", "GW", "H", "Hexplane2", 
 "Hexplane3", "Hexplane4", "Hexplane5", "HR", "HRTR", "HT", "HTHR", 
 "I6", "I8", "I9", "IWP", "IWPr", "Lidinoid", "Manta", "Manta35", 
 "Manta51", "N", "N14", "N26", "N38", "OCTO", "oD", "oH", "oP", 
 "oSP", "P", "P3a", "PFRD", "R2", "R3", "rPD", "RTW", "S", "SS", 
 "SS4", "SSP", "Starfish", "Starfish43", "Starfish47", "Starfish55",
 "Starfish59", "Starfish63", "Starfish75", "Starfish87", "tD", 
 "tDHandles", "TR", "TRHT", "TriCosta5", "TriCosta7", "Triplane1", 
 "Triplane2", "Triplane3", "Triplane4", "Triplane5", "Wei"};
drawCountrySVGWithLabeledPins[country_, itemQuantity_, 
 possibleItems_] := 
 Module[{ctry, divisions, divisionNameAtPoint, nPins, pts, mePos, 
 mypin, baseMap, baseSvgFile, svgText, pathData, divisionNames, 
 divisionIDs, nDiv, divStart, divEnd, divisionPaths, 
 divisionPathsWithID, svg1, pinPaths, pathsPerPin, pinLabels, 
 pinPathsWithTitles, svg2, svgWithClass, html, outputHtmlFile},
 
 ctry = Entity["Country", country];
 divisions = ctry["AdministrativeDivisions"];
 
 divisionNameAtPoint[pt_, divisions_] :=
 Module[{p = GeoPosition[pt]},
 With[{inside = 
 Pick[divisions, 
 GeoWithinQ[#, p] & /@ EntityValue[divisions, "Polygon"]]}, 
 If[inside =!= {}, EntityValue[First[inside], "Name"],
 EntityValue[First@GeoNearest[divisions, p, 1], "Name"]
 ]
 ]
 ];
 
 (*how many pins you want*)
 nPins = itemQuantity;
 pts = RandomGeoPosition[ctry, nPins][[1]];
 mePos = RandomGeoPosition[ctry];
 mypin = Graphics[GraphicsGroup[
 {FaceForm[Orange],
 FilledCurve[{
 {Line@Join[{{0, 0}},
 ({Cos[#], 3 + Sin[#]} & /@ 
 Range[-(2 Pi)/20, Pi + (2 Pi)/20, Pi/20]), {{0, 0}}]},
 {Line[(0.5 {Cos[#], 6 + Sin[#]} & /@ Range[0, 2 Pi, Pi/20])]}
 }]
 }],
 PlotRange -> All, Background -> None, ImageSize -> 24];
 baseMap = GeoGraphics[{
 GeoStyling[White], EdgeForm[{Black, Thick}], Polygon[divisions],
 Table[
 GeoMarker[pts[[i]], mypin, "Alignment" -> Bottom],
 {i, 1, nPins}],
 GeoMarker[mePos,
 Graphics[{Black, Disk[]}], "Alignment" -> Center, 
 "Scale" -> Offset[15]
 ],
 Text[Style["Me", Bold, Black, 12], mePos, {0, -2.5}]},
 GeoRange -> ctry,
 GeoRangePadding -> None, GeoBackground -> None, 
 AspectRatio -> Automatic, ImageSize -> {400, 400}];
 
 baseSvgFile = "C:\\Users\\" <> country <> "-base.svg";
 
 Export[baseSvgFile, baseMap, "SVG"];
 svgText = Import[baseSvgFile, "Text"];
 pathData = StringCases[svgText, RegularExpression["<path\\b[^>]*>"]];
 divisionNames = EntityValue[divisions, "Name"];
 divisionIDs = StringReplace[
 StringReplace[divisionNames, ", " <> country -> ""], {" " -> "-",
 "," -> ""}];
 nDiv = Length[divisions];
 divStart = 4;
 divEnd = 3 + nDiv;
 divisionPaths = pathData[[divStart ;; divEnd]];
 divisionPathsWithID = MapThread[
 StringReplace[#1, "<path " -> "<path id=\"" <> #2 <> "\" "] &,
 {divisionPaths, divisionIDs}];
 svg1 = 
 StringReplace[svgText, 
 Thread[divisionPaths -> divisionPathsWithID]];
 pinPaths = pathData[[divEnd + 1 ;;]];
 pathsPerPin = Length[pinPaths]/nPins;
 pinLabels = Table[
 StringRiffle[{
 RandomChoice[possibleItems],
 ToString[pts[[i]]],
 ToString[divisionNameAtPoint[pts[[i]], divisions]]},
 "\n"],
 {i, nPins}];
 pinPathsWithTitles = MapIndexed[
 Module[{idx = First[#2],
 group = Ceiling[First[#2]/pathsPerPin], label}, 
 label = pinLabels[[group]];
 StringReplace[
 StringReplace[#1, 
 "<path " -> "<path class=\"pinPath\" pointer-events=\"all\" "],
 "/>" -> 
 "><title class=\"romogiTooltip\">" <> label <> 
 "</title></path>"]
 ] &, pinPaths];
 svg2 = StringReplace[svg1, Thread[pinPaths -> pinPathsWithTitles]];
 svgWithClass = 
 StringReplace[svg2, "<svg " -> "<svg class=\"mapSvg\" ", 1];
 html = 
 StringRiffle[{"<!DOCTYPE html>", "<html>", "<head>", 
 "<meta charset=\"UTF-8\">", 
 "<title>" <> country <> " Map</title>", "<style>", "html, body {",
 " margin: 0;", " padding: 0;", " width: 100%;", 
 " height: 100%;", "}", "body {", " display: flex;", 
 " align-items: center;", " justify-content: center;", 
 " background: #ffffff;", " overflow: hidden;", "}", 
 "svg.mapSvg {", " max-width: 100%;", " max-height: 100%;", 
 " display: block;", "}", "</style>", "</head>", "<body>", 
 svgWithClass, "</body>", "</html>"}, "\n"];
 outputHtmlFile = "C:\\Users\\" <> country <> "-map.html";
 Export[outputHtmlFile, html, "Text"]
 ];
answered yesterday
$\endgroup$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.