X-Y Graphics in Live

Hello all,

I'm new in Live and I need to create a graphic that show the Curve of a Pump (Head vs Flow) I did it in Operate but is it possible to do in Live and How?

Thanks, 

Luis

  • Can you post what you did or how you accomplished in Operate?

    Is it based on data reading from modules, etc?
  • Hi Luis.

    We have what sound like similar graphics for compressor anti-surge control and have recently migrated from Operate to Live. In both cases, the plots are made up of multiple individual lines with their geometry (start X/Y, end X/Y) linked to array parameters from a module.

    Operate:

    Live:

    Martin

  • In reply to Martin Rooke:

    Example Live config:

  • Luis, I've been working on a pump curve solution as well.  I also used Mike's approach of creating a segmented line based on a control module array.  Below is my original application for an X/Y chart to plot a Signal Characterizer.

    This has 20 line segments with the Start/End points based on the coordinate pairs in the Curve_X and Curve Y arrays of the SGCHR block.  In this case, I added the curve data points to facilitate tuning the curves.  

    For a Pump Curve, I created a  signal characterizer curve for the Head to Flow for the Pump speed based on the manufacturer.  This plots the curve and also produces the expected flow at a given Head.  

    I plot the actual Head/Flow point with a reticle that shows the actual flow relative to the curve.  I use the expected flow to set a color animation on the reticle if it exceeds a certain deadband. 

    I think the trick here is to create a GEM for the curve.  In my case, I created a plot area gem that is 100 x 100 pt, and that aligns with a the 0-100% range I used in the two curves.  That meant I didn't have to worry about rescaling the data in the graphic.  In Mikes example, he has set the position in inches, and the scale of the value is applied to 10.61 in the X axis and 8.75 in the Y.

    When a measurement animation like this, I struggled a bit.  In this example, there is no Scale parameter, so the default scale of 0 to 100% is used.  I think what happens is the value is scaled into the Start and End point values, such that a value of 0 (in this case) is converted to 0 inches, and a value of 100 is converted to 10.61 inches.  Do the same for the End point of the line. 

    However, I had a problem that Mike seems to have overcome.  When you draw a line on your GEM grid, the line gets its position coordinates, and from there it plots the Start and End points.  The Position of the line is relative to the container (Gem, or display, or group).  If the slope is negative, the Start Point will be 0,0, i.e. at the line position (origin).  IF the line slope is positive, X1 = 0, but Y1 is some positive number.    So a line has three sets of coordinates.

    Since this line is positioned dynamically at run time, I'm wondering if Mike has set all the line positions to be the same in the GEM, and by animating the start and end points relative to a fixed position, the Start and End values will set the line segment at runtime.  I will have to try this.

    Mike, can you confirm how you set the position coordinates of the lines?  Are they set to (0, 0,) or (0, 8.75)?  and you only animate the Start and End Points?

    My curve animation is probably overly complicated because I set the Line position and derive X1,Y1 and X2, Y2 relative to line position, which meant I had to consider slope.  Crazy.  I'm going to try to see if I can just set start and end coordinates with a fixed common origin (position) on all the lines.  I notice that Mike's lines are a lot smoother, where as mine are a bit jagged where segments "join".  

    On my Pump Curve,

    My challenge was a variable speed pump, where the pump curve moved through the operating range.  Again, I'm using a Signal Characterizer in the module for the pump curve. 

    At first, I used a calc block to adjust the curve values based on the Pump Speed.  I triggered the curve update with a speed deadband so I did not do the calculation every scan, and also had an adjustment to calculate no faster than every X seconds.  What I found was the pump curves are generally the same shape over the operating range of the pump.  The curve would be a bit expanded and shifted to create the "operating range" of the pump.  This made me think that instead of updating the curve based on speed, I can set the curve at minimum speed and make an adjustment on the output of the curve based on the actual speed, shifting the expected Flow value for a given Head, based on the speed. 

    In the display, I adjust the position and size of the curve based on the speed.  This gets me a dynamic curve that is more responsive without using more controller CPU.  The Actual Head/Flow coordinate is plotted as before with a Reticle and animated if actual deviates to far from normal curve, indicating wear or cavitation. 

    The control module signal characterizer allows me to know what the flow should be and set an abnormal condition in the plot.   I'm using a bit map image of the pump operating range as the back drop for the pump curve, which provides some color.  This is sized to the plot GEM and the dynamic curve is calibrated to overlay this chart.  Still experimenting to figure out how to handle different pump curves and data from manufactures.  I'm leaning to creating a Spread sheet for each pump model where I can generate the backdrop "image" plot and the Signal characterizer values to use in the module.  

    Other notes:

    I tried using the freeform line in Live.  To animate it at different speeds, I found that you cannot adjust the size of the line shape at runtime.  Had to abandon that idea.

    Worked out using a splined line that was manually set, and I grouped this to resize and move.  That worked, but if I ungrouped it for some reason, the first and last line points were not properly set and would maintain their group value but apply it to the display.  Moved the line into a GEM and manipulate the gem size and position instead.  No need to group the lines in that GEM.  And if I do, the group is the same size as the GEM so no problem ungrouping.

    In the end, I think that the x/y plot from the Sigchar block gives the best approach, and it's easier to tune the curve through the array.  Map the arrays to a detail faceplate so you can tune it from Live while viewing the curve.  

    Andre Dicaire

  • In reply to Luis G. Rodriguez:

    Hey Luis,  I did a bit of testing as I too need my x/y plot to work well.  What I did was to configure each line using the property animation, like Mike did.  The Line Horizontal and vertical positions were left at 0, which sets all the line positions at the upper left corner of my GEM.  The Gem is simply a Box to provide a background and 20 lines that are configured to read the Curve_X and Curve_Y arrays of a Signal Characterizer.

    I created the GEM to be 500 x 500 pt.  For each line segment, the start and end points are defined based on this size.

    Note that for the vertical properties, I set the Start point to 500 and End Point to 0.  This scales the 0% value to align with the bottom of the GEM.  

    in this example, I displayed the position of Line 1 (0,0,) and show the X1/Y1, and X1/Y2 properties of the line.  The scale is 50 to 200 for both x and y.  So when Curve_Y[1][1] is 50, the line should start at the bottom of the plot.  And that requires the Horizontal Start to be 500.  And that's what you see.  I guess it would be clearer if I'd used a 0 based EU range, but I wanted to show that by using a Scaling parameter, it works for any scale you need in the Signal Characterizer.

    The Table to the right is a GEM I made to allow me to "tune" the Signal characterizer.   Makes it easy to work with the curve.

    the lesson I learned is the property animation will scale the value to the measurement range you define in the Start Point and End point fields.  By defining a Scaling parameter, the value is converted properly to the measurement range, in my case 0 to 500 for both x and y.  By inverting the values on vertical properties, everything works and I get a clean line.

    However, if I resize this GEM, the actual measurement of the GEM at runtime is different, but the animated line positioning is scaled to the Start and End Points and these cannot be animated.  So the line draws based on the original 500 pt size.  If I make the plot area have the size (250 x 250) that 0 value positions the line 250 pt below the plot area.  Arrgh.  

    To resolve this and make the GEM scalable, you have to either resize your value or adjust the Scaling range as a function of the instance size relative to the design size.  I'm thinking an On Open script could read the width and height and determine the new size, and from this define new X_Scale and Y_Scale properties.  ie  If I resize to 50% of original design and have a scale of 50 to 200, I could change the upper scale value to 350, and the animation would derive a measurement that half what it would have been.  For now I'm creating the GEM to the Size I want and will deal with making a dynamic solution till later.

    I could also build a series of XY Plots for say 100 x100, 200 x 200 and 400x400 and use the closest match, leaving them at their defined size.  But making it resizable would be nice.

    Anyway, hope this helps.  Your question and Mike's insight spurred me to finish my Pump curve solution.  Now to make it variable speed.

    Andre Dicaire

  • In reply to Andre Dicaire:

    Hi Andre, thank you very much, I'll try that approach
  • In reply to Luis G. Rodriguez:

    I found it very easy to modify the GEM to adjust for size by selecting all 20 lines and editing the four fields for Start and End point that need to match the size of the plot area. For now, I'm not worrying about making it resizable. Mainly that I will use this in a consistent fashion so one size will work for me, for now. Thanks for posting your request as it spurred me to complete my project based on Mike's input. I'd say this is exactly how this forum is intended to help us accomplish more together.

    Andre Dicaire

  • The Emerson Compressor Team has successfully created a dynamic compressor mapping (Pressure Ratio vs. Flow) on DeltaV Live. Please reach out to Emerson's local business partner for more information.  This is an out-of-the-box solution that can be purchased.  

  • In reply to Matt Stoner:

    Matt,

    The Emerson Compressor Team has this code in Operate; if you need it, please reach out to the Houston office.
  • In reply to Soap Chan:

    Hello everyone,

    This is a fascinating topic. In the future, DeltaV Live will natively support XY Plots (Scattered Plots), but while we get there, seeing the ingenuity displayed here is quite impressive. The solution presented by   seems to be what   is looking for.

    But I took some time to investigate a little more on the other suggestions presented as I noticed the animation complexity to properly position those segments by manipulating their X1, Y1, X2 and Y2 coordinates. In trying to simplify and optimize that implementation, and in collaboration with a DeltaV Live developer, we would like to offer an alternative. Of course, this solution doesn’t compare to the sophistication of the Emerson Compressor Team’s solution, and I highly encourage you to contact them or your local office for information about it.

    The code below was used in a GEM designed to read the Curve_X and the Curve_Y values from a Signal Characterizer function block. The size of that GEM is 400 pt x 400 pt and it is resizable. The building blocks for the GEM are the same as indicated by Martin and Andre, 20 lines representing the segments. None of those lines are animated in any way, and their properties are manipulated via scripting on an OnOpen Script for the GEM:

    //Create empty arrays to store X and Y curves
    let Xary: number[] = [];
    let Yary: number[] = [];
    
    //Read the arrays with data from the Characterization Function Block in parallel.
    const readXValuePromises: any[] = [];
    const readYValuePromises: any[] = [];
    for (let i= 1;i<=21;i++) {
        readXValuePromises.push(DLSYS.ReadAsync(Gem.FB + "/CURVE_X[" + i +"]"));
        readYValuePromises.push(DLSYS.ReadAsync(Gem.FB + "/CURVE_Y[" + i +"]"));
    }
     
    //Await all reads to complete.
    const xResults = await Promise.all(readXValuePromises);
    const yResults = await Promise.all(readYValuePromises);
     
    //Now that all the promises have resolved, we can read the return values and push to Xary and Yary.
    xResults.forEach(result => Xary.push(result.Value));
    yResults.forEach(result => Yary.push(result.Value));
    
    //Find the min and max values for each array to calculate the chart’s axis
    let Xmax: number = Xary[0];
    let Xmin: number = Xary[0];
    let Ymax: number = Yary[0];
    let Ymin: number = Yary[0];
    
    for (let i = 0; i <= 20; i++) {
        if (Xary[i] > Xmax) {
            Xmax = Xary[i];
        }
        if (Xary[i] < Xmin) {
            Xmin = Xary[i];
        }
        if (Yary[i] > Ymax) {
            Ymax = Yary[i];
        }
        if (Yary[i] < Ymin) {
            Ymin = Yary[i];
        }
    }
    
    //Add padding to the Y axis to present the curve within the chart
    let Yaxismax: number = Ymax + ((Ymax - Ymin) * 0.1); //Increase Y axis by 10% of range
    let Yaxismin: number = Ymin - ((Ymax - Ymin) * 0.1); //Decrease Y axis by 10% of range
    
    //Calculate a font size proportional to the new size of the GEM, original size is 400 Pt. Limit the smallest font size to 5
    let font_Size: number = Gem.txt_X0.Font.FontSize * Gem.Width.Pt / 400;
    if (font_Size < 5) {
        font_Size = 5;
    } 
    
    //Update Axis Scales with the actual labels
    let Yinc: number = (Yaxismax - Yaxismin) / 20; //even though there are 10 data points, dividing by 20 adjusts for the increment by 2 in the loop
    let Xinc: number = (Xmax - Xmin) / 20;
    for (let i=0; i<=20; i=i+2) {
        let lblX: string = (Xmin + (Xinc * i)).toFixed(Gem.Xdec); //Function number.toFixed(decimals) returns a string
        let lblY: string = (Yaxismin + (Yinc * i)).toFixed(Gem.Ydec); //Function number.toFixed(decimals) returns a string
        
        const txtX = (Gem as any)[`txt_X${i}`]; //this function help parametrizing the objects on the display, this syntax represents Gem.txt_X[i]
        const txtY = (Gem as any)[`txt_Y${i}`]; //this function help parametrizing the objects on the display, this syntax represents Gem.txt_Y[i]
        txtX.Label = lblX;
        txtX.Font.FontSize = font_Size;
        txtY.Label = lblY;
        txtY.Font.FontSize = font_Size;
    }
    
    //Move the Xmax and Ymax value to variables for Target location animation later.
    Gem.GemXmax = Xmax;
    Gem.GemYmax = Yaxismax;
    Gem.GemXmin = Xmin;
    Gem.GemYmin = Yaxismin;
    
    // Update the segments with the start point and end point from the arrays above
    for (let i=0; i<20; i++) {
        const x1 = new Types.Measurement({Pt: (Xary[i] - Xmin) * Gem.Width.Pt / (Xmax - Xmin)});
        const y1 = new Types.Measurement({Pt: (Yary[i] - Yaxismin) * -Gem.Height.Pt / (Yaxismax - Yaxismin)});
        const x2 = new Types.Measurement({Pt: (Xary[i + 1] - Xmin) * Gem.Width.Pt / (Xmax - Xmin)});
        const y2 = new Types.Measurement({Pt: (Yary[i + 1] - Yaxismin) * -Gem.Height.Pt / (Yaxismax - Yaxismin)});
        const line = (Gem as any)[`Segment${i + 1}`];
        line.X1 = x1;
        line.Y1 = y1;
        line.X2 = x2;
        line.Y2 = y2;
        
    }
    
    //Calculate the location of the target based on characterizer's X and Y
    let tx: number = ((Gem.Input1 - Xmin) * Gem.Width.Pt / (Xmax - Xmin)) - Gem.Target.Width.Pt/2;
    let ty: number = Gem.Height.Pt - ((Gem.Output1 - Ymin) * Gem.Height.Pt / (Yaxismax - Yaxismin)) - Gem.Target.Height.Pt/2;
    console.log(Gem.Name + " Target Location: " + tx + ", " + ty);
    
    Gem.Target.X = new Types.Measurement({Pt:tx});
    Gem.Target.Y = new Types.Measurement({Pt:ty});
    

    In addition, 6 variables were created for the GEM:

    TargetX, TargetY, GemXmax, GemXmin, GenYmax, GemYmin

    To dynamically update the target's position using the input and the output to the characterizer block, variables TargetX and TargetY were connected to the GEM's Input and Output parameter references (TargetX Variable Value -> Gem.Input1 and TargetY Variable Value -> Gem.Output1) and the following script was added to the OnWrite for each variable:

    TargetX OnWrite Script

    let tx: number = ((Gem.Input1 - Gem.GemXmin) * Gem.Width.Pt / (Gem.GemXmax - Gem.GemXmin)) - Gem.Target.Width.Pt/2;
    Gem.Target.X = new Types.Measurement({Pt:tx});
    
    return tx;
    

    TargetY OnWrite Script

    let ty: number = Gem.Height.Pt - ((Gem.Output1 - Gem.GemYmin) * Gem.Height.Pt / (Gem.GemYmax - Gem.GemYmin)) - Gem.Target.Height.Pt/2;
    Gem.Target.Y = new Types.Measurement({Pt:ty});
    
    return ty;

    This is a long post, trying to demonstrate the power of TypeScript and how to optimize reading and updating graphics using these functions. If you want to give this a try, contact me and I will email you the GEM. I wasn't able to attach it here.

    Best regards,

    Camilo Fadul 

    Camilo Fadul | DeltaV Solution Marketing Director

    https://www.linkedin.com/in/cfadul

  • In reply to Camilo Fadul:

    Wow.  I had started down this path of using a scripted solution but decided not to do it.  The main reason is that Live provides lots of ways to accomplish complex task with zero scripting, or at least focused scripting in a display field.   When migrating from Operate to Live, custom VBA scripting was not converted.  By using only built in standard functionality, the solution would be somewhat future proofed.

    But mainly, my typescript programming skills are not polished enough to write the above. :-)

    Martin Rooke 's approach seemed the cleanest to me and I was successful in plotting my desired XY curve with zero extra scripting.  The Emerson solution sparked me to use Float arrays for static curves Rather than Signal Characterizer, for module execution efficiency.  My GEM now works with either a float array or a SGCR block.

    As for resizing of a GEM instance, that is a manual task in graphics Studio but a fairly easy.  Resize the GEM and then Select all 20 lines to adjust the affected Start Point and End Point, which is really 4 fields common on all 20 lines.  Bottom line, if the built in Measurment Animation were enhanced to allow the Start Point and End Point variables to be dynamically linked to say the GEM Height or Width, that would rock.

    My current GEM with MIN and Max lines based on min/max speed of Pump, and the P-CURVE, currently at 50% of Speed.  The P-CURVE moves as Pump Speed moves.  The Actual HEAD and FLOW values will position a Reticle Pointer on the chart. The P-CURVE SGCR will provide expected Flow value and abnormal deviation can be reflected in the curve.

    No Scripting in LIve....

    But I will certainly be studying the script provided above.  Thank you, Camilo.

    Andre Dicaire