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>