842 views
in GUI Development by

We're using graphing components that are based off the PulseOximeter example. So, stroked path and Bezier3.

Wondering if there is a way to render a dis-continuous line where between time t and t+n there is no line visible.

Or if there is a way to change the color of the line between a certain period of time.

Code we inherited from PulsiOxi is as follows:

// iterate through all visible data entries of the data storage 
while (( x < StrokePath.Bounds.w + PixelPerValue ) && ( inx >= 0 ))
{
  if ( inx < noOfData )
  {
    data = DataStorage.GetData( current - inx );
    slope = DataStorage.GetSlope( current - inx );

    data = (float)StrokePath.Bounds.h * ( data - (float)MinRange ) / (float)( MaxRange - MinRange );

    if ( firstVal == true )
    {
      Path.Begin( 0, x - (int32)LineWidth, data /*StrokePath.Bounds.h  / 2 */);  
      Path.AddLine( 0, x, data );
      firstVal = false;
    }
    else                                                  
    { 
      Path.AddBezier3( 0, x - dx, lastData + lastSlope * ratio, x - dx, data - slope * ratio, x, data, noOfEdges );
    }

    lastSlope = slope;
    lastData = data;
  }  



  x = x + PixelPerValue;
  inx = inx - 1;
}

Path.AddLine( 0, x + (int32)LineWidth, StrokePath.Bounds.h / 2 );

 

1 Answer

0 votes
by

Hello Mike,

Wondering if there is a way to render a dis-continuous line where between time t and t+n there is no line visible.

You can achieve such effects by splitting the single path is several sub-path. Let's assume, you want to split one curve in 3 parts. For this purpose you configure the Path object to be able to store 3 sub-paths. Then you can initialize each sub-path individually, e.g. with a Beziér curve. In this manner, each sub-path can contain separate curves not connected together. Please note the first parameter in the invocation of AddBezier(). It determines the sub-path to which the Beziér curve has to be added. See also Initialize a sub-path and Store data in a sub-path.

Or if there is a way to change the color of the line between a certain period of time.

In this application case you would need to store the individual curves in separate Path objects and connect each Path object to its individual Stroked Path view. In each view you can configure other color. In other words, if you want the curve to appear in 3 colors, then you need 3 Stroked Path views and 3 Path data objects. If the effect you want to achieve is just a color gradient, then it would be already possible to do this with a single Stroked Path view. In this case you can specify the colors for the left and right edges of the view and the displayed curve is interpolated linearly between the specified colors.

Does it help you further?

Best regards

Paul Banach

by
We plot data and at times it may be invalid. So I need to visually indicate these invalid data points or path segments. And the number of them may be indeterminate or large. So I'm unsure if, as you suggested, having separate path objects will work if I have a pre-determined number of those objects.

A similar problem is we have events that take place that I would like to visually indicate with a symbol along the path line.

Perhaps the solution to this is not in tinkering with the paths themselves. But to generate a colored scrim or object(s) that scrolls with the X time access...?
by

Hello Mike,

the Path data object and Stroked Path view are optimised to store coordinates and raster from them the corresponding vector graphic shapes. For the sake of efficiency you should load the Path data object only with data points you want to display. Embedded Wizard own Path data objects are not intended to manage indeterminate or large amount of data point.

I would try following approach:

1. Manage all data in separate data storage - outside of the Path data object.

2. From the data storage extract the interesting range of data points and load the values in Path data objects. Doing this you can configure the Path data objects with the actually expected number of sub-paths and the number of values in each sub-path.

3. If necessary, create Path data objects dynamically depending on the actual situation. Similarly, you can create the Stroked Path views dynamically.

4. If necessary (when data changes), you can recreate or re-initialize the Path data objects as well as the StrokedPath views.

A similar problem is we have events that take place that I would like to visually indicate with a symbol along the path line.

5. I'm not sure whether I understand what you mean. Displaying some cursor, decorations, etc. together with vector graphic should not be an issue. You will need to know the corresponding coordinates and arrange the 'object' at this position.

Perhaps the solution to this is not in tinkering with the paths themselves. But to generate a colored scrim or object(s) that scrolls with the X time access...?

6. The Path object implements methods to scroll its contents. This means, when new data arrive, the oldest entries are removed before the new entries are added. Maybe this is what you mean with "scroll with the X time ...". See the section Evaluate and modify the coordinates stored in the Path Data object. Especially note the method ShiftNodes().

Does it help you further?

Best regards

Paul Banach

by

Got it!

Wasn't too bad. Just created two further paths for the two types of invalid data states we can have. Then just did:  if(inInvalidState) PathInvalid.AddLine()

Looks like this:

by

Oh, I take that back. Works until there is another invalid set of data on the same path. Then it draws a line segment from the end of the first red line to the start of the next red line:

I must need to create a new subPath every time there's an invalid data path segment....?

From my UpdateViewState()


//  ZEROS PATH /////////////////////////////////////////////////////////////////////////
noOfEdges = PixelPerValue / 4 + 1;
current = DataStorage.Current;
noOfData = DataStorage.NoOfData;

// search for index and horizontal position of the first coordinate that is left of the views origin 
inx = (ScrollOffset + Bounds.w) / PixelPerValue + 1;   
x = ( ScrollOffset + Bounds.w ) % PixelPerValue - PixelPerValue; 
                   
// clear the path 
Path_Zeros.InitSubPath( 0, noOfEdges * inx + 4 );

lastData = 0.0;
lastSlope = 0.0;
dx = PixelPerValue / 2;
firstVal = true;
ratio = 0.25;
  
// iterate through all visible data entries of the data storage 
while (( x < StrokePath_Zeros.Bounds.w + PixelPerValue ) && ( inx >= 0 ))
{
  if ( inx < noOfData )
  {
    data = DataStorage.GetData( current - inx );
    slope = DataStorage.GetSlope( current - inx );

    if(Application::Device.STATUS_Machine_State_Storage.GetData( current - inx ) == 30.0)
    {
      data = (float)StrokePath_Zeros.Bounds.h * ( data - (float)MinRange ) / (float)( MaxRange - MinRange );

      if ( firstVal == true )
      {
        Path_Zeros.Begin( 0, x - (int32)StrokePath_Zeros.Width, data /*StrokePath.Bounds.h  / 2 */);  
        Path_Zeros.AddLine( 0, x, data );
        firstVal = false;
      }
      else                                                  
      { 
        Path_Zeros.AddBezier3( 0, x - dx, lastData + lastSlope * ratio, x - dx, data - slope * ratio, x, data, noOfEdges );
      }

      lastSlope = slope;
      lastData = data;
    }
  }  

  x = x + PixelPerValue;
  inx = inx - 1;
}

if(Application::Device.STATUS_Machine_State_Storage.GetData( current - inx ) == 30.0)
  Path_Zeros.AddLine( 0, x + (int32)StrokePath_Zeros.Width, StrokePath.Bounds.h / 2 );
// END ZEROS PATH /////////////////////////////////////////////////////////////////////////
    

 

 

by

Tried adding a SubPaths counter "SubPaths_Zeros". Then tried .Close() ing the path. But that doesnt seem to fix the issue...

//  ZEROS PATH /////////////////////////////////////////////////////////////////////////
noOfEdges = PixelPerValue / 4 + 1;
current = DataStorage.Current;
noOfData = DataStorage.NoOfData;

// search for index and horizontal position of the first coordinate that is left of the views origin 
inx = (ScrollOffset + Bounds.w) / PixelPerValue + 1;   
x = ( ScrollOffset + Bounds.w ) % PixelPerValue - PixelPerValue; 
  
  
Path_Zeros.SetMaxNoOfSubPaths( 4 );  
                   
// clear the path 
Path_Zeros.InitSubPath( SubPaths_Zero, noOfEdges * inx + 4 );

lastData = 0.0;
lastSlope = 0.0;
dx = PixelPerValue / 2;
firstVal = true;
ratio = 0.25;
  
// iterate through all visible data entries of the data storage 
while (( x < StrokePath_Zeros.Bounds.w + PixelPerValue ) && ( inx >= 0 ))
{
  if ( inx < noOfData )
  {
    data = DataStorage.GetData( current - inx );
    slope = DataStorage.GetSlope( current - inx );

    if(Application::Device.STATUS_Machine_State_Storage.GetData( current - inx ) == 30.0)
    {
      data = (float)StrokePath_Zeros.Bounds.h * ( data - (float)MinRange ) / (float)( MaxRange - MinRange );

      if ( firstVal == true )
      {
        Path_Zeros.Begin( SubPaths_Zero, x - (int32)StrokePath_Zeros.Width, data /*StrokePath.Bounds.h  / 2 */);  
        Path_Zeros.AddLine( SubPaths_Zero, x, data );
        firstVal = false;
      }
      else                                                  
      { 
        Path_Zeros.AddBezier3( SubPaths_Zero, x - dx, lastData + lastSlope * ratio, x - dx, data - slope * ratio, x, data, noOfEdges );
      }

      lastSlope = slope;
      lastData = data;
    }
  }  

  x = x + PixelPerValue;
  inx = inx - 1;
}

if(Application::Device.STATUS_Machine_State_Storage.GetData( current - inx ) == 30.0)
{
  Path_Zeros.AddLine( SubPaths_Zero, x + (int32)StrokePath_Zeros.Width, StrokePath.Bounds.h / 2 );
  Path_Zeros.Close(SubPaths_Zero);
  SubPaths_Zero = SubPaths_Zero + 1;
}
// END ZEROS PATH /////////////////////////////////////////////////////////////////////////
    

 

by

Hello Mike,

You are on the right track.

Works until there is another invalid set of data on the same path. Then it draws a line segment from the end of the first red line to the start of the next red line [...]

I must need to create a new subPath every time there's an invalid data path segment....?

Sure, if you want multiple, separate path segments then you will need to store them as individual sub-paths.

Tried adding a SubPaths counter "SubPaths_Zeros". Then tried .Close() ing the path. But that doesnt seem to fix the issue...

I'm not sure what you tried to achieve ... The Close() operation may be is too much. It causes the end position of the sub-path to be connected with the begin position of the sub-path. The resulting shape is then 'closed'.

Did you find the solution?

Best regards

Paul Banach

by
I see, I'll avoid doing a Close() on a SubPath then.

I'm still confused as to how I should be iterating through SubpPaths. I assume I have to keep an index (what I meant by a SubPath counter) and call a new Path_Zeros.InitSubPath() with a new path index...? I'm confused as to when to InitSubPath(). Should it be on firstVal?  Or do I only do firstVal's Begin() and AddLine() once for the entire plot, and instead InitSubPath() based on the logic of the data (if its invalid)...?
by

Hello Mike,

I'm still confused as to how I should be iterating through SubpPaths. I assume I have to keep an index (what I meant by a SubPath counter) and call a new Path_Zeros.InitSubPath() with a new path index...? I'm confused as to when to InitSubPath(). Should it be on firstVal?  Or do I only do firstVal's Begin() and AddLine() once for the entire plot, and instead InitSubPath() based on the logic of the data (if its invalid)...?

You call InitSubPath() before you load data to the sub-path for the first time. Later, you can omit InitSubPath() invocation if the capacity of the sub-path does not change. It means, with InitSubPath() you instruct Embedded Wizard to reserve memory for given number of path edges (coordinate pairs). Consequently, if the number of edges within a sub-path grows, you will need to re-initialize the sub-path with bigger capacity. If the number remains unchanged, it is sufficient to invoke InitSubPath() at the first initialization time.

Finally, the Path objects can be seen as an array of sub-path objects. A sub-path object in turn can be seen as an array of coordinate pairs (edges). By using the methods SetMaxNoOfSubPaths() and InitSubPath() you determine the capacity of those arrays.

Does it help you?

Best regards

Paul Banach

by
That helped. Got things working. However, I have to set SetMaxNoOfSubPaths() with a pre-determined upper limit. I don't suppose there is a way to dynamically increment this as needed ... ? As every time it is called everything gets re-initialized.
by

Hello Mike,

yes, SetMaxNoOfSubPaths() controls the memory allocation for the entire path. Therefore, when resizing the path, already existing data is discarded. This restriction results from optimization requirements. There is actually no way to add new sub-paths to existing path without loosing existing data.

I will take this as feature request in our issue tracking system.

Best regatrds

Paul Banach

Ask Embedded Wizard

Welcome to the question and answer site for Embedded Wizard users and UI developers.

Ask your question and receive answers from the Embedded Wizard support team or from other members of the community!

Embedded Wizard Website | Privacy Policy | Imprint

...