SVGs on Custom Pages: Difference between revisions

From MidasWiki
Jump to navigation Jump to search
(The midas documentation has moved to https://midas.triumf.ca/docs/)
Tag: Replaced
 
(3 intermediate revisions by one other user not shown)
Line 1: Line 1:
[[File:SVGcustom_page.svg|thumbnail|none|Figure 9: SVG based custom page]]
The midas documentation has moved. See [https://midas.triumf.ca/docs/develop/features/monitoring_and_control/custom_pages/dialogs/#diagrams Diagrams] in Custom pages: dialogs, tabs, sliders and diagrams for the new documentation about using SVG images on custom pages, including this page's example of valves and an expansion volume.
 
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 ==
 
The custom page displays the SVG inside an <code>mtable</code>.
The two valves turn green when open and red when closed.
This is done by retrieving the SVG element using <code>objVal = svgDoc.getElementById(valveID)</code> and setting its fill color via <code>obj_Valve.style.fill = ...</code>
 
 
Sets of <code>modbbutton</code> are used to change the associated ODB boolean variable. A confirmation dialog appears to prevent accidental valve operation.
 
 
The expansion volume shows its fill state both via a <code>modbvbar</code> and by adjusting the height of the corresponding SVG object.
Depending on how the object is constructed in Inkscape, scaling and translating it can be non-trivial.
 
 
The example code below keeps the object's bottom edge fixed while scaling its height based on the fill level.
This means that only the y-dimension is scaled.
Since scaling also shifts the y-position, the original position and height must be retrieved first.
There may also be an additional scaling factor in the SVG, which is why the <code>updateVolume</code> function first compares the <code>viewBox</code> parameters.
Then, the position and dimensions are extracted from the transformation matrix defined in the XML.
 
<pre>
<!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>
</pre>
 
== SVG XML code ==
 
<pre>
 
<?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>
 
</pre>
 
 
[[Category:Custom]]

Latest revision as of 22:01, 30 October 2026

The midas documentation has moved. See Diagrams in Custom pages: dialogs, tabs, sliders and diagrams for the new documentation about using SVG images on custom pages, including this page's example of valves and an expansion volume.