Say when using EA, and when I detect the current bar has the lowest price I have seen so far, I will draw a horizontal line below it, is it possible to achieve with EA rather than custom indicator?
yes, if your conditions are met - you can draw a horizontal line or trend line or update its price parameters. in order to draw a line, use the following function, for moving - ObjectSetDouble():
bool HLineCreate(const long chart_ID=0, // chart's ID
const string name="HLine", // line name
const int sub_window=0, // subwindow index
double price=0, // line price
const color clr=clrRed, // line color
const ENUM_LINE_STYLE style=STYLE_SOLID, // line style
const int width=1, // line width
const bool back=false, // in the background
const bool selection=true, // highlight to move
const bool hidden=true, // hidden in the object list
const long z_order=0) // priority for mouse click
{
//--- if the price is not set, set it at the current Bid price level
if(!price)
price=SymbolInfoDouble(Symbol(),SYMBOL_BID);
//--- reset the error value
ResetLastError();
//--- create a horizontal line
if(!ObjectCreate(chart_ID,name,OBJ_HLINE,sub_window,0,price))
{
Print(__FUNCTION__,
": failed to create a horizontal line! Error code = ",GetLastError());
return(false);
}
//--- set line color
ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
//--- set line display style
ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);
//--- set line width
ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width);
//--- display in the foreground (false) or background (true)
ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
//--- enable (true) or disable (false) the mode of moving the line by mouse
ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
//--- hide (true) or display (false) graphical object name in the object list
ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);
//--- set the priority for receiving the event of a mouse click in the chart
ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
//--- successful execution
return(true);
}
Related
Below is the current code.
The indicator compiles and runs
When running the waves move with the candles as new bar forms.
But when you turn the platform off for some time and then turn it back on The candles and sin waves are now no longer synchronized.
Somehow the subwindow rests back to when the platform was turned on.
So no one out of 92 looky-loos wants to tackle this problem. I tried to put 3 pictures on here to help explain, but the system here will not display the pictures. If you are a coder trying to help, copy the code here, and paste it into your platform. Run it. Mark a high peak or low trough with a text symbol that will not move and then put a vertical line through the text character and the candle above it. as the program proceeds the vertical line will move with the candles and the wave moves with the vertical line, the text marker stays put so you can see where you started. Then turn off your platform with the indicator still loaded, wait for several candle periods then turn the platform back on and you will see that the waves reset back to the right. the vertical line continued on with the appropriate candle and the text marker also reset to the right with the peak or valley of the sin wave.
//---- indicator settings
#property indicator_separate_window
#property indicator_buffers 8 // 7
#property indicator_color1 Red
#property indicator_color2 Orange
#property indicator_color3 Yellow
#property indicator_color4 Lime
#property indicator_color5 Blue
#property indicator_color6 DodgerBlue
#property indicator_color7 DarkViolet
#property indicator_color8 White
//---- indicator buffers
double ExtBuffer1[], ExtBuffer2[], ExtBuffer3[], ExtBuffer4[],
ExtBuffer5[],ExtBuffer6[], ExtBuffer7[], ExtBuffer8[];
double Dgr1[], Dgr2[], Dgr3[], Dgr4[], Dgr5[], Dgr6[], Dgr7[],
Dgr8[];
extern datetime StartTime=D'1999.11.10 00:00';
//+-------------------------------------------------------------+
//| Custom indicator initialization function |
//+-------------------------------------------------------------+
int init()
{
IndicatorBuffers(7); // 6
SetIndexStyle(0,DRAW_LINE); SetIndexBuffer(0,ExtBuffer1);
SetIndexShift(0,295);
SetIndexStyle(1,DRAW_LINE); SetIndexBuffer(1,ExtBuffer2);
SetIndexShift(1,264);
SetIndexStyle(2,DRAW_LINE); SetIndexBuffer(2,ExtBuffer3);
SetIndexShift(2,203);
SetIndexStyle(3,DRAW_LINE); SetIndexBuffer(3,ExtBuffer4);
SetIndexShift(3,166);
SetIndexStyle(4,DRAW_LINE); SetIndexBuffer(4,ExtBuffer5);
SetIndexShift(4,74);
SetIndexStyle(5,DRAW_LINE); SetIndexBuffer(5,ExtBuffer6);
SetIndexShift(5,102); // DodgerBlue
SetIndexStyle(6,DRAW_LINE); SetIndexBuffer(6,ExtBuffer7);
SetIndexShift(6,78); // Dark Violet
SetIndexStyle(7,DRAW_LINE);
SetIndexBuffer(7,ExtBuffer8); SetIndexShift(7,85); // White
SetLevelValue(0,0);
SetIndexStyle(0,DRAW_LINE,0,3); // 0
SetIndexStyle(2,DRAW_LINE,0,3); // 0
SetIndexStyle(3,DRAW_LINE,0,3); // 0
SetIndexStyle(4,DRAW_LINE,0,3); // 0
SetIndexStyle(5,DRAW_LINE,0,3); // 0
SetIndexStyle(6,DRAW_LINE,0,3); // 0
SetIndexStyle(7,DRAW_LINE,0,3); // 0
return(0);
}
//+-------------------------------------------------------------+
//| Accelerator/Decelerator Oscillator |
//+-------------------------------------------------------------+
int start()
{
int limit; // -----------------------------mine
int counted_bars=IndicatorCounted();
//---- last counted bar will be recounted
if(counted_bars>0) counted_bars--;
limit=Bars-counted_bars;
//-----------------------------------------mine
int Shift;
int i;
int b; // mine
Shift=iBarShift(Symbol(),PERIOD_CURRENT,StartTime);
ArrayResize(Dgr1,Shift+1);
ArrayResize(Dgr2,Shift+1);
ArrayResize(Dgr3,Shift+1);
ArrayResize(Dgr4,Shift+1);
ArrayResize(Dgr5,Shift+1);
ArrayResize(Dgr6,Shift+1);
ArrayResize(Dgr7,Shift+1);
ArrayResize(Dgr8,Shift+1);
MyCalc(Shift,1);
for(b=Shift; b<limit; b++) // mine
{
for(i=Shift; i>=0; i--) // >
{
ExtBuffer1[i]=Dgr1[i];
ExtBuffer2[i]=Dgr2[i];
ExtBuffer3[i]=Dgr3[i];
ExtBuffer4[i]=Dgr4[i];
ExtBuffer5[i]=Dgr5[i];
ExtBuffer6[i]=Dgr6[i];
ExtBuffer7[i]=Dgr7[i];
ExtBuffer8[i]=Dgr8[i];
}
} // mine
return(0);
}
//+-------------------------------------------------------------+
void MyCalc(int Shift, int Yhigh )
{
int i;
for(i=Shift;i>=0;i--)
{
Dgr1[i]=i*2.5; Dgr2[i]=i*2.5; Dgr3[i]=i*2.5; Dgr4[i]=i*2.5;
Dgr5[i]=i*2.5;
double val1=i*2.5;
double val2=i*2.5;
double val3=i*2.5;
double val4=i*2.5;
double val5=i*2.5;
double val6=i*2.5;
double val7=i*2.5;
double val8=i*2.5;
Dgr1[i]=MathSin(3.14159*val1/298)- 1/2 * Yhigh;
Dgr2[i]=MathSin(3.14159*val2/149)- 1/2 * Yhigh;
Dgr3[i]=MathSin(3.14159*val3/98)- 1/2 * Yhigh;
Dgr4[i]=MathSin(3.14159*val4/75)- 1/2 * Yhigh;
Dgr5[i]=.5*MathSin(3.14159*val5/60)- 1/2 * Yhigh;
Dgr6[i]=.5*MathSin(3.14159*val6/48)- 1/2 * Yhigh;
Dgr7[i]=.5*MathSin(3.14159*val7/42)- 1/2 * Yhigh;
Dgr8[i]=.5*MathSin(3.14159*val8/38)- 1/2 * Yhigh;
}
}
Your problem is that the indicator starts drawing at D'1999.11.10 00:00', however as this date is no doubt not available in your visible chart history, the indicator instead starts redrawing as the earliest available bar. This continues as you run the indicator, however when restarting MQL4, the visible bars is limited by your settings (Tools>Options>Charts>Max bars in chart) and upon reloading the indicator again reverts to start drawing at the earliest available bar which has now changed. As stated in my original comment, your calculation are not directly linked to candles so they will not shift consistently. If you wish to make them consistently, make the start point consistent somehow.
I'm just learning to code and have few questions. Can someone tell me please how to do this thing;
1- How to prevent my custom indicator being duplicated on the chart? If it's already on the chart and I will drop it on to the chart again, I want it to detect the first one and abort launching the second one.
something like this: but working :)
int OnInit()
{
int indicators_total = ChartIndicatorsTotal(0,0);
for(int i = 0; ndicators_total > i; i++)
{
if(ChartIndicatorName(0,0,i)==IndicatorName)
return(INIT_FAILED); (AND THEN EXIT)
}
}
2 - How do I detect if there is more than one indicator with the same name on the chart?
3 - How to write an "if" statement to do something if indicator "x" is not present (=0) on the chart?
Something like this: but working :)
if(IndicatorName==0)
{
Print("INDI ",IndicatorName, " NOT DETECTED");
}
4 - And is there a way to put keyboard event (F11 - full screen) into code? To make my custom indicator be able to detect when full screen is on just like with the "chart scale" ChartGetInteger(0,CHART_SCALEFIX); ?
Welcome to SOF. Look at counter below. The idea is to count number of indicators on the chart. The one you are adding will be in this list either once (this copy) or more (you are trying to add a new copy to an existing one). This example is for main chart (subwindow = 0).
int OnInit()
{
const string indName = WindowExpertName(); // name of your indicator
int counter=0;
for(int i=ChartIndicatorsTotal(0,0)-1;i>=0;i--)
{
//printf("%i %s: i=%d/%d, %s",__LINE__,__FILE__,i,ChartIndicatorsTotal(0,0),ChartIndicatorName(0,0,i));
if(ChartIndicatorName(0,0,i)==indName)
counter++;
}
if(counter>1)
{
Print("already exist!");
return INIT_FAILED;
}
//--- indicator buffers mapping
// do all other indicator preparation work here
//---
return(INIT_SUCCEEDED);
}
I am writing an MQL4 Custom Indicator that would tell how many bars ago a particular set of moving averages crossed.
To be specific, I want the output to show me that
"The 20 period MA( .. PRICE_OPEN ) is below MA( .. PRICE_CLOSE ) for the past 10 bars".
in a form of an int number.
double ma1 = iMA( Symbol(), 10080, 20, 0, 0, PRICE_OPEN, 0 );
double ma2 = iMA( Symbol(), 10080, 20, 0, 0, PRICE_CLOSE, 0 );
I want to find out that ma1 has been above or below ma2 for how many bars since the current bar.
TLDR;
MQL4 Custom Indicator design is a bit schizophrenic task with 2 parts
Once decided to design a Custom Indicator, one has to pay attention to both sides of the coin.
MQL4 code has a non-trivial signature for a caller-side( normally an
Expert Advisor or a Script type of MQL4 code )and
Custom Indicator, on it's own, operates in a special mode, where it calculates & store it's values into one or more arrays called IndexBuffer-s.
Phase I.: design Caller-side interface ( what all do we need to set / receive ? )
Phase II.: design Custom Indicator implementation side
This way, the given indicator shall
set just one parameter: a period ... 20
receive just one int a value of days { +:above | 0: xoss | -: under }
Phase I. : Design Caller-side interface
Given the above parametrisation and the MQL4 concept of Custom Indicator construction, the following universal template ( recommended to be routinely maintained as a commented section right in CustomIndicator-file ) reflects details needed for the Caller-side interface ( which one benefits from by just a copy-paste mechanics, while the implementation-integrity responsibility is born by the CustomIndicator maintainer ):
// CALL-er SIDE INTERFACE |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//
// #define sIndicatorPathNAME "#AliceInTheWonderlands_msMOD_0.00"
//
// //_____________________________________INPUT(s)
// // iMA_PERIOD:
// extern int iMA_PERIOD = 20;
//
// //_____________________________________OUTPUT(s):
// #define iOutputDoubleUpOrDnBUFFER 0
//
// //_____________________________________CALL-SIGNATURE:
//
// double iCustom( _Symbol, // string symbol, // symbol: Symbol name on the data of which the indicator will be calculated. NULL means the current symbol.
// PERIOD_W1, // int timeframe, // timeframe
// sIndicatorPathNAME, // string name, // path/name of the custom indicator compiled program: Custom indicator compiled program name, relative to the root indicators directory (MQL4/Indicators/). If the indicator is located in subdirectory, for example, in MQL4/Indicators/Examples, its name must be specified as "Examples\\indicator_name" (double backslash "\\"must be specified as separator instead of a single one).
// iMA_PERIOD, // ...[1] ..., // custom indicator [1]-st input parameter
// <<N/A>>, // ...[2+] // custom indicator further input parameters (if necessary)
// iOutputDoubleUpOrDnBUFFER, // int mode, // line index: Line index. Can be from 0 to 7 and must correspond with the index, specified in call of the SetIndexBuffer() function.
// i // int bar_shift // shift
// );
// ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Typical Expert Advisor call looks like this:
int anIndicatorVALUE = iCustom( _Symbol,
PERIOD_W1,
sIndicatorPathNAME,
iMA_PERIOD,
iOutputDoubleUpOrDnBUFFER,
aCurrentBarPTR
);
if ( 0 > anIndicatorVALUE ) // on "line" under for <anIndicatorVALUE> [PERIOD]-s
{ ...
}
Phase II. : Design Custom Indicator implementation side
Preset an overall capacity
#property indicator_buffers 1 // compile time directive
Allocate memory for IndexBuffer(s) needed
IndicatorBuffers( 1 ); // upper limit of 512 is "far" enough
Declare IndexBuffer
double UpOrDnBUFFER[]; // mandatory to be a double
Associate IndexBuffer with an index
SetIndexBuffer( 0, UpOrDnBUFFER ); // index 0: UpOrDnBUFFER[]
ArraySetAsSeries( UpOrDnBUFFER, True ); // reverse AFTER association
The core logic of any Custom Indicator is inside an OnCalculate() function, where you
- implement desired calculusand- store resulting values into respective cells of the UpOrDnBUFFER[];
Writing a fully fledged code upon request is not the key objective of StackOverflow, but let me sketch a few notes on this, as Custom Indicator implementation side requires a bit practice:
because Custom Indicator OnCalculate() operates "progressively", so do design your calculation strategy & keep in mind the state-less-ness between "blocks" of forward-progressive blocks of calculations.
as given, crosses are tri-state system, so watch issues on cases, where decisions are made on a "live" bar, where the state { above | cross | under } may change many times during the bar-evolution.
I want to draw a line using mouse-event in Opencv in a webcam frame. I also want to erase it just like an eraser in MS-Paint.How can i do it? I dont have much idea about it. But i have this scrambled pseduo code from my head which can be completely wrong but i will write it down anyway. I would like to know how to implement it in c++.
So, i will have two three mouse event-
event 1- Mouse leftbuttonup-- this will be used to start the drawing
event 2- Mouse move -- this will be used to move the mouse to draw
event 3:- Mouse leftbuttondown-this will be used to stop the drawing.
event 4- Mouse double click - this event i can use to erase the drawing.
I will also have a drawfunction for a line such as line(Mat image,Point(startx,starty),Point(endx,endy),(0,0,255),1));
Now, i dont know how to implement this in a code format. I tried a lot but i get wrong results. I have a sincere request that please suggest me the code in Mat format not the Iplimage format. Thanks.
please find working code below with inlined explained comments using Mat ;)
Let me know in case of any problem.
PS: In main function, I have changed defauld cam id to 1 for my code, you should keep it suitable for you PC, probably 0. Good Luck.
#include <iostream>
#include <opencv\cv.h>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
class WebCamPaint
{
public:
int cam_id;
std::string win_name;
cv::VideoCapture webCam;
cv::Size frame_size;
cv::Mat cam_frame, drawing_canvas;
cv::Point current_pointer, last_pointer;
cv::Scalar erase_color, paint_color;
int pointer_size;
//! Contructor to initialize basic members to defaults
WebCamPaint()
{
cam_id = 0;
pointer_size = 5;
win_name = std::string("CamView");
current_pointer = last_pointer = cv::Point(0, 0);
erase_color = cv::Scalar(0, 0, 0);
paint_color = cv::Scalar(250, 10, 10);
}
//! init function is required to set some members in case default members needed to change.
bool init()
{
//! Opening cam with specified cam id
webCam.open(cam_id);
//! Check if problem opening video
if (!webCam.isOpened())
{
return false;
}
//! Reading single frame and extracting properties
webCam >> cam_frame;
//! Check if problem reading video
if (cam_frame.empty())
{
return false;
}
frame_size = cam_frame.size();
drawing_canvas = cv::Mat(frame_size, CV_8UC3);
//! Creating Activity / Interface window
cv::namedWindow(win_name);
cv::imshow(win_name, cam_frame);
//! Resetting drawing canvas
drawing_canvas = erase_color;
//! initialization went successful ;)
return true;
}
//! This function deals wih all processing, drawing and displaying ie main UI to user
void startAcivity()
{
//! Keep doing until user presses "Esc" from Keyboard, wait for 20ms for user input
for (char user_input = cv::waitKey(20); user_input != 27; user_input = cv::waitKey(20))
{
webCam >> cam_frame; //Read a frame from webcam
cam_frame |= drawing_canvas; //Merge with actual drawing canvas or drawing pad, try different operation to merge incase you want different effect or solid effect
cv::imshow(win_name, cam_frame); //Display the image to user
//! Change size of pointer using keyboard + / -, don't they sound fun ;)
if (user_input == '+' && pointer_size < 25)
{
pointer_size++;
}
else if (user_input == '-' && pointer_size > 1)
{
pointer_size--;
}
}
}
//! Our function that should be registered in main to opencv Mouse Event Callback
static void onMouseCallback(int event, int x, int y, int flags, void* userdata)
{
/* NOTE: As it will be registered as mouse callback function, so this function will be called if anything happens with mouse
* event : mouse button event
* x, y : position of mouse-pointer relative to the window
* flags : current status of mouse button ie if left / right / middle button is down
* userdata: pointer o any data that can be supplied at time of setting callback,
* we are using here to tell this static function about the this / object pointer at which it should operate
*/
WebCamPaint *object = (WebCamPaint*)userdata;
object->last_pointer = object->current_pointer;
object->current_pointer = cv::Point(x, y);
//! Drawing a line on drawing canvas if left button is down
if (event == 1 || flags == 1)
{
cv::line(object->drawing_canvas, object->last_pointer, object->current_pointer, object->paint_color, object->pointer_size);
}
//! Drawing a line on drawing canvas if right button is down
if (event == 2 || flags == 2)
{
cv::line(object->drawing_canvas, object->last_pointer, object->current_pointer, object->erase_color, object->pointer_size);
}
}
};
int main(int argc, char *argv[])
{
WebCamPaint myCam;
myCam.cam_id = 1;
myCam.init();
cv::setMouseCallback(myCam.win_name, WebCamPaint::onMouseCallback, &myCam);
myCam.startAcivity();
return 0;
}
UIView drawRect is jerky i.e., does not scroll smoothly across the screen. I have tried performing 'setNeedsDisplay' at various intervals from 200-ms to 500-ms and nothing seems
to improve the look. That is as the plots move across the screen they stop and start.
Is there any way that I can improve this to make the display more smooth?
/*==============================================================================*/
/* 'drawRect' - override function. */
/* */
/* This function is responsible for plotting cardiac waveforms (ECG) and also */
/* erases any prior ECG plot values that may be currently visible on the */
/* screen. */
/* */
/* It does this indirectly via 'EcgThread' or 'ECGErase' which send the */
/* 'setNeedsDisplay' message which triggers the 'drawRect' callback function. */
/* Once an ECG is started 'EcgThread' controls the frequency of 'drawRect' */
/* being by simple timer. (See 'EcgThread' for current timer value.) */
/* */
/*==============================================================================*/
/* Here's how the entire ECG process works: */
/* */
/* 1) User starts ECG which causes a 'send_ecg' command to be sent to */
/* 'CommThread'. */
/* */
/* 2) 'CommThread' the sends the 'send_ecg' command to the Pacemaker and then */
/* sends itself the 'read_ecg' command.*/
/* */
/* 3) The Pacemaker then starts acquiring 64 cardiac A/D samples (10-bits) per */
/* second and sends them back to 'CommThread'. */
/* */
/* 4) As samples are received by 'CommThread' (via 'read_ecg') on a streaming */
/* basis they are stored in 'ECGY' array (vertical plots) in reverse order */
/* i.e., from top to bottom (currently room for 128 samples). */
/* */
/* 5) 'EcgThread' runs continuously on a timer basis sending 'setNeedsDisplay' */
/* message which causes 'iOS' to perform callbacks to 'drawRect' who is */
/* responsible for drawing the cardiac (plots) waveforms from left to right */
/* across (horizontally) the screen. */
/* */
/* 6) 'drawRect' processes 'ECGY' bottom to top (opposite of 'CommThread') and */
/* each draw loop (plotting new values) takes about 13-millseconds. This is */
/* necessary because not doing so results in drawing upside down plots. */
/* */
/* 7) User stops ECG and 'TimerProc' sends a series of 'send_ecgstop' commands */
/* to 'CommThread' who in turn sends the 'send_ecgstop' commands to the PM. */
/* The reason why we send multiple 'send_ecgstop' is due to the streaming */
/* nature of the sending ECG samples and getting the PM to 'listen' to us. */
/* Usually stopping will go smoothly. Occasionally it may be necessary to */
/* move the Wand away, wait a few seconds and place Wand back over patient's */
/* chest (causing an interrupt) before normal operation returns. */
/* */
/*==============================================================================*/
- (void) drawRect : (CGRect) rect // Callback routine
{
int i, ii, x, xx, y; // Local array indices
fEcgDraw = YES; // Show 'drawRect' is running
[super drawRect : rect]; // Call standard handler
CGContextRef context = UIGraphicsGetCurrentContext (); // Get graphics context
CGContextSetAllowsAntialiasing (context, NO); // Turn off anti-alliaing
CGContextSetLineWidth (context, 1); // Set width of 'pen'
/*==============================================================================*/
/* 'HH' is used as a height bias in order to position the waveform plots in */
/* middle of the view (screen). */
/*==============================================================================*/
HH = 424; // Force height bias for now
if (fEcgErase == YES) // Show we erase the view?
{
CGContextSetStrokeColorWithColor (context, // Set color of 'pen'
[UIColor blackColor].CGColor); // Black (to erase)
/*==============================================================================*/
/* Erase the last screen. */
/*==============================================================================*/
for (i = 0, x = 0; i < 127; i++) // Iterate for all array elements
{
CGContextMoveToPoint (context, // Update current position to specified point
ECGX[x], // Starting X-coordinate
(HH - ECGS[x])); // Starting Y-coordinate (with height bias)
CGContextAddLineToPoint (context, ECGX[(x + 1)], // Draw line from current position
(HH - ECGS[((x + 1) % 127)])); // Ending Y-coordinate (with height bias)
x++; // Step to next array element
} // end - for (i = 0; i < 127; i++)
CGContextClosePath (context); // Close current path
CGContextStrokePath (context); // Stroke current path (paint the path)
fEcgErase = NO; // Reset erase flag
} // end - if (fEcgErase == YES)
else if (fECGLOOP) // Did request come from 'EcgThread'?
/*==============================================================================*/
/* Draw ECG cardiac waveforms on view. */
/*==============================================================================*/
{
xx = 1; // Counts markers
x = 0; // Reset horizontal axis
y = YY; // Use saved startimg ECGY[] index
ii = 0; // Initialize marker count
#define GRIDSIZE 12 // Grid width in pixels
int width = rect.size.width; // Get the view width
int height = rect.size.height; // Get the view height
/*==============================================================================*/
/* First draw a grid pattern to draw ECG waveforms into. */
/*==============================================================================*/
CGContextSetStrokeColorWithColor (context, // Set color of 'pen'
[UIColor lightGrayColor].CGColor); // Use 'light gray' for grid pattern
for (i = 0; i <= width; i = i+GRIDSIZE) // First the vertical lines
{
CGContextMoveToPoint (context, i, 0); // Update current position to specified point
CGContextAddLineToPoint (context, i, height); // Draw line from current position
} // end - for (i = 0; i <= width; i = i+GRIDSIZE)
for (i = 0 ; i <= height; i = i+GRIDSIZE) // Then the horizontal lines
{
CGContextMoveToPoint (context, 0, i); // Update current position to specified point
CGContextAddLineToPoint (context, width, i); // Draw line from current position
} // end - for (i = 0 ; i <= height; i = i+GRIDSIZE)
CGContextClosePath (context); // Close current path
CGContextStrokePath (context); // Stroke current path (paint the path)
/*==============================================================================*/
/* Now draw (plot) cardiac waveforms using using pre-stored ECG sample values. */
/*==============================================================================*/
for (i = 0; i < 127; i++) // Iterate for number ECGY[] entries
{
/*==============================================================================*/
/* Erase the prior ECG A/D plot value. */
/*==============================================================================*/
#if 0 // NOT NEEDED CUZ WE SELECTED CLEAR CONTEXT IN EcgViewController.xib
CGContextSetStrokeColorWithColor (context, // Set color of 'pen'
[UIColor blackColor].CGColor); // Black to erase old plot
CGContextMoveToPoint (context, // Update current position to specified point
ECGX[x], // Starting X-coordinate of prior position
(HH - ECGS[x])); // Starting Y-corrdinate with height bias
CGContextAddLineToPoint (context, // Draw line from current position
ECGX[(x + 1)], // Ending X-coordinate
(HH - ECGS[((x + 1))])); // Ending Y-coordinate using saved Y-axis (with height bias)
CGContextClosePath (context); // Close current path
CGContextStrokePath (context); // Stroke current path (paint the path)
#endif // NOT NEEDED CUZ WE SELECTED CLEAR CONTEXT IN EcgViewController.xib
/*==============================================================================*/
/* Plot the next ECG A/D plot value. */
/*==============================================================================*/
CGContextSetStrokeColorWithColor (context, // Set color of 'pen'
[UIColor greenColor].CGColor); // White to draw new plot
CGContextMoveToPoint (context, // Update current position to specified point
ECGX[x], // Starting X-coordinate of new position
(HH - ECGY[y])); // Starting Y-coordinate with height bias
CGContextAddLineToPoint (context, // Draw line & prevent overrun
ECGX[(x + 1)], // Ending X-coordinate
(HH - ECGY[((y + 1) % 127)])); // Ending Y-axis (with height bias)
CGContextClosePath (context); // Close current path
CGContextStrokePath (context); // Stroke current path (paint the path)
ECGS[x] = ECGY[y]; // Save last plot value for erase
x++; // Next ECGX[] (y-axis) plot value index
/*==============================================================================*/
/* Below as we increment 'y' it will eventually roll to zero and when we get */
/* to the end of the above 'for' loop 'y' will have its starting value. */
/*==============================================================================*/
y = ((y + 1) % 127); // Next ECGY[] (y-axis) plot value & prevent overrun
ulPlots++; // Count number of plots
} // end - for (i = 0; i < 127; i++)
y = ((y + 16) % 127); // Next starting y-axis 'ECGY' index
YY = y; // Save it for next iteration
EcgCount = 0; // Reset skip count (inc. by 'CommThread'
} // end - if (fEcgErase == YES)
// UIGraphicsPopContext();
fEcgDraw = NO; // Show 'drawRect' not running
// [NSThread sleepForTimeInterval : 0.1]; // Delay a little
} // end - 'drawRect'
/*===============================END OF FUNCTION================================*/
Keep in mind that drawing only ever happens on the main thread, which means that while drawRect: is running, your entire UI blocks. If this drawing code is slow, then your application will be nonresponsive while the drawing is taking place. Based on the size of the code and amount of individual drawing operations you're doing in this method, that seems to be what's happening here.
drawRect: gets its name from the fact that you're supposed to limit your drawing to the area described by rect. It's not 100% clear from your question if the entire graph changes every time, or if only a little bit of new data is being added. If there's a way you can restructure your code so that the entire thing doesn't need to be redrawn every time, that will almost certainly eliminate your jerkiness problems.
For example, you could also avoid drawing the gridlines on each refresh by putting them in a separate view behind the view that contains the plot. The gridlines (I'm guessing) don't need to be redrawn very often, so if you make your plot view transparent, UIKit will composite them for you and you can avoid a drawing operation. Probably a small savings, but anything helps. That would also mean that you can erase your view by filling it with the [UIColor clearColor]. Drawing over the old plot with the background color is an extremely expensive operation, but rectangle fills are dirt cheap. It's less code and it runs faster.
If that's not enough, you could also do your drawing into a separate UIImage offscreen, and then simply replace the contents of your view with this image. This would give you better performance because you would be able to do the image drawing (which is the expensive part) in a separate thread, and so the drawing operation wouldn't block the main application thread, which will eliminate the jerkiness.
I had a similar problem where I wanted to rotate an object through a specific number of radians based on a touch gesture. The rotation was jittery. I solved the problem by using CADisplayLink to synchronize the updates with the run loop:
#property (nonatomic, strong) CADisplayLink *displayLink;
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(rotate)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
Every time the run loop updated the UI, my 'rotate' method was called. The rotation was smooth and I didn't notice any performance hits.
This was for an affine transformation that is not a very expensive operation. I suppose you could put setNeedsDisplay in a method that is called by a CADisplayLink object but that would likely be an expensive operation. Still, maybe worth a try.