# Change curve's color based on its position relative to a second curve

• 19th October 2021, 19:11
SeanM
Change curve's color based on its position relative to a second curve
Is there any easy way to change the color of one curve based on its values relative to a second curve?

Say I have 2 curves that each contain their own arbitrary data:
1. Signal curve - in general painted with a black pen (more on that in a second...)
2. Reference curve - painted with a blue pen

The pen for Signal should be red whenever the ySignal >= yReference, and black whenever ySignal < yReference. If it helps, the two curves are identically sampled in x.

I don't see anything in the API that easily allows this, so unless I'm missing something, my approach is to:
1. Plot all of Signal in black first
2. Plot all of Reference in blue next
3. Manually parse through the y-values of both Signal and Reference and create multiple curves for the regions where the signal data is >= the reference data, and then plot those resulting curves with a red pen and a higher z-order than the

The other possible option I see is based off from this hint: https://sourceforge.net/p/qwt/mailman/message/20349266, where I make my own special curve class that takes both the Signal and Reference and overrides drawCurve to change the pen color based on their relative values
• 20th October 2021, 16:38
d_stranz
Re: Change curve's color based on its position relative to a second curve
Quote:

The other possible option I see is based off from this hint: https://sourceforge.net/p/qwt/mailman/message/20349266, where I make my own special curve class that takes both the Signal and Reference and overrides drawCurve to change the pen color based on their relative values
This seems to me to be the most straightforward way to do it, except that I would not create a new curve class that internalizes the calculation of the colors based on two inputs.

I would create a curve class that stores x, y, and color on a point-by-point basis, and use external logic to set the point colors. This allows you to reuse that curve class any place you might need a multi-colored curve, not just for this one special case.

If you use a color table (a set of QColors with an index) and store the color index for each point instead of an actual QColor, then to instantly change colors all you need to do is store a different color in the table and update the plot. The point's color index doesn't change, but the color it points to does. (For example, to turn off the signal vs. reference coloring, simply change the color table so that all colors are the same).

You can also optimize the drawing by keeping track of the current color (or color index) and changing to a new pen only when the color changes.
• 20th October 2021, 19:08
SeanM
Re: Change curve's color based on its position relative to a second curve
Thanks for the response!

I ended up going with my initial idea:
Quote:

I make my own special curve class that takes both the Signal and Reference and overrides drawCurve to change the pen color based on their relative values
for the following reason - where the curves cross is not guaranteed to occur on an actual data point, so whenever I encounter an interval where the curves swap positions, I need to paint a straight line that changes colors mid way through the interval. And I probably should have been more clear in my request - I care more about the color of the lines between data points than I care about the points themselves.

For example say I have this simple example where I only have 2 data points for each curve:
• Signal: (0,0) -> (1,1)
• Reference: (1,1) -> (0,0)

The point where those two curves cross is (0.5, 0.5) and there's no data sample at x = 0.5. So by actually measuring the two signals' data within drawCurves(), I can detect when a transition happens between two of my sample locations, compute the exact point that they cross, and then draw two lines using two different pen colors for this interval, using the first pen color from the previous sample point to the crossing point, and the other pen color from the crossing point to my current sample point.

So in the example above, I want to paint the signal curve with a black pen from (0, 0) -> (0.5, 0.5) since the signal is below the reference during that region, and then paint the signal curve with a red pen from (0.5, 0.5) -> (1, 1) since the signal is above the reference for this region

I've attached a screenshot and a slightly more complicated example that does what I need it to do in case it helps anyone else. In the screenshot, you'll see two plots:
• the first plot is just my two original data sets (the signal in black, and the reference in blue)
• the second plot shows my desired result the signal curve is black whenever it's below the reference curve, red whenever its above the reference. I've also added a brush below the reference curve to highlight the "safe" region a little better

Attachment 13726
• 20th October 2021, 19:22
d_stranz
Re: Change curve's color based on its position relative to a second curve
Quote:

for the following reason - where the curves cross is not guaranteed to occur on an actual data point, so whenever I encounter an interval where the curves swap positions, I need to paint a straight line that changes colors mid way through the interval.
And you could have accomplished that with my suggestion simply by inserting a new point in the data you pass to the curve. Now what you have is a very specialized, non-reusable class that mixes up program logic with visual appearance. If specifications change, now you have to rewrite your curve class instead of simply changing the logic that creates the data used by the curve.

But if it works for you and you are happy with it, that's what counts.
• 21st October 2021, 18:24
SeanM
Re: Change curve's color based on its position relative to a second curve
Thanks for the continued discussion, I appreciate it.

First off, I'll admit that I'm not seeing how your approach vs. my approach is any more or less reusable, but I need to relay the updated the requirements here first so you have more of the full picture.

1. The two curves are now no longer required to have the same x sampling. In fact, there's no requirement that any point in the reference data shares the same x value as a point in the signal data (although it's pretty likely that they will both contain a data point at x=0, but beyond that all bets are off)
2. I can't add or remove samples from either curve on the business logic side, at least not without keeping a pristine copy of the original data.

Not sure if any of those have any effect on your approach, but at least you know them now. As far as your concept of changing the color table to change colors, my inherited curve has a second pen parameter added to it, so if I want to visually enable/disable the comparison, all I have to do is change the second pen to have the same color as the curve pen and replot, so I don't see where the color table concept saves me anything? Other than maybe your idea would be more reusable in cases where I need/want more than 2 colors? Which isn't a requirement that I have currently.

Also, to make sure I understand your approach, I assume you're using the color index at a given point to indicate the color the line should be from that point to the next point? So for example if I had 3 points, and assigned them the color indices for black, red, and green respectively, I'd get a black line from point 1 to 2, a red line from point 2 to 3, and green would be unused? In my usage I really only care about intervals, not the points themselves (i.e. I don't have symbols enabled) so I'm trying to understand what I gain by storing a color at every data point.

With your approach, how would you handle all the existing setSamples() overloads, as there is nothing that prevents a user from calling those, but then you've got no color indices to go along with your data points? Would you delete those functions and force the caller to use something like setSamples(QVector<QPointF> samples, QVector<int> colorIndices)?

What I like about my approach so far is that it's just a plain QwtPlotCurve with essentially just 2 added functions setReferenceData(const QVector<QPointF>&) and setViolationPen(const QPen&). Then in my overridden drawCurves(), I just make the pen color decision for each interval, including the cases where a crossing happens in between actual sample points, but I don't have to store those extra crossing points anywhere after I've used them to draw an interval.

Calling setSamples() will automatically replot the new signal data against the existing reference data. Calling setReferenceData() will automatically replot the existing signal data against the new reference. And calling setViolationPen() will replot the existing signal vs. the existing reference with a new pen.

• 22nd October 2021, 16:42
d_stranz
Re: Change curve's color based on its position relative to a second curve
Quote:

Thanks for the continued discussion, I appreciate it.
So do I. I don't like religious wars over approaches to coding or coding style, but discussions of the pros and cons of different ways to accomplish the same task are always useful.

I guess my method of thinking is shaped by being a library builder for much of my coding career. My company has millions of lines of code in libraries that we use for all of our applications, and which we have licensed to other companies in a similar business for their use. Very early on, I helped lead efforts to develop protocols for data interchange in my industry and wrote code for it (in 1992) that is still in use today. Reusability and generality is in my genetics at this point.

So, my approach when writing anything new that I view as a tool and not an application-specific piece of code is to consider the design and implementation so that it makes that tool as reusable as possible in as many different types of application as possible. Widgets for data plotting are a good example. I have developed a number of these over the years, first in MFC and then as I moved into Qt first in Qwt, then in the Qt Graphics / View framework (using some parts of Qwt in the QGraphicsScene), and now using QCustomPlot.

My goal with this latest implementation is to have a single base widget that I can use wherever I need a plot - curve, stick, scatter, bar, whatever - where each type of plot can be fed from the same data source (a QAbstractItemModel), and where the model itself does not need to keep a duplicate copy of the data. Some of my curve plots can have millions of points.

In my field, stick plots are very common. This is a plot where each data point is represented by a vertical stick starting at y = 0 and extending to the y value for that point. Each stick can have a different color, can be decorated with a symbol (like a circle, star, triangle, etc.) and have a multi-line label at the top end. So my goal is to design a curve class that can do all of those things, but where all of them (except the stick itself) are optional. I suppose I could even add a visibility flag for the sticks.

(To answer your question about the color table: if the user doesn't specify one or specifies an index outside the table range, the curve defaults to using a pre-defined single color, blue or black probably, for all points, and provides a method where you can set the default color if the pre-defined default isn't your thing).

QCustomPlot is very well-designed so that it lives up to its name - you can design something that puts nearly any type of x-y data into a plot by deriving from a low-level "plottable" class with a very basic interface that the QCustomPlot calls to actually display the plottable. Compare this to QtCharts, where the plots and their interfaces are chiseled into granite and can't be changed at all.

It is then up to the business logic to feed the plot the data and configuration details it needs to determine what to plot. So how would I handle your requirements?

Quote:

1. The two curves are now no longer required to have the same x sampling. In fact, there's no requirement that any point in the reference data shares the same x value as a point in the signal data (although it's pretty likely that they will both contain a data point at x=0, but beyond that all bets are off)
2. I can't add or remove samples from either curve on the business logic side, at least not without keeping a pristine copy of the original data.
In the business logic, I would use the reference and signal data to construct the data for the curve that will be plotted using your rules for how color is determined. If I need to add extra points to the curve to accommodate color changes, fine, only the plot will know. The original data is unchanged, it is only the data that will be plotted that shows the result of the calculation.

This is what I have referred to in other posts as distinguishing between the map and the terrain. The terrain is a constant - it's the Earth and it is what it is. How you draw the map of that terrain of course depends on the terrain, but you can draw the map at any scale, add false color, contour lines, names for roads and buildings, none of which you see when you look at the dirt in front of you.

So in your case, instead of the curve class doing the calculation of color, I would write a separate, business-specific class that takes the signal and reference data structures and produces a plot-compatible data array with the color or color index of each point specified that will be fed to the generic curve class that knows what to do with such data.

Adding the ability to specify a color on a point-by-point basis adds to the reusability of the curve class. Sure, a two-pen solution might meet the current need, but maybe the next project will require a curve that can be color-coded to indicate a change in one variable as another changes - think of a plot of the elevation changes along a stream bed that codes changes in water velocity with the color of each point or section of the curve. Two pens aren't enough, and maybe a mapping function is needed that converts a y value into a color from a QGradient will work better.

Yes, all of this is more work, and will likely require tweaking, but with enough thought put into the design and implementation of the component side, I can pull that component out of the box, plop it into a window, and just use it. My application is responsible for feeding it data as required and I can use the same component across many applications.