SVGs on Custom Pages

From MidasWiki
Revision as of 13:11, 12 May 2025 by Rudzki (talk | contribs) (Created page with "thumbnail|none|Figure 9: SVG based custom page 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 == <pre> <!DOCTYPE html> <html class="mcss"> <head>...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
Figure 9: SVG based custom page

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>