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
-
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$Domen– Domen2025年11月17日 13:35:41 +00:00Commented 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$Teg Louis– Teg Louis2025年11月18日 01:45:19 +00:00Commented yesterday
1 Answer 1
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. (
baseSvgFileandoutputHtmlFile)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"]
];
Explore related questions
See similar questions with these tags.