<!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" style="--width: 131.21;">
<div id="menu">
<div>
Left: <input type="text" id="left" value="0" style="width: 5rem;"/>
Right: <input type="text" id="right" value="131.21"/>
</div>
Top: <input type="text" id="top" value="0"/>
Bottom: <input type="text" id="bottom" value="100"/>
<button onclick="updateVB();">Update</button>
</div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 131.21 100" width="524.84px" height="400px">
<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="#388">
<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="#720">
<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 id="vbLeft" style="left: 0px;"></div>
<div id="vbRight" style="left: calc(var(--width) * 4px + 16px);"></div>
<div id="vbTop" style="top: 75px;"></div>
<div id="vbBottom" style="top: 491px;"></div>
</div>
<script src="script.js"></script>
</body>
</html>
var activeRegion = false;
var svg = document.querySelector('svg');
var allGroups = svg.querySelectorAll('g:Not(#outline)');
allGroups.forEach((el) => {
el.addEventListener('click',regionClick);
});
document.getElementById('vbBottom').addEventListener('pointerdown',vbDown);
document.getElementById('vbRight').addEventListener('pointerdown',vbDown);
document.getElementById('vbTop').addEventListener('pointerdown',vbDown);
document.getElementById('vbLeft').addEventListener('pointerdown',vbDown);
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);
}
}
function regionDown(evt){
var el = evt.currentTarget;
var id = el.id;
if (id != activeRegion){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 updateVB() {
var l = parseFloat(document.getElementById('left').value);
var r = parseFloat(document.getElementById('right').value);
var t = parseFloat(document.getElementById('top').value);
var b = parseFloat(document.getElementById('bottom').value);
vbLTRB = [l,t,r,b];
width = r - l;
height = b - t;
svg.setAttribute('viewBox',l + " " + t + " " + width + " " + height);
svg.setAttribute('width',400*width/height+"px");
var vbb = document.getElementById('vbBottom')
vbb.style.top = (100*4+91)+"px";
vbb.style.width = width/height*400+"px";
var vbr = document.getElementById('vbRight')
vbr.style.left = (400*width/height+16)+"px";
var vbr = document.getElementById('vbLeft')
vbr.style.left = (0)+"px";
var vbt = document.getElementById('vbTop')
vbt.style.width = width/height*400+"px";
vbt.style.top = 75+"px";
}
function vbDown(evt){
var el = evt.currentTarget;
var id = el.id;
el.addEventListener('pointerup',vbUp);
el.addEventListener('pointercancel',vbUp);
el.addEventListener('pointerout',vbUp);
el.addEventListener('pointermove',vbMove);
console.log(evt.clientX,evt.clientY);
bcr = svg.getBoundingClientRect();
if (id == "vbBottom"){
initialVB[0] = (evt.clientY-bcr.top);
initialVB[1] = parseFloat(document.getElementById('vbBottom').style.top);
}
else if (id == "vbRight") {
initialVB[0] = (evt.clientX-bcr.left);
initialVB[1] = parseFloat(document.getElementById('vbRight').style.left);
}
else if (id == "vbTop") {
initialVB[0] = (evt.clientY-bcr.top);
initialVB[1] = parseFloat(document.getElementById('vbTop').style.top);
}
else if (id == "vbLeft") {
initialVB[0] = (evt.clientX-bcr.left);
initialVB[1] = parseFloat(document.getElementById('vbLeft').style.left);
}
}
function vbMove(evt){
var el = evt.currentTarget;
var id = el.id;
var eq = [(evt.clientX-bcr.left), (evt.clientY-bcr.top)];
var offset;
if (id == "vbBottom"){
offset = eq[1]-initialVB[0];
document.getElementById('vbBottom').style.top = (initialVB[1]+offset)+"px";
}
else if (id == "vbRight"){
offset = eq[0]-initialVB[0];
document.getElementById('vbRight').style.left = (initialVB[1]+offset)+"px";
}
else if (id == "vbTop"){
offset = eq[1]-initialVB[0];
document.getElementById('vbTop').style.top = (initialVB[1]+offset)+"px";
}
else if (id == "vbLeft"){
offset = eq[0]-initialVB[0];
document.getElementById('vbLeft').style.left = (initialVB[1]+offset)+"px";
}
}
function vbUp(evt){
var el = evt.currentTarget;
el.removeEventListener('pointerup',vbUp);
el.removeEventListener('pointercancel',vbUp);
el.removeEventListener('pointerout',vbUp);
el.removeEventListener('pointermove',vbMove);
var id = el.id;
var eq = [(evt.clientX-bcr.left), (evt.clientY-bcr.top)];
var offset;
if (id == "vbBottom"){
offset = eq[1]-initialVB[0];
document.getElementById('vbBottom').style.top = (initialVB[1]+offset)+"px";
document.getElementById('bottom').value = parseFloat(document.getElementById('bottom').value)+offset*height/bcr.height;
}
else if (id == "vbRight"){
offset = eq[0]-initialVB[0];
document.getElementById('vbRight').style.left = (initialVB[1]+offset)+"px";
document.getElementById('right').value = parseFloat(document.getElementById('right').value)+offset*width/bcr.width;
}
else if (id == "vbTop"){
offset = eq[1]-initialVB[0];
document.getElementById('vbTop').style.top = (initialVB[1]+offset)+"px";
document.getElementById('top').value = parseFloat(document.getElementById('top').value)+offset*height/bcr.height;
}
else if (id == "vbLeft"){
offset = eq[0]-initialVB[0];
document.getElementById('vbLeft').style.left = (initialVB[1]+offset)+"px";
document.getElementById('left').value = parseFloat(document.getElementById('left').value)+offset*width/bcr.width;
}
}
.interior {
stroke-width: var(--sw);
}
.selected path {
stroke-dasharray: 2 1 1 1;
stroke-width: max(var(--sw), 6);
}
.outline {
stroke-width: calc(1.5 * var(--sw));
stroke: black;
fill: none;
}
#container {
position: relative;
}
#container > svg {
position: absolute;
left: 16px;
top: 91px;
}
#vbTop {
display: inline-block;
position: absolute;
left: 16px;
height: 16px;
width: calc(var(--width) * 4px);
background: blue;
}
#vbLeft {
display: inline-block;
position: absolute;
top: 91px;
width: 16px;
height: 400px;
background: blue;
}
#vbBottom {
display: inline-block;
position: absolute;
left: 16px;
height: 16px;
width: calc(var(--width) * 4px);
background: blue;
}
#vbRight {
display: inline-block;
position: absolute;
top: 91px;
width: 16px;
height: 400px;
background: blue;
}
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 edit the viewBox via direct input. Set the left, right, top , and bottom. Or move the blue bars to change the size. Click update to see the new viewBox.
The script.js allows you to edit the viewBox. Handling text input is relatively easy, but moving the bars to change the viewBox requires a bit more code.
You probably want to add more functionality, but this code could get you started to easily change the viewBox to fit your needs.