SVGs on Custom Pages
Jump to navigation
Jump to search
In this example, an SVG image forms the basis of a custom page. It displays two valves and an expansion volume.
The valves change color depending on their status — open or closed. The expansion volume dynamically changes its height.
All objects within the SVG file are directly modified using JavaScript functions.
Custom Page HTML code
<!DOCTYPE html>
<html class="mcss">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="midas.css">
<script src="controls.js"></script>
<script src="midas.js"></script>
<script src="mhttpd.js"></script>
<title>Helium example</title>
<style>
.modbbutton {font-size:20px;}
.text {font-size:20px;}
</style>
<script>
// Valve Coloring --------------------------------------------------------------
function updateValveColor(flag, valveID) {
console.log(valveID, flag);
const svgDoc = document.getElementById('svgObject').contentDocument;
//The valveID needs to match the ID given in the svg file
const obj_Valve = svgDoc.getElementById(valveID);
obj_Valve.style.fill = (flag === 1) ? "#00FF00" : "#FF0000";
}
// Confirmation routines --------------------------------------------------------
function confirmValve(arg, valveAddress) {
const txt = arg === "Close" ? "Are you sure to close the valve?" : "Are you sure to open the valve?";
const val = arg === "Open" ? 1 : 0;
dlgConfirm(txt, function(confirmed) {
if (confirmed) {
modbset(valveAddress, val);
}
});
return false; // Prevent ODB value change immediately
}
// Animation -------------------------------------------------------------------
/**
* Update the volume of an SVG element by applying a transformation based on input.
* @param {number} elem - The scale factor to modify the volume.
* @param {string} svgID - The ID of the SVG element to be transformed.
* Comment: This example specifically transform only the ySize of the object,
* with the special case that the bottom edge stays in place
*/
function updateVolume(elem, svgID) {
const svgDoc = document.getElementById('svgObject').contentDocument;
const rootSvg = svgDoc.documentElement;
// Get the width and height of the root SVG element.
const widthAttr = rootSvg.getAttribute("width");
const heightAttr = rootSvg.getAttribute("height");
// Get and parse the viewBox attribute.
const viewBoxAttr = rootSvg.getAttribute("viewBox");
const viewBoxParts = viewBoxAttr.split(" ").map(parseFloat);
const viewBoxWidth = viewBoxParts[2];
const viewBoxHeight = viewBoxParts[3];
// Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.
const xScale = widthAttr / viewBoxWidth;
const yScale = heightAttr / viewBoxHeight;
// Get the element to modify based on the provided svgID.
const obj_Vol = svgDoc.getElementById(svgID);
const bbox = obj_Vol.getBBox();
// Extract the transformation matrix from the element's transform attribute.
const transformAttr = obj_Vol.getAttribute("transform");
const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);
let transformedY, transformedHeight;
// Apply matrix transformation if it exists.
if (matrixMatch) {
const [a, b, c, d, e, f] = matrixMatch[1].split(",").map(parseFloat);
// Apply matrix to the top-left corner of the bbox to get transformed Y position and height.
transformedY = d * bbox.y + f;
transformedHeight = d * bbox.height;
}
// Scale factor is 0.01 times the provided 'elem' value.
const scale = 0.01 * elem;
// Calculate the new Y position after applying scaling.
const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);
// Update the element's transform attribute with the new translation and scaling.
// In this example only the height (y-axis) is changed
updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);
}
/**
* Updates the transform attribute of an SVG element by applying new translation and scale values.
* @param {Element} elem - The SVG element whose transform attribute is to be updated.
* @param {string} newTranslate - The new translation value as a string (e.g., "0,100").
* @param {string} newScale - The new scale value as a string (e.g., "1,0.5").
*/
function updateTransform(elem, newTranslate, newScale) {
let transform = elem.getAttribute("transform") || "";
// Regular expressions to match existing translate and scale transformations.
const translateRegex = /translate\(([^)]+)\)/;
const scaleRegex = /scale\(([^)]+)\)/;
// Remove any existing translate and scale transformations from the transform string.
transform = transform
.replace(translateRegex, '')
.replace(scaleRegex, '')
.trim();
// Construct the new transform string with updated translation and scale.
const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();
// Apply the new transform to the element.
elem.setAttribute("transform", newTransform);
}
</script>
</head>
<body class="mcss" onload="mhttpd_init('Example');">
<!-- header and side navigation will be filled in mhttpd_init -->
<div id="mheader"></div>
<div id="msidenav"></div>
<div id="mmain">
<!-- modb values to listen for status changes -->
<div id="filling_valve" class="modb" data-odb-path="/Equipment/Example/Filling Valve" onchange="updateValveColor(this.value,'Filling_valve');" onload="updateValveColor(this.value,'Filling_valve');">
<div id="exhaust_valve" class="modb" data-odb-path="/Equipment/Example/Exhaust Valve" onchange="updateValveColor(this.value,'Exhaust_valve');" onload="updateValveColor(this.value,'Exhaust_valve');">
<table class="mtable">
<tr><th class="mtableheader">Helium example</th></tr>
<tr><td>
<div style="position:relative;width:600px;margin:auto">
<!-- Import of a single SVG file, colors and sizes are changed directly for each object -->
<object id="svgObject" type="image/svg+xml" data="Midas_helium_example.svg"></object>
<!-- Control and monitoring instances -->
<!-- Filling line -->
<button class="modbbutton" class="mbutton" data-odb-path="/Equipment/Example/Filling Valve"
style="position:absolute; top: 473px;left:125px;"
data-validate='confirmValve("Open","/Equipment/Example/Filling Valve")'
data-odb-value="1">Open</button>
<button class="modbbutton" class="mbutton" data-odb-path="/Equipment/Example/Filling Valve"
style="position:absolute; top: 473px;left:195px;"
data-validate='confirmValve("Close","/Equipment/Example/Filling Valve")'
data-odb-value="0">Close</button>
<div class="text" style="position:absolute; top: 443px; left:125px; font-weight: bold;">Filling valve:</div>
<!-- Exhaust line -->
<button class="modbbutton" class="mbutton" data-odb-path="/Equipment/Example/Exhaust Valve"
style="position:absolute; top: 300px;left:352px;"
data-validate='confirmValve("Open","/Equipment/Example/Exhaust Valve")'
data-odb-value="1">Open</button>
<button class="modbbutton" class="mbutton" data-odb-path="/Equipment/Example/Exhaust Valve"
style="position:absolute; top: 300px;left:422px;"
data-validate='confirmValve("Close","/Equipment/Example/Exhaust Valve")'
data-odb-value="0">Close</button>
<div class="text" style="position:absolute; top: 270px; left:352px; font-weight: bold;">Exhaust valve:</div>
<!-- Expansion Volume -->
<div class="modbvbar" data-odb-path="/Equipment/Example/Expansion volume"
style="width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px"
data-min-value="0" data-max-value="100" data-log="0"
onchange="updateVolume(this.value, 'Expansion_volume')"></div>
<div class="mvaxis" style="width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left"
data-min-value="0" data-max-value="100" data-log="0"></div>
</td></tr>
</table>
</div>
</body>
</html>
SVG XML code
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="600"
height="750"
viewBox="0 0 600 750.00002"
version="1.1"
id="svg1"
inkscape:version="1.4.1 (unknown)"
sodipodi:docname="Midas_helium_example.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="2.0661644"
inkscape:cx="423.974"
inkscape:cy="308.30073"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1">
<marker
style="overflow:visible"
id="marker2"
refX="0"
refY="0"
orient="auto-start-reverse"
inkscape:stockid="Triangle arrow"
markerWidth="1"
markerHeight="1"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid">
<path
transform="scale(0.5)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="M 5.77,0 -2.88,5 V -5 Z"
id="path2" />
</marker>
</defs>
<g
inkscape:label="Gas system"
inkscape:groupmode="layer"
id="layer1">
<g
id="Gas_cabinet"
inkscape:label="Gas_cabinet"
style="display:inline"
transform="matrix(4.056096,0,0,4.056096,-726.2293,-806.75454)">
<rect
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.264635;stroke-miterlimit:3.2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="Gas_cabinet_outline"
width="45.862354"
height="58.905613"
x="267.93396"
y="308.49619"
rx="0"
ry="0"
inkscape:label="Gas_cabinet_outline" />
<g
id="Gas_bottle"
transform="matrix(3.6447445,0,0,3.4512234,112.28368,-106.71016)"
style="display:inline;mix-blend-mode:normal;fill:#ffffff;stroke-width:0.118799"
inkscape:label="Gas_bottle">
<path
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 45.466186,125.93812 v -1.31329 c 0,0 0.07542,-0.46399 0.475282,-0.46399 0.43403,0 0.541916,0.46399 0.541916,0.46399 v 1.31329"
id="path11882"
sodipodi:nodetypes="ccscc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 44.681364,126.73032 c 0,0 0.09055,-0.94582 1.313641,-0.94582 1.120378,0 1.273201,0.94582 1.273201,0.94582 v 9.66595 h -2.586842 z"
id="path11880"
sodipodi:nodetypes="cscccc" />
</g>
<text
xml:space="preserve"
style="font-weight:bold;font-size:13.3333px;font-family:Laksaman;-inkscape-font-specification:'Laksaman Bold';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:3.77953"
x="179.48"
y="151.93512"
id="Gas_cabinet_title"
transform="matrix(0.49308498,0,0,0.49308498,179.04638,229.4703)"
inkscape:label="Gas_cabinet_title"><tspan
sodipodi:role="line"
style="font-size:13.3333px;fill:#000000;fill-opacity:1;stroke-width:3.77953"
x="179.48"
y="151.93512"
id="tspan96-8-4-0">Gas cabinet</tspan></text>
</g>
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2"
d="M 408.62454,498.40226 V 468.28588 H 288.15902 v 64.98024 H 93.771462 V 255.7319"
id="Filling_line"
inkscape:label="Filling_line"
sodipodi:nodetypes="cccccc" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 220.44846,552.04894 v -37.8841 l -58.46319,37.86918 v -37.8843 z"
id="Filling_valve"
sodipodi:nodetypes="ccccc"
inkscape:label="Filling_valve" />
<text
xml:space="preserve"
style="font-weight:bold;font-size:26.6666px;font-family:Laksaman;-inkscape-font-specification:'Laksaman Bold';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:7.55906"
x="26.068356"
y="43.542694"
id="Expansion_volume_title"
inkscape:label="Expansion_volume_title"><tspan
sodipodi:role="line"
style="font-size:26.6666px;fill:#000000;fill-opacity:1;stroke-width:7.55906"
x="26.068356"
y="43.542694"
id="tspan96-8-8">Expansion volume</tspan></text>
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;marker-end:url(#marker2)"
d="m 251.67406,254.578 v 104.14268 h 97.7657 181.89378 V 251.47464"
id="Exhaust_line"
inkscape:label="Exhaust_line"
sodipodi:nodetypes="ccccc" />
<g
id="Expansion_volume"
inkscape:label="Expansion_volume"
style="display:inline"
transform="matrix(7.5560886,0,0,7.5560886,-161.95634,-143.19437)">
<path
style="mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 25.181199,26.590622 h 37.055527 l -2.746143,6.664982 2.885185,6.396771 -2.746137,6.65182 2.919945,6.490565 H 24.903107 l 2.798286,-6.490565 -2.937328,-6.813076 2.885185,-6.235515 z"
id="path4823"
sodipodi:nodetypes="ccccccccccc" />
<path
style="mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1"
d="M 27.691557,33.255604 H 59.644908"
id="path3275"
sodipodi:nodetypes="cc" />
<path
style="mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1"
d="M 24.974876,39.556896 H 62.361591"
id="path3277"
sodipodi:nodetypes="cc" />
<path
style="mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1"
d="m 27.950288,46.304195 h 31.69462"
id="path3279"
sodipodi:nodetypes="cc" />
</g>
<path
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 449.20856,377.30494 v -37.8841 l -58.4632,37.86918 v -37.8843 z"
id="Exhaust_valve"
sodipodi:nodetypes="ccccc"
inkscape:label="Exhaust_valve" />
</g>
</svg>