first of all let me introduce you to the problem.
Let's say that we have one sensor that is measuring something, tank oil/water level etc.
Sensor is calibrated for taking measurements from 0-3.5 [m].
According to that, we recieve feedback in percentage from 0 to 100 %.
Now, let's say that instead 0 [m] being my zero, we want that 1,2 [m] is 0%, and respectively 2,4 [m] to be 100%.
First thing I did is to calculate how much are values of 1,2 [m] and 2,4 [m] in original scale.
1,2[m] -> 34.2% and 2,4[m] -> 68,4%
So value of 34.2% on the original scale corresponds to 0% on new scale.
Value of 68.4% on the original scale corresponds to 100% on new scale.
Now if I want to rescale the original scale to be between these two values, how would you do it?
I suspect next step is substituing new "zero" (34,2 %) from measured value, and then I would need to map first one to other one, or other one to first one...something like that.
Thanks in advance!! Sorry if it is not clear, I tried to be clear as much as possible with problem.
P.S. Solution is appreciated, but I am looking forward to hearing explanation, this would be better.
First, let's give names to these values.
The original height range is:
Hmin_0 = 0[m], Hmax_0 = 3.5[m]
The new height range is:
Hmin_1 = 1.2[m], Hmax_1 = 2.4[m]
Let's call x the sensor's output. We have:
x(h=0[m])=0 %
x(h=3.5[m])=100 %
Let's call x0 and x1 the sensor's reading for h = Hmin_1 and h = Hmax_1 respectively:
x0=Hmin_1/(Hmax_0-Hmin_0)*100 [%]
x1=Hmax_1/(Hmax_0-Hmin_0)*100 [%]
We are looking for a function f(x)=a*x+b such that:
f(x0) = 0
f(x1) = 100
You can check that the function:
f(x)=(x-x0)/(x1-x0)*100 (equ.1)
is the one you are looking for. Indeed, f(x0)=0 and f(x1)=100 as required.
Or, if you're not convinced, you can solve the system of 2 linear equations:
a * x0 + b = 0 (equ. 2)
a * x1 + b = 100 (equ. 3)
which will give:
a=100/(x1-x0)
b=-x0*100/(x1-x0).
Regards
Sensor is calibrated for taking measurements from 0-3.5 [m].
Let max = 3.5
we receive feedback in percentage from 0 to 100 %.
Let's call it feedback
we want that 1.2 [m] is 0%, and respectively 2.4 [m] to be 100%.
Let low = 1.2 and high = 2.4
Then the mapping can be derived as follows:
First convert feedback to the original absolute measure, i.e. max * feedback/100
Then subtract low so to make sure that an absolute value of low maps to 0.
Then apply a coeficient so that the absolute distance of high - low maps to 100. That coefficient is thus 100 / (high - low).
That's it. The final formula is thus:
(max * feedback / 100 - low) * 100 / (high - low)
Or equivalent:
(max * feedback - low * 100) / (high - low)
Here is a JavaScript snippet that makes this conversion for feedback in [0, 10, 20, ..., 100]:
let max = 3.5;
let low = 1.2;
let high = 2.4;
for (let feedback = 0; feedback <= 100; feedback += 10) {
let output = (max * feedback - low * 100) / (high - low);
console.log(feedback, "=>", output);
}
Related
I have been attempting to detect peaks in sinusoidal time-series data in real time, however I've had no success thus far. I cannot seem to find a real-time algorithm that works to detect peaks in sinusoidal signals with a reasonable level of accuracy. I either get no peaks detected, or I get a zillion points along the sine wave being detected as peaks.
What is a good real-time algorithm for input signals that resemble a sine wave, and may contain some random noise?
As a simple test case, consider a stationary, sine wave that is always the same frequency and amplitude. (The exact frequency and amplitude don't matter; I have arbitrarily chosen a frequency of 60 Hz, an amplitude of +/− 1 unit, at a sampling rate of 8 KS/s.) The following MATLAB code will generate such a sinusoidal signal:
dt = 1/8000;
t = (0:dt:(1-dt)/4)';
x = sin(2*pi*60*t);
Using the algorithm developed and published by Jean-Paul, I either get no peaks detected (left) or a zillion "peaks" detected (right):
I've tried just about every combination of values for these 3 parameters that I could think of, following the "rules of thumb" that Jean-Paul gives, but I have so far been unable to get my expected result.
I found an alternative algorithm, developed and published by Eli Billauer, that does give me the results that I want—e.g.:
Even though Eli Billauer's algorithm is much simpler and does tend to reliably produce the results that I want, it is not suitable for real-time applications.
As another example of a signal that I'd like to apply such an algorithm to, consider the test case given by Eli Billauer for his own algorithm:
t = 0:0.001:10;
x = 0.3*sin(t) + sin(1.3*t) + 0.9*sin(4.2*t) + 0.02*randn(1, 10001);
This is a more unusual (less uniform/regular) signal, with a varying frequency and amplitude, but still generally sinusoidal. The peaks are plainly obvious to the eye when plotted, but hard to identify with an algorithm.
What is a good real-time algorithm to correctly identify the peaks in a sinusoidal input signal? I am not really an expert when it comes to signal processing, so it would be helpful to get some rules of thumb that consider sinusoidal inputs. Or, perhaps I need to modify e.g. Jean-Paul's algorithm itself in order to work properly on sinusoidal signals. If that's the case, what modifications would be required, and how would I go about making these?
Case 1: sinusoid without noise
If your sinusoid does not contain any noise, you can use a very classic signal processing technique: taking the first derivative and detecting when it is equal to zero.
For example:
function signal = derivesignal( d )
% Identify signal
signal = zeros(size(d));
for i=2:length(d)
if d(i-1) > 0 && d(i) <= 0
signal(i) = +1; % peak detected
elseif d(i-1) < 0 && d(i) >= 0
signal(i) = -1; % trough detected
end
end
end
Using your example data:
% Generate data
dt = 1/8000;
t = (0:dt:(1-dt)/4)';
y = sin(2*pi*60*t);
% Add some trends
y(1:1000) = y(1:1000) + 0.001*(1:1000)';
y(1001:2000) = y(1001:2000) - 0.002*(1:1000)';
% Approximate first derivative (delta y / delta x)
d = [0; diff(y)];
% Identify signal
signal = derivesignal(d);
% Plot result
figure(1); clf; set(gcf,'Position',[0 0 677 600])
subplot(4,1,1); hold on;
title('Data');
plot(t,y);
subplot(4,1,2); hold on;
title('First derivative');
area(d);
ylim([-0.05, 0.05]);
subplot(4,1,3); hold on;
title('Signal (-1 for trough, +1 for peak)');
plot(t,signal); ylim([-1.5 1.5]);
subplot(4,1,4); hold on;
title('Signals marked on data');
markers = abs(signal) > 0;
plot(t,y); scatter(t(markers),y(markers),30,'or','MarkerFaceColor','red');
This yields:
This method will work extremely well for any type of sinusoid, with the only requirement that the input signal contains no noise.
Case 2: sinusoid with noise
As soon as your input signal contains noise, the derivative method will fail. For example:
% Generate data
dt = 1/8000;
t = (0:dt:(1-dt)/4)';
y = sin(2*pi*60*t);
% Add some trends
y(1:1000) = y(1:1000) + 0.001*(1:1000)';
y(1001:2000) = y(1001:2000) - 0.002*(1:1000)';
% Add some noise
y = y + 0.2.*randn(2000,1);
Will now generate this result because first differences amplify noise:
Now there are many ways to deal with noise, and the most standard way is to apply a moving average filter. One disadvantage of moving averages is that they are slow to adapt to new information, such that signals may be identified after they have occurred (moving averages have a lag).
Another very typical approach is to use Fourier Analysis to identify all the frequencies in your input data, disregard all low-amplitude and high-frequency sinusoids, and use the remaining sinusoid as a filter. The remaining sinusoid will be (largely) cleansed from the noise and you can then use first-differencing again to determine the peaks and troughs (or for a single sine wave you know the peaks and troughs happen at 1/4 and 3/4 pi of the phase). I suggest you pick up any signal processing theory book to learn more about this technique. Matlab also has some educational material about this.
If you want to use this algorithm in hardware, I would suggest you also take a look at WFLC (Weighted Fourier Linear Combiner) with e.g. 1 oscillator or PLL (Phase-Locked Loop) that can estimate the phase of a noisy wave without doing a full Fast Fourier Transform. You can find a Matlab algorithm for a phase-locked loop on Wikipedia.
I will suggest a slightly more sophisticated approach here that will identify the peaks and troughs in real-time: fitting a sine wave function to your data using moving least squares minimization with initial estimates from Fourier analysis.
Here is my function to do that:
function [result, peaks, troughs] = fitsine(y, t, eps)
% Fast fourier-transform
f = fft(y);
l = length(y);
p2 = abs(f/l);
p1 = p2(1:ceil(l/2+1));
p1(2:end-1) = 2*p1(2:end-1);
freq = (1/mean(diff(t)))*(0:ceil(l/2))/l;
% Find maximum amplitude and frequency
maxPeak = p1 == max(p1(2:end)); % disregard 0 frequency!
maxAmplitude = p1(maxPeak); % find maximum amplitude
maxFrequency = freq(maxPeak); % find maximum frequency
% Initialize guesses
p = [];
p(1) = mean(y); % vertical shift
p(2) = maxAmplitude; % amplitude estimate
p(3) = maxFrequency; % phase estimate
p(4) = 0; % phase shift (no guess)
p(5) = 0; % trend (no guess)
% Create model
f = #(p) p(1) + p(2)*sin( p(3)*2*pi*t+p(4) ) + p(5)*t;
ferror = #(p) sum((f(p) - y).^2);
% Nonlinear least squares
% If you have the Optimization toolbox, use [lsqcurvefit] instead!
options = optimset('MaxFunEvals',50000,'MaxIter',50000,'TolFun',1e-25);
[param,fval,exitflag,output] = fminsearch(ferror,p,options);
% Calculate result
result = f(param);
% Find peaks
peaks = abs(sin(param(3)*2*pi*t+param(4)) - 1) < eps;
% Find troughs
troughs = abs(sin(param(3)*2*pi*t+param(4)) + 1) < eps;
end
As you can see, I first perform a Fourier transform to find initial estimates of the amplitude and frequency of the data. I then fit a sinusoid to the data using the model a + b sin(ct + d) + et. The fitted values represent a sine wave of which I know that +1 and -1 are the peaks and troughs, respectively. I can therefore identify these values as the signals.
This works very well for sinusoids with (slowly changing) trends and general (white) noise:
% Generate data
dt = 1/8000;
t = (0:dt:(1-dt)/4)';
y = sin(2*pi*60*t);
% Add some trends
y(1:1000) = y(1:1000) + 0.001*(1:1000)';
y(1001:2000) = y(1001:2000) - 0.002*(1:1000)';
% Add some noise
y = y + 0.2.*randn(2000,1);
% Loop through data (moving window) and fit sine wave
window = 250; % How many data points to consider
interval = 10; % How often to estimate
result = nan(size(y));
signal = zeros(size(y));
for i = window+1:interval:length(y)
data = y(i-window:i); % Get data window
period = t(i-window:i); % Get time window
[output, peaks, troughs] = fitsine(data,period,0.01);
result(i-interval:i) = output(end-interval:end);
signal(i-interval:i) = peaks(end-interval:end) - troughs(end-interval:end);
end
% Plot result
figure(1); clf; set(gcf,'Position',[0 0 677 600])
subplot(4,1,1); hold on;
title('Data');
plot(t,y); xlim([0 max(t)]); ylim([-4 4]);
subplot(4,1,2); hold on;
title('Model fit');
plot(t,result,'-k'); xlim([0 max(t)]); ylim([-4 4]);
subplot(4,1,3); hold on;
title('Signal (-1 for trough, +1 for peak)');
plot(t,signal,'r','LineWidth',2); ylim([-1.5 1.5]);
subplot(4,1,4); hold on;
title('Signals marked on data');
markers = abs(signal) > 0;
plot(t,y,'-','Color',[0.1 0.1 0.1]);
scatter(t(markers),result(markers),30,'or','MarkerFaceColor','red');
xlim([0 max(t)]); ylim([-4 4]);
Main advantages of this approach are:
You have an actual model of your data, so you can predict signals in the future before they happen! (e.g. fix the model and calculate the result by inputting future time periods)
You don't need to estimate the model every period (see parameter interval in the code)
The disadvantage is that you need to select a lookback window, but you will have this problem with any method that you use for real-time detection.
Video demonstration
Data is the input data, Model fit is the fitted sine wave to the data (see code), Signal indicates the peaks and troughs and Signals marked on data gives an impression of how accurate the algorithm is. Note: watch the model fit adjust itself to the trend in the middle of the graph!
That should get you started. There are also a lot of excellent books on signal detection theory (just google that term), which will go much further into these types of techniques. Good luck!
Consider using findpeaks, it is fast, which may be important for realtime. You should filter high-frequency noise to improve accuracy. here I smooth the data with a moving window.
t = 0:0.001:10;
x = 0.3*sin(t) + sin(1.3*t) + 0.9*sin(4.2*t) + 0.02*randn(1, 10001);
[~,iPeak0] = findpeaks(movmean(x,100),'MinPeakProminence',0.5);
You can time the process (0.0015sec)
f0 = #() findpeaks(movmean(x,100),'MinPeakProminence',0.5)
disp(timeit(f0,2))
To compare, processing the slope is only a bit faster (0.00013sec), but findpeaks have many useful options, such as minimum interval between peaks etc.
iPeaks1 = derivePeaks(x);
f1 = #() derivePeaks(x)
disp(timeit(f1,1))
Where derivePeaks is:
function iPeak1 = derivePeaks(x)
xSmooth = movmean(x,100);
goingUp = find(diff(movmean(xSmooth,100)) > 0);
iPeak1 = unique(goingUp([1,find(diff(goingUp) > 100),end]));
iPeak1(iPeak1 == 1 | iPeak1 == length(iPeak1)) = [];
end
Sorry about the vague title. I'm not sure how to concisely word what I'm about to ask. This is more of a math/algorithms question than a programming question.
In an app that I'm developing, we have a value that can fluctuate anywhere between 0 and a predetermined maximum (in testing it's usually hovered around 100, so let's just say 100). This range of data is continuous, meaning there are an infinite number of possible values- as long as it's between 0 and 100, it's possible.
Right now, any value returned from this is mapped to a different range that is also continuous- from 1000 to 200. So if the value from the first set is 100, I map it to 200, and if the value from the first set is 0, it gets mapped to 1000. And of course everything in between. This is what the code looks like:
-(float)mapToRange:(float)val withMax:(float)maxVal{
// Establish range constants.
const int upperBound = 1000;
const int lowerBound = 200;
const int bandwidth = upperBound - lowerBound;
// Make sure we don't go above the calibrated maximum.
if(val > maxVal)
val = maxVal;
// Scale the original value to our new boundaries.
float scaled = val/maxVal;
float ret = upperBound - scaled*bandwidth;
return ret;
}
Now, what I want to do is make it so that the higher original values (closer to 100) increase in larger increments than the lower original values (closer to 0). Meaning if I slowly start decreasing from 100 to 0 at a steady rate, the new values starting at 200 move quickly toward 1000 at first but go in smaller increments the closer they get to 1000. What would be the best way to go about doing this?
Your value scaled is basically the 0-100 value represented in the range 0-1 so it's good to work with. Try raising this to an integer power, and the result will increase faster near 1 and slower near 0. The higher the power, the larger the effect. So something like:
float scaled = val/maxVal;
float bent = scaled*scaled*scaled*scaled; // or however you want to write x^4
float ret = upperBound - bent*bandwidth;
Here's a sketch of the idea:
That is, the span A to B, maps to the smaller span a to b, while the span C to D maps to the larger span c to d. The larger the power of the polynomial, the more the curve will be bent into the lower right corner.
The advantage of using the 0 to 1 range is that the endpoints stay fixed since x^n=x when x is 0 or 1, but this, of course, isn't necessary as anything could be compensated for by the appropriate shifting and scaling.
Note also that this map isn't symmetric (though my drawing sort of looks that way), though course a symmetric curve could be chosen. If you want to curve to bend the other way, choose a power less than 1.
I'm trying to add a fade effect to my form by manually changing the opacity of the form but I'm having some trouble calculating the correct value to increment by the Opacity value of the form.
I know I could use the AnimateWindow API but it's showing some unexpected behavior and I'd rather do it manually anyways as to avoid any p/invoke so I could use it in Mono later on.
My application supports speeds ranging from 1 to 10. And I've manually calculated that for a speed of 1 (slowest) I should increment the opacity by 0.005 and for a speed of 10 (fastest) I should increment by 0.1. As for the speeds between 1 and 10, I used the following expression to calculate the correct value:
double opSpeed = (((0.1 - 0.005) * (10 - X)) / (1 - 10)) + 0.1; // X = [1, 10]
I though this could give me a linear value and that that would be OK. However, for X equal 4 and above, it's already too fast. More than it should be. I mean, speeds between 7, and 10, I barely see a difference and the animation speed with these values should be a little more spaced
Note that I still want the fastest increment to be 0.1 and the slowest 0.005. But I need all the others to be linear between them.
What I'm doing wrong?
It actually makes sense why it works like this, for instance, for a fixed interval between increments, say a few milliseconds, and with the equation above, if X = 10, then opSpeed = 0.1 and if X = 5, then opSpeed = 0.47. If we think about this, a value of 0.1 will loop 10 times and a value of 0.47 will loop just the double. For such a small interval of just a few milliseconds, the difference between these values is not that much as to differentiate speeds from 5 to 10.
I think what you want is:
0.005 + ((0.1-0.005)/9)*(X-1)
for X ranging from 1-10
This gives a linear scale corresponding to 0.005 when X = 1 and 0.1 when X = 10
After the comments below, I'm also including my answer fit for a geometric series instead of a linear scale.
0.005 * (20^((X-1)/9)))
Results in a geometric variation corresponding to 0.005 when X = 1 and 0.1 when X = 10
After much more discussion, as seen in the comments below, the updates are as follows.
#Nazgulled found the following relation between my geometric series and the manual values he actually needed to ensure smooth fade animation.
The relationship was as follows:
Which means a geometric/exponential series is the way to go.
After my hours of trying to come up with the appropriate curve fitting to the right hand side graph and derive a proper equation, #Nazgulled informed me that Wolfram|Alpha does that. Seriously amazing. :)
Wolfram Alpha link
He should have what he wants now, barring very high error from the equation above.
Your problem stems from the fact that the human eye is not linear in its response; to be precise, the eye does not register the difference between a luminosity of 0.05 to 0.10 to be the same as the luminosity difference between 0.80 and 0.85. The whole topic is complicated; you may want to search for the phrase "gamma correction" for some additional information. In general, you'll probably want to find an equation which effectively "gamma corrects" for human ocular response, and use that as your fading function.
It's not really an answer, but I'll just point out that everyone who's posted so far, including the original question, are all posting the same equation. So with four independent derivations, maybe we should assume that the equation was probably correct.
I did the algebra, but here's the code to verify (in Python, btw, with offsets added to separate the curves:
from pylab import *
X = arange(1, 10, .1)
opSpeed0 = (((0.1 - 0.005) * (10 - X)) / (1 - 10)) + 0.1 # original
opSpeed1 = 0.005 + ((0.1-0.005)/9)*(X-1) # Suvesh
opSpeed2 = 0.005*((10-X)/9.) + 0.1*(X-1)/9. # duffymo
a = (0.1 - 0.005) / 9 #= 0.010555555555... # Roger
b = 0.005 - a #= -0.00555555555...
opSpeed3 = a*X+b
nonlinear01 = 0.005*2**((2*(-1 + X))/9.)*5**((-1 + X)/9.)
plot(X, opSpeed0)
plot(X, opSpeed1+.001)
plot(X, opSpeed2+.002)
plot(X, opSpeed3+.003)
plot(X, nonlinear01)
show()
Also, at Nazgulled's request, I've included the non-linear curve suggested by Suvesh (which also, btw, looks quite alot like a gamma correction curve, as suggested by McWafflestix). The Suvesh's nonlinear equation is in the code as nonlinear01.
Here's how I'd program that linear relationship. But first I'd like to make clear what I think you're doing.
You want the rate of change in opacity to be a linear function of speed:
o(v) = o1*N1(v) + o2*N2(v) so that 0 <= v <=1 and o(v1) = o1 and o(v2) = o2.
If we choose N1(v) to equal 1-v and N2(v) = v we end up with what you want:
o(v) = o1*(1-v) + o2*v
So, plugging in your values:
v = (u-1)/(10-1) = (u-1)/9
o1 = 0.005 and o2 = 0.1
So the function should look like this:
o(u) = 0.005*{1-(u-1)/9} + 0.1*(u-1)/9
o(u) = 0.005*{(9-u+1)/9} + 0.1*(u-1)/9
o(u) = 0.005*{(10-u)/9} + 0.1(u-1)/9
You can simplify this until you get a simple formula for o(u) where 1 <= u <= 10. Should work fine.
If I understand what you're after, you want the equation of a line which passes through these two points in the plane: (1, 0.005) and (10, 0.1). The general equation for such a line (as long as it is not vertical) is y = ax+b. Plug the two points into this equation and solve the resulting set of two linear equations to get
a = (0.1 - 0.005) / 9 = 0.010555555555...
b = 0.005 - a = -0.00555555555...
Then, for each integer x = 1, 2, 3, ..., 10, plug x into y = ax+b to compute y, the value you want.
What's a good algorithm for "bouncing cards" like the ones you see in solitaire games?
What's the coolest card animation you've seen?
Edit - Any besides the Windows game?
The x-axis velocity is constant. The y-velocity is incremented by some value every frame. Each frame, the current x and y positions are incremented by the respective velocities. If the card would end up below the window, y-velocity is multiplied by something like -0.9. (negative number > -1) This produces the series of descending bounces.
Two parts:
motion in the vertical direction is
governed by a second order equation,
like d=1/2at². For Earth, of
course, a= 32 ft/sec² but
you'll have to twiddle the
constants.
When the card hits the edge, as
"recursive" says, the velocity
vector is multiplied by -1 times the
component normal to the surface. If
you want it to bounce nicely to a
stop, make the -1 some slightly
smaller value, like -0.9.
Animate it by updating and redrawing the card some number of times a second, changing the position of the card each time. The easy way is to compute something like (pseudo-Python):
vel_x = # some value, units/sec
vel_y = # some value, units/sec
acc_x = # some value in units/sec^2
acc_y = # some value in units/sec^2
while not_stopped():
x = x+vel_x
y = y+vel_y
# redraw the card here at new position
if card_collided():
# do the bounce calculation
vel_x = x + (0.5 * acc_x) # 1st derivative, gives units/sec
vel_y = y + (0.5 * acc_y)
As long as the cards are staying four-square with the sides, then you collide with the sides when the distance between the card center and the wall is 1/2 the width or hieght as appropriate.
After struggling with the code Charlie provided for an hour or so, I came up with the proper algorithm (after reading recursive's response thoroughly). In real Python:
def bouncing_cards():
x = 0.0
y = 0.0
vel_x = 3.0
vel_y = 4.0
while x < windowWidth:
drawImage(img, x, y)
x += vel_x
y += vel_y
vel_y += 1.0
if y + cardHeight >= windowHeight:
y = windowHeight - cardHeight
vel_y *= -0.9
Gives the following using wxPython: http://i.imgur.com/t51SXVC.png :)
I am trying to build a function grapher,
The user enters xmin, xmax, ymin, ymax, function.
I got the x, y for all points.
Now i want to translate this initial referential to a Canvas starting at 0,0 up to
250,250.
Is there a short way or should i just check
if x < 0
new x = (x - xmin) * (250 / (xmax - xmin)) ?
etc ..
Also this basic approach does not optimise sampling.
For example if my function f(x) = 5 i dont need to sample the xrange in 500 points,
i only need two points. I could do some heuristic checks.
But for a function like sin(2/x) i need more sampling around x (-1,1) how would you aproach such a thing ?
Thanks
Instead of iterating over x in the original coordinates, iterate over the canvas and then transform back to the original coordinates:
for (int xcanvas = 0; xcanvas <= 250; i++) {
double x = ((xmax - xmin) * xcanvas / 250.0) + xmin;
double y = f(x);
int ycanvas = 250 * (y - ymin) / (ymax - ymin) + .5;
// Plot (xcanvas, ycanvas)
}
This gives you exactly one function evaluation for each column of the canvas.
You can estimate the derivative (if you have one).
You can use bidirectional (dichotomic) approach: estimate the difference and split the segment if necessary.
I think I would start by reasoning about this in terms of transformations from canvas to maths contexts.
(canvas_x, canvas_y) -> (maths_x, maths_y)
(maths_x, maths_y) -> (canvas_x, canvas_y)
maths_x -> maths_y
You iterate over the points that a displayable, looping over canvas_x.
This would translate to some simple functions:
maths_x = maths_x_from_canvas_x(canvas_x, min_maths_x, max_maths_x)
maths_y = maths_y_from_maths_x(maths_x) # this is the function to be plotted.
canvas_y = canvas_y_from_maths_y(maths_y, min_maths_y, max_maths_y)
if (canvas_y not out of bounds) plot(canvas_x, canvas_y)
Once you get here, it's relatively simple to write these simple functions into code.
Optimize from here.
I think that for this approach, you won't need to know too much about sample frequencies, because you sample at a rate appropriate for the display. It wouldn't be optimal - your example of y = 5 is a good example, but you'd be guaranteed not to sample more than you can display.