<!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">
Label Size: <input type="range" oninput="svg.style.setProperty('--fs',0.5*Math.pow(10,(this.value-10)/10)+'rem');" id="labelSize" min="0" max="20" value="10">
<button onclick="toggleMoveLabel()">Move Label(s)</button>
</div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox='0 0 131.21 100' style="--fs:0.5rem;" width='524.84px' height='400px'>
<g id="region-GA" fill='#608' >
<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" />
<text dominant-baseline="middle" text-anchor="middle" x='30.45' y='62.34'>
GA
</text>
</g>
<g id="region-SC" fill='#013' >
<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" />
<text dominant-baseline="middle" text-anchor="middle" x='63.62' y='43.28'>
SC
</text>
</g>
<g id="region-NC" fill='#704' >
<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" />
<text dominant-baseline="middle" text-anchor="middle" x='84.72' y='17.64'>
NC
</text>
</g>
</svg>
</div>
<script src="script.js"></script>
</body>
</html>
var activeRegion = false;
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 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);
}
}
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);
}
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);
}
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);
}
})
}
.interior {
stroke-width: 0.25;
stroke: gray;
}
.selected path {
stroke-width: 2;
stroke: red;
}
.outline {
stroke-width: 1;
stroke: black;
fill: none;
}
#container {
position: relative;
}
#container > svg {
position: absolute;
left: 16px;
top: 66px;
}
#vbTop {
display: inline-block;
position: absolute;
left: 16px;
height: 16px;
width: calc({{width * 4}}px);
background: blue;
}
text {
fill: white;
font-size: var(--fs);
pointer-events: none;
cursor: pointer;
}
This repl includes a few JavaScript functions that allow you to move the labels on SVG shapes.
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. Each has a label centered on the centroid of the state.
Clicking the Move Label(s) button toggles between moving the labels or the shapes, but there is not currently any indication of which mode is currently active.
The script.js allows you to move the labels or the shapes. When the labels move, they stay in the same location relative to the shape regardless if that shape moves later.
You can also increase or decrease the size of all labels.
You probably want to add more functionality, but this code could get you started to easily place labels exactly where you want them.