Skip to content

Commit

Permalink
updated for 2v8
Browse files Browse the repository at this point in the history
  • Loading branch information
p-j-miller authored May 23, 2022
1 parent e00e11e commit d413156
Show file tree
Hide file tree
Showing 12 changed files with 2,342 additions and 38 deletions.
96 changes: 84 additions & 12 deletions UDataPlotWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
// : display to user 1 example of every type of error in csv file (via rprintf)
// : if dates present on some lines then flag lines without a date as a potential error
// : new (exact) median (recursive median filter) algorithm, which falls back to sampling if the execution time becomes long.
//
// 2v8 24/3/2022 : strptime() function added for date/time handling
// : csvsave added % complete and buffering on output to speed up writing to file.
// : csvsave interpolates if required so x values do not need to be identical on all traces
//---------------------------------------------------------------------------
/*----------------------------------------------------------------------------
* Copyright (c) 2019,2020,2021,2022 Peter Miller
Expand Down Expand Up @@ -121,9 +123,11 @@
#include <float.h>
#include "matrix.h" /* must come before include of multiple-lin-reg.h */
#include "multiple-lin-reg-fn.h"
#include "time_local.h"


extern TForm1 *Form1;
const char * Prog_Name="CSVgraph (Github) 2v7"; // needs to be global as used in about box as well.
const char * Prog_Name="CSVgraph (Github) 2v8e"; // needs to be global as used in about box as well.
#if 1 /* if 1 then use fast_strtof() rather than atof() for floating point conversion. Note in this application this is only slightly faster (1-5%) */
extern "C" float fast_strtof(const char *s,char **endptr); // if endptr != NULL returns 1st character thats not in the number
#define strtod fast_strtof /* set so we use it in place of strtod() */
Expand Down Expand Up @@ -1467,8 +1471,11 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
char *start_s;
char *se;// expression (if there is one)
bool isnumber;
char *date_time_fmt=NULL;
date_time_fmt=strdup(AnsiOf(Date_time_fmt->Text.c_str())); // only do this once
if(Xcol_type->ItemIndex==3) rprintf("Date/time format is: %s\n",date_time_fmt);
s=strdup(AnsiOf(Edit_ycol->Text.c_str()));
if(s==NULL)
if(s==NULL||date_time_fmt==NULL)
{
ShowMessage("Error (No RAM): invalid ycol ["+Edit_ycol->Text+"] (valid range 1.."+AnsiString(MAX_COLS)+")");
fclose(fin);
Expand All @@ -1480,8 +1487,8 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
#if 1
// if x axis label is the default label then set it based on the data just about to be read in
if( pScientificGraph->XLabel==default_x_label)
{ // update label from data read
switch(Xcol_type->ItemIndex)
{ // update label from data read
switch(Xcol_type->ItemIndex)
{case 0:
if(is_fft)
{pScientificGraph->XLabel="Frequency"; // x=linenumber in file
Expand All @@ -1493,6 +1500,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
}
break;
case 1: // time h:m:s.s - converted into time (secs)
case 3: // date & time - also converted into seconds
if(is_fft)
{
pScientificGraph->XLabel="Frequency (Hz)";
Expand Down Expand Up @@ -1575,7 +1583,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
print_rpn(); // print out rpn for debugging
rprintf("\n");
#endif
// use se later in title for trace so cannot free yet
// use se later in title for trace so cannot free yet
}
else
{
Expand All @@ -1584,7 +1592,8 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
fclose(fin);
StatusText->Caption="Invalid expression for ycol";
free(s);
free(se);
free(se);
if(date_time_fmt!=NULL) {free(date_time_fmt); date_time_fmt=NULL;}
addtraceactive=false;// finished
return;
}
Expand All @@ -1600,7 +1609,8 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
// rprintf("Error: invalid ycol (range 1..%d)\n",MAX_COLS);
ShowMessage("Error: invalid ycol (range 1.."+AnsiString(MAX_COLS)+")");
fclose(fin);
StatusText->Caption="Invalid ycol";
StatusText->Caption="Invalid ycol";
if(date_time_fmt!=NULL) {free(date_time_fmt); date_time_fmt=NULL;}
addtraceactive=false;// finished
return;
}
Expand Down Expand Up @@ -1871,12 +1881,11 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
}
}
else
{
{double ti;
switch(Xcol_type->ItemIndex)
{case 0: xval=lines_in_file; // x=linenumber in file
break;
case 1: // time h:m:s.s (with optional date of form 05-Jul-19 or 2020-03-31 or similar terminated in whitespace)
double ti; // needs to be a double as we subtract first_time before converting to a float
st=col_ptrs[xcol-1];
while(isspace(*st)) ++st; // skip any leading whitespace
if(*st=='"')
Expand Down Expand Up @@ -1937,6 +1946,64 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
xval=ti; // can loose resolution for big times, but we are limited by storing xvalues as floats.
}
break;
case 3: // date/time with user specified format - stored in char * date_time_fmt
{ // there is no need to skip initial whitespace or deal with quotes here as can be defined in date_time_fmt
//char * ya_strptime(const char *s, const char *format, struct tm *tm)
char *dptr;
struct tm my_tm;
memset(&my_tm, 0, sizeof(struct tm));// zero all members of tm
strp_tz.initialised=0; // mark as not initialised, so will be zeroed on call to ya_strptime();
st=col_ptrs[xcol-1];
dptr=ya_strptime(st,date_time_fmt,&my_tm);
if(dptr==NULL)
{// something was wrong with date/time or format
allvalidxvals=false;
if((++nos_errs<=MAX_ERRS || !found_error_type[ERR_TYPE1]) )
{found_error_type[ERR_TYPE1]=true; // note we have printed an example of this type of error
rprintf("Warning: x value on line %d has an invalid date/time (strptime(\"%s\") returned NULL): %s\n",lines_in_file+1,date_time_fmt,col_ptrs[xcol-1]);
}
continue; // ignore line
}
if(*dptr!=0)
{ // ya_strptime() did not process all of the string again issue could be date/time or format
allvalidxvals=false;
if((nos_errs<=MAX_ERRS || !found_error_type[ERR_TYPE2]) )
{found_error_type[ERR_TYPE2]=true; // note we have printed an example of this type of error
rprintf("Warning: x value on line %d has an invalid date/time (format \"%s\" did not match whole string): %s\n",lines_in_file+1,date_time_fmt,col_ptrs[xcol-1]);
}
continue; // ignore line
}
if(!check_tm(&my_tm))
{ // ya_strptime() did not process all of the string again issue could be date/time or format
allvalidxvals=false;
if((nos_errs<=MAX_ERRS || !found_error_type[ERR_TYPE3]) )
{found_error_type[ERR_TYPE3]=true; // note we have printed an example of this type of error
rprintf("Warning: x value on line %d has an invalid date/time (check_tm() failed): %s\n",lines_in_file+1,col_ptrs[xcol-1]);
}
continue; // ignore line
}
ti=ya_mktime(&my_tm); /* fully functional version of mktime() that returns secs and takes (and changes if necessary) timeptr */
ti+=strp_tz.f_secs;// add in any fractional seconds */

if(firstxvalue)
{first_time=ti; // remember 1st value, and potentially use as offset for the rest of the values
}
if(start_time_from_0)
{
/* line below assumes times are in increasing order which is NOT guaranteed ! */
/* However, first_time is a double, as is ti so this potentially offers higher resolution as the difference is stored as a float */
xval=ti-first_time; // ofset time by time of 1st value (so we maximise resolution in the float xval)
}
else
{
xval=ti; // can loose resolution for big times, but we are limited by storing xvalues as floats.
}
if(0 && lines_in_file<5)
{// useful for debugging
rprintf("date/time on line %d returns %g secs (format \"%s\" line: %s, ti=%g)\n",lines_in_file+1,xval,date_time_fmt,col_ptrs[xcol-1],ti);
}
}
break;
default: // should only be "2" - value in specified column
st=col_ptrs[xcol-1];
while(isspace(*st)) ++st; // skip any leading whitespace
Expand Down Expand Up @@ -2036,6 +2103,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
fclose(fin);
StatusText->Caption="Error: not enough memory to load all specified columns";
if(s) free(s);
if(date_time_fmt!=NULL) {free(date_time_fmt); date_time_fmt=NULL;}
addtraceactive=false;// finished
pScientificGraph->fnDeleteGraph(iGraph); // delete partial column
if(iGraph==0 || !zoomed)
Expand Down Expand Up @@ -2118,7 +2186,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
if(median_ahead_t>0.0)
{
StatusText->Caption=FString;
pScientificGraph->fnMedian_filt_time1(median_ahead_t,iGraph,filter_callback);
pScientificGraph->fnMedian_filt_time1(median_ahead_t,iGraph,filter_callback);
}
break;
case 3:
Expand Down Expand Up @@ -2335,7 +2403,8 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
ShowMessage("Error: cannot rewind input file to read next column");
fclose(fin);
StatusText->Caption="Error reading multiple columns";
if(s) free(s);
if(s) free(s);
if(date_time_fmt!=NULL) {free(date_time_fmt); date_time_fmt=NULL;}
addtraceactive=false;// finished
return;
}
Expand All @@ -2344,6 +2413,9 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
goto repeatcomma; // go and process next
}
// free memory potentially used
if(s) free(s);
if(date_time_fmt!=NULL) {free(date_time_fmt); date_time_fmt=NULL;}
// #define DEBUG_RAM_USED /* when defined use rprintf to give "user" more info */
#if 1 /* this gives 357.1MB when task manager gives 358.3 MB */
TMemoryMap mmap;
Expand Down
31 changes: 23 additions & 8 deletions UDataPlotWindow.dfm
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object PlotWindow: TPlotWindow
OnResize = FormResize
DesignSize = (
1290
982)
1002)
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Expand Down Expand Up @@ -630,11 +630,11 @@ object PlotWindow: TPlotWindow
BorderStyle = bsNone
Color = clMenu
Constraints.MaxWidth = 250
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Charset = ANSI_CHARSET
Font.Color = clBlack
Font.Height = -13
Font.Name = 'MS Sans Serif'
Font.Style = []
Font.Name = 'Myanmar Text'
Font.Style = [fsBold]
ParentFont = False
ParentShowHint = False
ReadOnly = True
Expand All @@ -654,7 +654,8 @@ object PlotWindow: TPlotWindow
Items.Strings = (
'Line number in file'
'Time (h:m:s) in X column'
'Value in X column')
'Value in X column'
'Date/Time in X column')
ParentShowHint = False
ShowHint = True
TabOrder = 5
Expand Down Expand Up @@ -866,18 +867,32 @@ object PlotWindow: TPlotWindow
end
object Time_from0: TCheckBox
Left = 179
Top = 420
Top = 416
Width = 109
Height = 17
Hint =
'Tick this box to start time from zero (based on the first date/t' +
'ime in the csv file)'
Anchors = [akTop, akRight]
Caption = 'Start time from 0'
ParentShowHint = False
ShowHint = True
TabOrder = 24
OnClick = Time_from0Click
end
object Date_time_fmt: TEdit
Left = 179
Top = 452
Width = 109
Height = 21
Hint = 'Enter date/time format eg %d/%m/%y %H:%M:%S.%f'
Anchors = [akTop, akRight]
ParentShowHint = False
ShowHint = True
TabOrder = 25
Text = '%d-%b-%y %H:%M:%S.%f'
OnChange = Edit_xcolChange
end
end
object ActionList1: TActionList
Images = ImageList1
Expand Down Expand Up @@ -949,7 +964,7 @@ object PlotWindow: TPlotWindow
Left = 304
Top = 32
Bitmap = {
494C01010A000E00E80110001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
494C01010A000E00000210001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000003000000001002000000000000030
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
Expand Down
1 change: 1 addition & 0 deletions UDataPlotWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class TPlotWindow : public TForm
TLabel *Label15;
TLabel *Label16;
TCheckBox *Time_from0;
TEdit *Date_time_fmt;
void __fastcall FormDestroy(TObject *Sender);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall ResizeExecute(TObject *Sender);
Expand Down
62 changes: 46 additions & 16 deletions UScientificGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@

#include <exception>
#include "UScientificGraph.h"
#include "UScalesWindow.h"
#include "UDataPlotWindow.h"
#include "Unit1.h"
#include "About.h"
#define NoForm1 /* says Form1 is defined in another file */
#include "expr-code.h"
#include <cmath>
Expand All @@ -60,6 +64,8 @@
#define P_UNUSED(x) (void)x; /* a way to avoid warning unused parameter messages from the compiler */
#define DOUBLE float /* define as double to go back to original, but as points are stored as floats we can use floats in several places */

extern TForm1 *Form1;

double actual_dXMin=0,actual_dXMax=100,actual_dYMin=-1,actual_dYMax=1;// initialised to same values as below

int zoom_fun_level=0; // used to keep track of level of recursion in zoom function (allows partial display refresh for speed in multiple zooms)
Expand Down Expand Up @@ -3710,35 +3716,39 @@ bool TScientificGraph::SaveCSV(char *filename,char *x_axis_name)
int i,j;
SGraph *aGraph,*xGraph;
FILE *fp;
char errorstr[256]; // buffer for sprintf strings
errorstr[sizeof(errorstr)-1]=0; // make sure null terminated
char cstr[256]; // buffer for sprintf strings
time_t start_t=clock();
time_t lastT=start_t;
cstr[sizeof(cstr)-1]=0; // make sure null terminated
if(iNumberOfGraphs==0)
{snprintf(errorstr,sizeof(errorstr)-1,"Error on CSV save: Nothing to save!"); // sizeof -1 as last character set to null above
ShowMessage(errorstr);
{snprintf(cstr,sizeof(cstr)-1,"Error on CSV save: Nothing to save!"); // sizeof -1 as last character set to null above
ShowMessage(cstr);
return false;
}
// check that all graphs have the same number of points in them
// check if all graphs have the same number of points in them , if not warn user and extrapolate missing data
for (i=0; i<iNumberOfGraphs; i++) //for all graphs
{
aGraph=(SGraph*) pHistory->Items[i];
if(i==0)
aGraph=(SGraph*) pHistory->Items[i];
if(i==0)
{j=aGraph->nos_vals ; // nos items in trace 0
}
else
{if(j!= aGraph->nos_vals)
{snprintf(errorstr,sizeof(errorstr)-1,"Error on CSV save: lines on chart have different numbers of points (%d vs %d)!",aGraph->nos_vals,j); // sizeof -1 as last character set to null above
ShowMessage(errorstr);
return false;
{snprintf(cstr,sizeof(cstr)-1,"Warning on CSV save: traces on graph have different numbers of points (trace %d has %d while trace 1 has %d) - y values will be interpolated to match 1st trace x values.",i+1,aGraph->nos_vals,j); // sizeof -1 as last character set to null above
ShowMessage(cstr);
// return false; This is no longer a fault as we can interpolate
break; // only show warning message once
}
}
}
// all graphs have the same number of elements - open file
// open file for writing
fp=fopen(filename,"wt");
if(fp==NULL)
{snprintf(errorstr,sizeof(errorstr)-1,"Error on CSV save: cannot create file %s",filename); // sizeof -1 as last character set to null above
ShowMessage(errorstr);
{snprintf(cstr,sizeof(cstr)-1,"Error on CSV save: cannot create file %s",filename); // sizeof -1 as last character set to null above
ShowMessage(cstr);
return false;
}
setvbuf(fp,NULL,_IOFBF,128*1024); // set a reasonably large output buffer, and full buffering
// now write header line for csv file with names for each column
fprintf(fp,"\"%s\"",x_axis_name);
for (i=0; i<iNumberOfGraphs; i++) //for all graphs
Expand All @@ -3756,13 +3766,33 @@ bool TScientificGraph::SaveCSV(char *filename,char *x_axis_name)
// now print out main csv file
xGraph=(SGraph*) pHistory->Items[0];
for (j=0; j<xGraph->nos_vals; j++)
{ fprintf(fp,"%.9g",xGraph->x_vals[j]); // %.9g (9sf) gives max resolution for a float
for (i=0; i<iNumberOfGraphs; i++) //for all graphs
{float xj;
if((j&0x0ffff)==0 && (clock()-lastT)>= CLOCKS_PER_SEC)
{// display progress every second
lastT=clock();
snprintf(cstr,sizeof(cstr),"csv save: %.0f%% complete",100.0*(double)j/(double)(xGraph->nos_vals));
Form1->pPlotWindow->StatusText->Caption=cstr;
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
}
xj= xGraph->x_vals[j];
fprintf(fp,"%.9g",xj); // printf x value first. %.9g (9sf) gives max resolution for a float
for (i=0; i<iNumberOfGraphs; i++) // now print y values for all traces
{aGraph=(SGraph*) pHistory->Items[i];
fprintf(fp,",%.9g",aGraph->y_vals[j]);
if(i==0) fprintf(fp,",%.9g",aGraph->y_vals[j]); // trace 0: can always just print 1st y value as that trace provides x values
else if(aGraph->x_vals[j]==xj)
fprintf(fp,",%.9g",aGraph->y_vals[j]);// if x value matches trace 0 then just print matching y value (this is faster than always interpolating)
else
{// need to interpolate to get correct y value
// float interp1D(float *xa, float *ya, int size, float x, bool clip);
float yj=interp1D(aGraph->x_vals,aGraph->y_vals,aGraph->nos_vals,xj,true);
fprintf(fp,",%.9g",yj); // interpolated value
}
}
fprintf(fp,"\n");
}
fclose(fp);
snprintf(cstr,sizeof(cstr),"csv save: finished in %.1f secs",(clock()-start_t)/(double)CLOCKS_PER_SEC);
Form1->pPlotWindow->StatusText->Caption=cstr;
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
return true; // good exit
}
Loading

0 comments on commit d413156

Please sign in to comment.