<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="favicon.png">
<title></title>
<meta name="description" content="" />
<link href="style.css" rel="stylesheet" type="text/css" />
<style>
</style>
</head>
<body>
<div id="container">
<div id="menu">
<input type="color" onchange="chgColor(this.value)" value="#FFFFFF" />
<button onclick="copySVG()">Copy SVG</button>
<button onclick="downloadSVG()">Download SVG</button>
Region Size: <input type="range" oninput="setRegionSize(Math.pow(10,(this.value-50)/50));" id="regionSize" min="0" max="100" value="50">
<button onclick="duplicateRegion()">Duplicate</button>
<button onclick="deleteRegion()">Delete</button>
</div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 131.21 100" width="524.84px" height="400px">
<style>
.interior {
stroke-width: 0.25;
stroke: gray;
}
.selected path {
stroke-width: 2;
stroke: red;
}
.outline {
stroke-width: 1;
stroke: black;
fill: none;
}
text {
fill: white;
font-size: var(--fs);
pointer-events: none;
cursor: pointer;
}
</style>
<g id="region-GA" fill="#547">
<path id="GA-0" d="M 10.45 87.96 9.81 86.18 8.99 84.25 9.35 81.77 9.67 79.1 8.97 76.24 9.69 71.4 11.82 68.72 10.69 65.99 8.84 61.2 5.8 45.86 2.44 26.44 12.39 26.58 19.28 26.42 35.35 26.55 33.93 27.86 31.86 30.84 35.35 33.44 37.59 34.4 39.99 39.34 41.53 42.11 46.03 45.79 46.91 47.72 49.98 50.25 51.48 53.93 55.61 57.03 56.53 60.55 57.29 62.25 56.85 63.37 59.25 65.05 60.52 67.92 60.5 70.82 61.69 71.39 63.9 72.17 57.81 81.17 55.82 91.88 53.1 91.6 50.54 90.5 48.99 91.01 48.95 96.22 47.28 97.56 46.33 94.94 25.39 92.7 12.41 92.01 10.45 87.96" class="interior"></path>
</g>
<g id="region-SC" fill="#508">
<path id="SC-0" d="M 63.9 72.17 61.69 71.39 60.5 70.82 60.52 67.92 59.25 65.05 56.85 63.37 57.29 62.25 56.53 60.55 55.61 57.03 51.48 53.93 49.98 50.25 46.91 47.72 46.03 45.79 41.53 42.11 39.99 39.34 37.59 34.4 35.35 33.44 31.86 30.84 33.93 27.86 35.35 26.55 36.64 26.08 43.61 23.38 55.52 23.51 61.56 24.24 61.66 25.63 62.96 24.59 64.98 27.26 64.95 29.09 79.29 29.25 93.73 44.04 87.19 49.74 85.35 54.93 71.18 64.9 63.9 72.17" class="interior"></path>
</g>
<g id="region-NC" fill="#437">
<path id="NC-0" d="M 35.35 26.55 19.28 26.42 19.57 23.08 22.3 22.09 23.2 20.39 25.03 18.47 27.64 18.05 30.68 17.32 33.63 15.95 34.9 14.54 37.36 13.27 37.3 12.12 40.54 9.96 41.59 11.36 46.35 8.35 48.58 8.67 50.57 5.98 53.2 5.29 53.06 3.0 53.38 0.98 48.34 1.22 53.38 0.98 75.16 1.68 100.89 1.75 114.56 1.62 126.79 1.54 128.41 1.53 130.23 17.51 122.02 29.23 108.66 33.89 100.18 43.04 93.73 44.04 79.29 29.25 64.95 29.09 64.98 27.26 62.96 24.59 61.66 25.63 61.56 24.24 55.52 23.51 43.61 23.38 36.64 26.08 35.35 26.55" class="interior"></path>
</g>
</svg>
</div>
<script src="script.js"></script>
</body>
</html>
var activeRegion = false;
var activeScale = 50;
var moveLabel = false;
var svg = document.querySelector('svg');
var allGroups = svg.querySelectorAll('g:Not(#outline)');
allGroups.forEach((el) => {
el.addEventListener('click',regionClick);
});
var bcr = svg.getBoundingClientRect();
var initialEquivalent = [0,0];
var initialVB = [0,0];
var width = 131.21;
var height = 100;
var vbLTRB = [0,0,width,100];
function regionClick(evt){
var id = evt.currentTarget.id;
if (id == activeRegion){
//activeRegion = false;
}
else {
activeRegion = id;
}
allGroups.forEach((el) => {
el.classList.remove('selected');
el.removeEventListener('pointerdown',regionDown);
});
if (activeRegion){
var el = svg.querySelector("#"+activeRegion);
el.classList.add('selected');
el.addEventListener('pointerdown',regionDown);
var oldT = el.getAttribute('transform');
activeScale = 50;
if (oldT){
var idx = oldT.indexOf('scale(');
if (idx >= 0){
var tstring = oldT.substring(idx+6).split(")")[0].trim();
var rawScale = parseFloat(tstring);
activeScale = Math.round(Math.log10(rawScale)*50+50);
}
}
document.getElementById('regionSize').value = activeScale;
}
}
function regionDown(evt){
var el = evt.currentTarget;
var id = el.id;
if (moveLabel && id && id.substring(0,7) == "region-"){return;}
else if (id != activeRegion && !moveLabel){return;}
el.addEventListener('pointerup',regionUp);
el.addEventListener('pointermove',regionMove);
console.log(evt.clientX,evt.clientY);
bcr = svg.getBoundingClientRect();
initialEquivalent[0] = (evt.clientX-bcr.left)*width/bcr.width;
initialEquivalent[1] = (evt.clientY-bcr.top)*height/bcr.height;
var offset = el.getAttribute('transform');
if (offset){
console.log(offset);
var idx = offset.indexOf('translate(');
if (idx >= 0){
var tstring = offset.substring(idx+10).split(")")[0].trim();
var x = parseFloat(tstring.split(" ")[0]);
var y = parseFloat(tstring.split(" ")[1]);
initialEquivalent[0] -= x;
initialEquivalent[1] -= y;
}
}
}
function regionMove(evt){
var el = evt.currentTarget;
var id = el.id;
var eq = [(evt.clientX-bcr.left)*width/bcr.width, (evt.clientY-bcr.top)*height/bcr.height];
var offset = [eq[0]-initialEquivalent[0], eq[1]-initialEquivalent[1]];
var t = "translate("+offset[0]+" "+offset[1]+")";
el.setAttribute('transform',t+" scale("+Math.pow(10,(activeScale-50)/50)+")");
}
function regionUp(evt){
var el = evt.currentTarget;
el.removeEventListener('pointerup',regionUp);
el.removeEventListener('pointermove',regionMove);
var id = el.id;
var eq = [(evt.clientX-bcr.left)*width/bcr.width, (evt.clientY-bcr.top)*height/bcr.height];
var offset = [eq[0]-initialEquivalent[0], eq[1]-initialEquivalent[1]];
var t = "translate("+offset[0]+" "+offset[1]+")";
el.setAttribute('transform',t+" scale("+Math.pow(10,(activeScale-50)/50)+")");
}
function copySVG(){
var str = svg.outerHTML;
navigator.clipboard.writeText(str);
}
function downloadSVG() {
var svgString = svg.outerHTML;
var encodedSVG = svgString.replace(/"/g, '\'')
.replace(/%/g, '%25')
.replace(/#/g, '%23')
.replace(/</g, '%3C')
.replace(/>/g, '%3E')
.replace(/\s+/g,' ');
var url = 'data:image/svg+xml,'+encodedSVG;
var a = document.createElement('a');
a.setAttribute('href',url);
a.setAttribute('download','image.svg');
a.click();
}
function toggleMoveLabel(){
if (moveLabel){
moveLabel = false;
}
else {
moveLabel = true;
}
var els = svg.querySelectorAll('text');
els.forEach((el) => {
if (moveLabel){
el.style.pointerEvents = "all";
el.addEventListener('pointerdown',regionDown);
}
else {
el.style.pointerEvents = "none";
el.removeEventListener('pointerdown',regionDown);
}
})
}
function setRegionSize(sz){
if (!activeRegion){return;}
activeScale = Math.round(Math.log10(sz)*50+50);
var el = document.getElementById(activeRegion);
var oldT = el.getAttribute('transform');
var t = 'scale('+sz+')';
if (oldT){
var idx = oldT.indexOf('translate(');
if (idx >= 0){
var tstring = oldT.substring(idx).split(")")[0].trim()+")";
t = tstring + " "+t;
}
}
el.setAttribute('transform',t);
}
function duplicateRegion(){
if (!activeRegion){return;}
var el = document.getElementById(activeRegion);
clone = el.cloneNode(true);
clone.id = activeRegion+"-1";
clone.addEventListener('click',regionClick);
svg.appendChild(clone);
allGroups = svg.querySelectorAll('g:Not(#outline)');
}
function deleteRegion(){
if (!activeRegion){return;}
var el = document.getElementById(activeRegion);
el.parentElement.removeChild(el);
}
function chgColor(color){
if (!activeRegion){return}
var el = document.getElementById(activeRegion);
el.setAttribute('fill',color);
}
#container {
position: relative;
}
#container > svg {
position: absolute;
left: 16px;
top: 66px;
}
This repl includes a few JavaScript functions that allow you to move shapes from an SVG element.
To view this on TripleLog, click here.
The index.html is generated from other repls and includes SVG elements for the states of Georgia, South Carolina, and North Carolina.
The menu at the top allows you to change the fill color of regions, change the size of a region, delete regions, or duplicate regions. You can also move regions by clicking and dragging.
Once the SVG is as desired, click the Copy SVG button to copy the SVG to your clipboard (this seems to work in modern browsers) or click the download SVG button to download an SVG file.
The script.js allows you to edit the SVG. It also has functions to copy the SVG or download. Copying to the clipboard might depend on browsers, but the writeText() method seems to work on modern browsers.
You probably want to add more functionality.