Thursday, June 30, 2022

Day 16/100 Split Cat

Day 16/100 Split Cat

Cats are liquid, definitely!


So, I wrote a cat program. For those of you who don't know, "cat" is short for "concatenate", which is a way of saying that you will just jumble everything together. This is such a program. I simply adapted the last program, which was FileGlob, to this one, although I end up deleting most of it, anyway.

So, this is the result:

  for (int i=1;i<argc;i++) {
    if (!strcmp(argv[i],"-")) ifp=stdin;
    else if ((ifp=fopen(argv[i],"r"))==NULL) {
      puts("File open Error"); return 1;
    }
    while (lptr=fgets(Data,MAXSTR,ifp))
      ProcessData();
    if (ifp!=stdin) fclose(ifp);
  }

But it's so easy! Therefore, I added a special option: Split. Actually, this is closer to csplit, which split a file separated by a particular regex. I decided to just split it with a particular file name:

void ProcessData() {
  if (sscanf(Data,"WRITE FILE %s",ofname)) {
    if (ofp) { fclose(ofp); ofp=NULL; }
    ofp=fopen(ofname,"w"); return;
  }
  printf("%s",Data);
  if (ofp) fprintf(ofp,"%s",Data);
}

And, uh, that's it! Nothing to it. Too easy!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXSTR 256
char Data[MAXSTR];
char ofname[MAXSTR];
FILE *ifp;
FILE *ofp=NULL;
void ProcessData();

int Init() {
return 0;
}

int ReadData(int argc, char *argv[]) {
  char *lptr;

  for (int i=1;i<argc;i++) {
    if (!strcmp(argv[i],"-")) ifp=stdin;
    else if ((ifp=fopen(argv[i],"r"))==NULL) {
      puts("File open Error"); return 1;
    }
    while (lptr=fgets(Data,MAXSTR,ifp))
      ProcessData();
    if (ifp!=stdin) fclose(ifp);
  }

  return 0;
}

void ProcessData() {
  if (sscanf(Data,"WRITE FILE %s",ofname)) {
    if (ofp) { fclose(ofp); ofp=NULL; }
    ofp=fopen(ofname,"w"); return;
  }
  printf("%s",Data);
  if (ofp) fprintf(ofp,"%s",Data);
}

void CleanUp() {
  if (ofp) { fclose(ofp); ofp=NULL; }
}

int main (int argc, char *argv[] ) {
  int e=0;

  if (argc<2) {
    puts("cat file1 file2 file3 ...");
    puts("\"WRITE FILE [filename]\" will write to said file");
    return 1;
  }

  Init(argc,argv);
  ReadData(argc,argv);
  CleanUp();

  return e;
}

On that note: I did a function prototype declaration for ProcessData(). I could have just move it to the top, but I decided against it.


Wednesday, June 29, 2022

Day 15/100 FileGlob

 Day 15/100 FileGlob

Data Slurping on the go


I kind of delaying this function somewhat. It's not as easy as it looks. In fact, the code that I have here is at amateur hobbyist level, instead of professional level. However, it does the job just fine. Of course, you have to verify data integrity since the program is somewhat fragile.


The key point here is that I want to read data all in one go. I realize that I'm reading it per character and that's slow, technically speaking, but it's still fast enough for my use. There are 2 ways for data input: 1. Stdin, which is the Linux pipe. 2. File name on the command line.


I want a program that can handle both, and also, not to have to worry too much about allocating enough memory. I still have to watch out for it, but like I said, it's good enough for me.


Here is the data globbing section. I had some trouble because I thought sizeof() gives me the size of the allocated memory. Unfortunately, that's not true. So, I have to keep track of it manually.


    while ((c=fgetc(fp))!=EOF) {
      Data[DataUsed++]=c;
      if (DataUsed>=DataSize) {
        Data=(char *)realloc(Data,(DataSize+CHUNK+1));
        DataSize+=CHUNK;
        printf("DataUsed DataSize %d %d\n",DataUsed,DataSize);
        if (Data==NULL) exit(1);
      }
    }
    Data[DataUsed]='\0';


And I cap the data at the end with a string terminator NULL character. This is because the data is by lines, otherwise, I will simply use the DataUsed variables to check the size of the Data.


Here is the code that parse the data for lines, and assign them to an array of string pointers:


  printf("ShowLines...");
  Lines[LineUsed++]=&Data[0];
  for (i=0;i<DataUsed;i++) 
    if (Data[i]=='\n') {
      Data[i]='\0';
      Lines[LineUsed++]=&Data[i+1];
      if (LineUsed>=MAXLINE) LineUsed--;
    }
  printf("%d\n",LineUsed);
  for (i=0;i<LineUsed;i++) 
    printf(">>%s\n",Lines[i]);


Since I took a shortcut and neglected to dynamically resize the pointer array, I decided to just ignore the last line. I still count the lines, though, just in case. Alternatively, I can also do that using wc program to check the number of lines.


There's really not much to think about, other than keep track of everything, realloc() memory as needed, and not forgetting to free() it at the end of the program. The use of exit() indicates bad flow, and maybe I should use atexit() to clean up the memory.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define CHUNK 65535
#define CHUNK 64
#define MAXLINE 99

char *Data;
int DataSize=0;
int DataUsed=0;
FILE *fp;

char *Lines[MAXLINE];
int  LineUsed=0;

void Init(int argc, char *argv[]) {
  Data=(char *)malloc(CHUNK);\
  DataSize=CHUNK;
}

int ReadData(int argc, char *argv[]) {
  int c;

  for (int i=1;i<argc;i++) {
    puts(argv[i]);
    if (!strcmp(argv[i],"-")) fp=stdin;
    else if ((fp=fopen(argv[i],"r"))==NULL) {
      puts("File open Error");
      return 1;
    }

    while ((c=fgetc(fp))!=EOF) {
      Data[DataUsed++]=c;
      if (DataUsed>=DataSize) {
        Data=(char *)realloc(Data,(DataSize+CHUNK+1));
        DataSize+=CHUNK;
        printf("DataUsed DataSize %d %d\n",DataUsed,DataSize);
        if (Data==NULL) exit(1);
      }
    }
    Data[DataUsed]='\0';

    if (fp!=stdin) fclose(fp);
  }
  return 0;
}

void ProcessData() {
  int i;

  puts(Data);

  printf("ShowLines...");
  Lines[LineUsed++]=&Data[0];
  for (i=0;i<DataUsed;i++) {
    if (Data[i]=='\n') {
      Data[i]='\0';
      Lines[LineUsed++]=&Data[i+1];
      if (LineUsed>=MAXLINE) LineUsed--;
    }
  }
  printf("%d\n",LineUsed);
  for (i=0;i<LineUsed;i++) {
    printf(">>%s\n",Lines[i]);
  }
}

void CleanUp() {
  free(Data);
}

int main (int argc, char *argv[] ) {
  int e=0;

  if (argc<2) {
    puts("fileglob [filename]");
    return 1;
  }

  Init(argc,argv);
  ReadData(argc,argv);
  ProcessData();
  CleanUp();

  return e;
}


One more thing: I did this in a rush, and didn't clean it up. So, think of it as a rough note to be referenced, rather than a polished version.

Tuesday, June 28, 2022

Day 14/100 Turtle Graphic (Simple)

 Day 14/100 Turtle Graphic (Simple)

These turtles are surprisingly fast



It annoys me to no end that people are claiming that computer programming is hard and takes years to do. Even more so when I know for sure, that the programming in question is so easy, I have done it with no direction or guidance in an hour. That remains the longest time I spent with this program. I have done it with SmileBasic 3, on Nintendo 3DS, in as little as 10 minutes! Yes, it's that easy!


Yet, when I suggested that this problem is good for beginners, people resisted. "That's too complicated!" they say, "It involves a lot of math!" What can I say? The only math involved is sin() and cos(), and you don't really need to understand how they work. You just need to understand how to use it, and that's not really difficult at all. Really, the math is so simple, I was doing it (without properly understanding them until much later) when I was 13 years old! How's that for being a "difficult" problem for kids?


I asked as many teachers as I can possibly find to have them teach this, and not only they resisted, some were even felt insulted! I wonder why? Especially noticeable are python users where they insist that importing a turtle graphic library is the only acceptable answer in teaching children how to program this! The only exception is Dan Shiffman, who is an excellent Processing programming language, who featured it in one of his videos. Thanks to him, I have a 'clear' command, that unfortunately does nothing in this case. 


But really, is the problem really that hard? If you think about it, there's only a few variables:

Command, Number, Heading (degree), Distance, Angle(radians), and (x0,y0)-(x1,y1) when drawing a line. Include Pen variable if you want to be able to lift the pen off paper and not draw a line when moving the turtle.


The Commands are: Clear, Forward, TurnLeft, TurnRight, Pen, and Quit. Only six commands! How long can it be? Clear does nothing, so that's done immediately. Quit simply ends the function, so that's easy!


Pen command basically takes the Number variable, 0 for false (don't draw), 1 for true (draw). This can double as color if desired. So, that's easy, too!


TurnLeft and TurnRight is basically Heading-=Number and Heading+=Number. How hard is that? Not hard at all!


All in all, six commands, and I just did 5 of them really quick and easy! How can people say it's hard? I don't know. Let this be a lesson to you: If you working on a problem, don't worry whether it's hard or not, simply because you don't know how hard it is until you solve it, and once you solve it, you know how to solve it, so how can it be hard?


Anyway, here's the core of turtle graphic code:


an=map(0.0,(float)he,360.0,0,2*PI);
tx1=tx0+(float)di*sin(an);
ty1=ty0-(float)di*cos(an);
if (pe) SVGDrawLine(tx0,ty0,tx1,ty1);
tx0=tx1;ty0=ty1;


Not that hard, is it? The first line simply maps degree to radians. The only trigonometric functions used are on the second and third line. And this can be confusing. But it's not your fault!


You see, I'm using 0 degree as to point North, or upwards to 12 o'clock direction. Mathematicians, however, applied the 0 degree direction to point East, or toward 3 o'clock direction! So, there's a disjoint of application there. I'm no mathematician, so I use the layman's convention. You can use either. It's doesn't really matter at the end, since you can just rotate the turtle 90 degrees.


As far as sin() and cos() are concerned, you don't really need to know much more than the curve, and how they map according to distance. The function takes radians, rather annoyingly so, but the math calculations does take radians and converting degree to radians is just a one-liner, so I don't mind it that much.


As long as you can see that sin() goes (0)-(1)-(0)-(-1)-(0) for every 90 degrees or PI/2, then all is well. cos() goes (1)-(0)-(-1)-(0)-(1), or basically just shift sin() 90 degrees. That's not difficult at all.


Next you want to match the x and y axis with sin() and cos(), and it depends on which direction you want 0 degree to point at! Well, you know my choice. So there.


The rest is just taking the scaling the Number to each axis by multiplying it with the values of sin() and cos() for each appropriate axis. Note that in this case, y axis features substraction. This is because in mathematic, the (0,0) origin is at lower left corner whereas in computing, the origin is at upper left corner. If you're confused by this, and I certainly was confused in the beginning, just try it both ways, and you'll see.


I'm sorry to say that I have never seen this code shown anywhere else. The two codes that I have seen aren't like mine. The first one limits the angles to 90 degree increment. The second one does allow arbitrary angle, but forgot to subtract the y values! Or maybe that was intentional, since the person who did it was a mathematicians, and he chose East as his zero.


Once you have the values of x0,y0 and x1,y1 then all you have to do is draw the line. Put a check to see if the pen variable (pe) is non-zero. After that, you update the values of x0,y0 to that of x1,y1.


And that's all there is to it. I hope you will decide to implement this yourself, using any language you wish. Just don't use somebody else's turtle graphic library! Create your own library. Are you a computer programmer, or aren't you?


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MAXENTRY 10000
#define MAXSTR 2560
#define sx0 0
#define sx1 118
#define sy0 0
#define sy1 180
#define PI 3.141528


char Liner[MAXSTR];
char Data[MAXENTRY][MAXSTR];
int numentry;

//turtle
char co; //command
int  nu; //Number
int  he; //heading
int  di; //distance
float  an; //angle (rad)
float  tx0,ty0,tx1,ty1; //line
int  pe; //pen

int LoadData() {
  int num;
  char *buf;
  int i=0;


  for (i=0;buf=fgets(Liner,MAXSTR,stdin);i++) {
    strcpy(Data[i],Liner);
  }
  numentry=i;
  return 0;
}

float map(float x0,float x1, float x2,float y0, float y2) {
  return y0+((x1-x0)*(y2-y0)/(x2-x0)); //y1
}

void SVGDrawLine(float x0,float y0,float x1,float y1) {
  int t;

//  if (y0>y1) { t=y0; y0=y1; y1=t;
//               t=x0; x0=x1; x1=t; }
  printf("<line x1=\"%f\" y1=\"%f.0\" x2=\"%f.0\" y2=\"%f.0\" />\n",
         x0,y0,x1,y1);
}

int ProcessData() {
  int i,j;
  char c;
  int n1,n2;
  char nums[99];

  i=j=0;
  while (1) {
    c=Data[i][j++];
    switch (c) {
      case '\n': //newline
        i++;j=0;
        break;
      case 'c': //clearscreen
        puts("ClearScreen");
        break;
      case 'f': //forward
        strncpy(nums,Data[i]+j,3); j+=3;
        n1=atoi(nums); di=n1;
        printf("Forward %d\n",di);
        an=map(0.0,(float)he,360.0,0,2*PI);
        tx1=tx0+(float)di*sin(an);
        ty1=ty0-(float)di*cos(an);
        if (pe) SVGDrawLine(tx0,ty0,tx1,ty1);
        tx0=tx1;ty0=ty1;
        break;
      case 'l': //turn left
        strncpy(nums,Data[i]+j,3); j+=3;
        n1=atoi(nums); he-=n1;
        printf("Turn Left %d\n",he);
        break;
      case 'r': //turn right
        strncpy(nums,Data[i]+j,3); j+=3;
        n1=atoi(nums); he+=n1;
        printf("Turn Right %d\n",he);
        break;
      case 'p': //pen
        strncpy(nums,Data[i]+j,3); j+=3;
        n1=atoi(nums); pe=n1;
        printf("Pen %d\n",pe);
        break;
      case 'q': //quit
        puts("Quit");
        return 0;
      default:
        break;
    }
  }
  return 0;
}


int Init() {
  co=' ';
  nu=0; //Number
  he=0; //heading
  di=0; //distance
  an=0; //angle (deg)
  tx0=59;ty0=90; //line from
  tx1=tx0;ty1=ty0; //line to
  pe=1; //pen
  return 0;
}

int main (int argc, char *argv[] ) {
  int n,i;


  Init();
  LoadData();
  for (i=0;i<numentry;i++) {
    printf("%d\t%s",i,Data[i]);
  }
  ProcessData();
  return 0;
}




Monday, June 27, 2022

Day 13/100 Stock Trader

 Day 13/100 Stock Trader

3 White Soldiers and 3 Black Crows


Trading stock can be fun and profitable, but you do need to know what you're doing, or else, you will lose all hopes to achieving dreams beyond your avarice. I suppose you know about hi-lo charts as used in the stock market. I use them, too. However, that's not very interesting. Much more interesting, from learning to code point of view, is the candlestick chart.


The wick/tail ends indicates high/low of the day, and the bar indicates the open and close prices of the day. White means closing is higher than open. Black means open is higher than closing. Perfectly simple concept that is really suitable for sightings.


The hi-lo chart is really a single line with 2 branches on either side of the line. It takes just as much space as candlestick, but much, much less visible. Take a look at a sample of the candlestick chart:





Looks like $NFLX is having a bad time lately! Such a shame with all the new shows, too. Anyway, you can easily see the rising prices and the declining prices. 3 white bars in a row are white soldiers, indicating future prosperity. 3 black bars in a row are the black crows, indicating bad times ahead. Unfortunately, I see multiple crows on the chart.


The code to draw the candlestick chart is rather involved, as compared to the previous effort. Fortunately, it's rather easy to do, just time consuming due to the amount of data being processed.


void SVGCandleStick(int n, float o, float h, float l, float c) {
  int by=20;
  float b,t;
  int ay;
  float r;

  o=map(tmin,o,tmax,35,110);
  h=map(tmin,h,tmax,35,110);
  l=map(tmin,l,tmax,35,110);
  c=map(tmin,c,tmax,35,110);

  ay=by+(n*3);
  b=(o>c)?c:o;
  t=(o>c)?o:c;

  SVGDrawLine(30,ay+1,33,ay+1);

  SVGDrawLine(l,ay+1,b,ay+1);
  SVGDrawLine(t,ay+1,h,ay+1);
  SVGDrawLine(b,ay,t,ay);
  SVGDrawLine(b,ay+2,t,ay+2);
  SVGDrawLine(b,ay,b,ay+2);
  SVGDrawLine(t,ay,t,ay+2);
  if (o>c)
    for (r=0.0;r<=2.0;r+=0.25) {
      SVGDrawLine(b,ay+r,t,ay+r);
    }

}


The first part of the function is to scale the chart appropriately. I simply use millimeters for measuring since it's such a convenient scale. Using inches will be even more time consuming!


Then I draw the marks on the horizontal axis. This is followed by the candlestick drawing itself. 2 lines for top and bottom and 4 lines for the candlestick itself. Finally, if open is greater than closing, fill up the box with black.


It's more complicated than just drawing a rectangle because the direction of the line drawn matters. The output isn't the screen. Well, it does go to the screen, but I also want to make the output to be compatible with a plotter. That's why I purposely set the direction to draw to the higher X axis direction, never to the opposite direction.


This will be more obvious on the next project. However, I will stop here for now. As it is, I simply didn't bother cleaning up the program. Most likely, I will need to visit this program later. Once I set up graphical user interface, that is.



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define MAXSTR 256
#define MAXENTRY 10000

int Debug=0;
float min=5.0;
float max=20000.0;
int sca=20;
char scan[MAXSTR];
int tstamp=0;
int dir=0;
float val=-1;
int slot,lslot;

char Date[MAXENTRY][MAXSTR];
float Open[MAXENTRY];
float High[MAXENTRY];
float Low[MAXENTRY];
float Close[MAXENTRY];
float Adj[MAXENTRY];
float Volume[MAXENTRY];
float tmin=20000.0;
float tmax=0.0;
int numentry=0;

void ShowMsg() {
puts("
Usage: pnf [OPTION]...
Display Point and Figure chart given a stock.csv file.
Min and Max values are not automatically adjusted!
Default values are:
      -nN      minimum value. Default N0
      -xN      maximum value. Default N200
      -sN      scales. Default N20
      --help     display this help and exit
      --debug    debug info
");
}

int GetOpt(int argc, char *argv[]) {
int i;
  for (i=1;i<argc;i++) {
    if (!strncmp("-n",argv[i],2)) min=atof(argv[i]+2);
    if (!strncmp("-x",argv[i],2)) max=atof(argv[i]+2);
    if (!strncmp("-s",argv[i],2)) sca=atoi(argv[i]+2);
    if (!strncmp("--help",argv[i],6)) {
      ShowMsg(); return 0;
    }
    if (!strncmp("--debug",argv[i],7)) Debug=1;
  }

  if (Debug) {
    puts("Debug...");
    printf("Min: %0.2f\n",min);
    printf("Max: %0.2f\n",max);
    printf("Scale: %d\n",sca);
  }
  return 0;
}


float map(float x0,float x1, float x2,float y0, float y2) {
  return y0+((x1-x0)*(y2-y0)/(x2-x0)); //y1
}

void SVGDrawLine(float x0,float y0,float x1,float y1) {
  int t;
  if (y0>y1) { t=y0; y0=y1; y1=t;
               t=x0; x0=x1; x1=t; }
  printf("<line x1=\"%.2f\" y1=\"%.2f\" x2=\"%.2f\" y2=\"%.2f\" />\n",
         x0,y0,x1,y1);
}


void SVGDrawHeader() {
  SVGDrawLine(30,16,114,16);
  SVGDrawLine(30,16,30,177);
}

void SVGCandleStick(int n, float o, float h, float l, float c) {
  int by=20;
  float b,t;
  int ay;
  float r;

  o=map(tmin,o,tmax,35,110);
  h=map(tmin,h,tmax,35,110);
  l=map(tmin,l,tmax,35,110);
  c=map(tmin,c,tmax,35,110);

  ay=by+(n*3);
  b=(o>c)?c:o;
  t=(o>c)?o:c;

  SVGDrawLine(30,ay+1,33,ay+1);

  SVGDrawLine(l,ay+1,b,ay+1);
  SVGDrawLine(t,ay+1,h,ay+1);
  SVGDrawLine(b,ay,t,ay);
  SVGDrawLine(b,ay+2,t,ay+2);
  SVGDrawLine(b,ay,b,ay+2);
  SVGDrawLine(t,ay,t,ay+2);
  if (o>c)
    for (r=0.0;r<=2.0;r+=0.25) {
      SVGDrawLine(b,ay+r,t,ay+r);
    }
}



char *CSV2TSV(char *in) {
  for (;*in;in++) if (*in==',') *in=' ';
  return in;
}

int LoadData() {
  char line[MAXSTR];

  numentry=0;
  while (gets(line)!=NULL) {
    CSV2TSV(line);
    sscanf(line,"%s %f %f %f %f %f %f"
      ,Date[numentry],&Open[numentry],&High[numentry]
      ,&Low[numentry],&Close[numentry],&Adj[numentry]
      ,&Volume[numentry]);

    if (tmin>Low[numentry]) tmin=Low[numentry];
    if (tmax<High[numentry]) tmax=High[numentry];
    puts(line);
    numentry++;
  }

  return 0;
}

int ShowData() {
  int i,j;

  SVGDrawHeader();

  for (i=numentry-50,j=i;i<numentry;i++) {
    printf("%s\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\n"
      ,Date[i],Open[i],High[i]
      ,Low[i],Close[i],Adj[i]
      ,Volume[i]);

    SVGCandleStick(i-j,Open[i],High[i],Low[i],Close[i]);
    if (tmin>Low[i]) puts("Error: Low minimum");
  }
  printf("Min %.2f \t Max %.2f\n",tmin,tmax);
  return 0;
}

int main (int argc, char *argv[] ) {
  int e=0;
  char line[MAXSTR];

  GetOpt(argc, argv);
  if (gets(line)!=NULL) { //Remove header
    e=LoadData();
    if (!e) ShowData();
  }
  return e;
}





Saturday, June 25, 2022

Day 12/100 Graphic with SVG

 Day 12/100 Graphic with SVG

It makes a fine line!


So far I have been doing graphic using text only console character, and although it works, it's not really suitable for fine graphics. Of course, having only 80x25 grid doesn't help matters. But what if there's a way to do an ultra high resolution graphic, and do it via text terminal only programs? Turns out there is such a way, albeit not directly. Introducing Scalable Vector Graphic (SVG)!


The tutorial that is out there somewhat skimps on the installation process, and proper formatting. I had trouble setting things up. It's not until I finally gave in and installed Inkscape, that I figured out what's going on. The trick is that I play around with the various settings that Inkscape has, and there are several options to do. I chose the simplest, and go from there.


The SVG header has viewBox, resolution, and size. There's also default line and fill specification. So far, I mostly do monochrome graphics. Lines and rectangles, so to speak. But I checked out the specification, and it has curves as well, and that is something I'm interested in for future exercises.


The point is that you can do really nice graphic using SVG. Check out these pictures:


Line Graph:



Bar Graph:




Right now, I don't have a vector based font handy. Sure there is a True Type Font converter, but I'd rather make my own font. After all, this is all for learning purposes. Here is the actual, relevant SVG code, which I later inserted into the SVG framework file.


void SVGDrawLine(int x0,int y0,int x1,int y1) {
  int t;
  printf("<line x1=\"%d.0\" y1=\"%d.0\" x2=\"%d.0\" y2=\"%d.0\" />\n",
         x0,y0,x1,y1);
}

void SVGDrawRect(int x0,int y0,int x1,int y1) {
  printf("<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" />\n",
         x0,y0,(x1-x0),(y1-y0));
}


That's all there is to it. It's not something mysterious at all. If you can do XML, then you can do SVG. All that's left is to fill in the blanks, so to speak. I generated the SVG instructions by modifying my old stock/frequency analysis program. The output is intermingled with ASCII graphic. So, I have to filter them out. I simply use GREP command for that. It's a good thing Raspberry Pi is set up for learning computer programming rather well. I really enjoy using it to create computer programs.


Here is the SVG template that I use. Don't ask me what all this stuff do because I don't know! The most important bit is the one in the middle: width, height, and viewBox. I use millimeter because it's more precise than inches, and I don't have to use floating points so much. I inserted the SVG output between the <g> </g> markups, just below the 4 line commands. 


<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.0//EN'
'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd'>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" 
style="fill-opacity:1; color-rendering:auto; 
color-interpolation:auto; text-rendering:auto; 
stroke:black; stroke-linecap:square; 
stroke-miterlimit:10; shape-rendering:auto; 
stroke-opacity:0.4; fill:black; 
stroke-dasharray:none; font-weight:normal; 
stroke-width:1; font-family:'Dialog'; font-style:normal; 
stroke-linejoin:miter; font-size:12px; 
stroke-dashoffset:0; image-rendering:auto;" 

width="128.0mm" height="190.0mm" 
viewBox="0 0 128.0 190.0" 

xmlns="http://www.w3.org/2000/svg">
<!--Template generated by the Batik Graphics2D SVG Generator-->
<defs id="genericDefs" />

<g>
<line x1="10.0" y1="10.0" x2="118.0" y2="10.0" />
<line x1="118.0" y1="10.0" x2="118.0" y2="180.0" />
<line x1="118.0" y1="180.0" x2="10.0" y2="180.0" />
<line x1="10.0" y1="180.0" x2="10.0" y2="10.0" />
</g>
</svg>


This is just beginning of my journey to do computer graphic using C programming language. Would you believe that there's nobody who can answer the extremely simple question of "How do I draw a dot on screen using C?" I mean, there's tremendous amount of computer graphic libraries, but if you just want to draw a dot on screen, then you're just plain out of luck! Unbelievable!


Anyway, enough rambling around. The somewhat confusing and raw experiment using the old, traditional hack-and-slash method is below. That's not a clean software engineering, but like I said, this is only the beginning. Technological research does tend to be messy, since by definition, you don't know what you're doing! That's right. Learning new things involves having your ignorance exposed, at least for a little while. Later on, as you progress, you can refine things better. For now, though, it's just raw.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXENTRY 10000
#define MAXSTR 2560
#define sx0 28
#define sx1 108
#define sy0 20
#define sy1 170
#define cwidth 40.0

char Liner[MAXSTR];
int Data[MAXENTRY];
int minEntry=MAXENTRY;
int maxEntry=0;
int minValue=MAXENTRY;
int maxValue=0;

int LoadData() {
  int num;
  char *buf;
  int i=0;
  while (buf=fgets(Liner,MAXSTR,stdin)) {
    num=atoi(buf); Data[i]=num;
    if (minEntry>i) minEntry=i;
    if (maxEntry<i) maxEntry=i;
    i++;
  }
  return 0;
}


float map(float x0,float x1, float x2,float y0, float y2) {
  return (x1-x0)*(y2-y0)/(x2-x0); //y1
}

void SVGDrawLine(int x0,int y0,int x1,int y1) {
  int t;
  printf("<line x1=\"%d.0\" y1=\"%d.0\" x2=\"%d.0\" y2=\"%d.0\" />\n",
         x0,y0,x1,y1);
}

void SVGDrawRect(int x0,int y0,int x1,int y1) {
  printf("<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" />\n",
         x0,y0,(x1-x0),(y1-y0));
}

void DrawHLine(int start, int stop, char mark) {
  int i;
  if (start>stop) { i=start; start=stop; stop=i; }
  for (i=start;i<=stop;i++) putchar(mark);
}

void DrawHeader() {
  printf("\n\n\n");
  DrawHLine(0,5,' ');
  printf("%5d",minValue);
  DrawHLine(15,(72-5),'-');
  printf("%5d\n",maxValue);
}

void GraphBar(char LineMark) {
  int i,j;
  int spacer;
  DrawHeader();
  for (i=minEntry;i<=maxEntry;i++) {
    printf("%4d%5d  ",i,Data[i]);
    spacer=(int)map((float)minValue,
      (float)Data[i],(float)maxValue,0.0,cwidth);
    DrawHLine(0,spacer,'X'); putchar('\n');
    SVGDrawRect(
                sx0,
                sy0+(i*(sy1-sy0)/(maxEntry-minEntry)),
                sx0+(int)((float)spacer*2.0),
                (sy0+2)+(i*(sy1-sy0)/(maxEntry-minEntry))
      );
  }
}

void GraphLine(char LineMark) {
  int i,j;
  int spacer;
  int g0,g1;
  int l0,l1;
  DrawHeader();
  for (i=minEntry;i<=maxEntry;i++) {
    if (i==minEntry)
      g0=(int)map((float)minValue,
         (float)Data[i],(float)maxValue,0.0,cwidth);
    g1=(int)map((float)minValue,
       (float)Data[i],(float)maxValue,0.0,cwidth);
    printf("%4d%5d  ",i,g1); l0=g0; l1=g1;
    if (l0>l1) { j=l0;l0=l1;l1=j; }
    DrawHLine(0,l0,' '); DrawHLine(l0,l1,'+');
    putchar('\n');
    if (i>minEntry) SVGDrawLine(
                sx0+(g0*2),
                sy0+((i-1)*(sy1-sy0)/(maxEntry-minEntry)),
                sx0+(g1*2),
                sy0+((i)*(sy1-sy0)/(maxEntry-minEntry))
      );
    g0=g1;
  }
}

int main (int argc, char *argv[] ) {
  int n,i;
  for (i=0;i<MAXENTRY;i++) Data[i]=0;
  LoadData();
  for (i=minEntry;i<=maxEntry;i++) {
    if (minValue>Data[i]) minValue=Data[i];
    if (maxValue<Data[i]) maxValue=Data[i];
    printf("%d\t%d\n",i,Data[i]);
  }
  printf("\n");
  printf("Entry: %d\t%d\n",minEntry,maxEntry);
  printf("Value: %d\t%d\n",minValue,maxValue);
  GraphBar('X');
  GraphLine('+');
  return 0;
}


One more thing: I'm using C for this, but if you're really into Graphic, then I suggest Learning Processing. Dan Shiffman is great at that. You should seek him out!

Friday, June 24, 2022

Day 11/100 Frequency Graph

 Day 11/100 Frequency Graph

Picturing the most likely occurrences


So, I mentioned about doing frequency analysis for the last program, which is Happy Number. I somewhat made it like it's not a trivial thing to do. However, that is not true at all! It is trivial to count occurrences and use an array to do that. You don't even need hash to do it. Just a simple array will do!


int LoadData() {
  int num;
  char *buf;
  while (buf=fgets(Liner,MAXSTR,stdin)) {
    num=atoi(buf); Data[num]++;
    if (minEntry>num) minEntry=num;
    if (maxEntry<num) maxEntry=num;
  }
  return 0;
}


And that's it! It just doesn't get any easier than that! So, the hour of code is still on-going. What do I do with the rest of the time? Trying to make the program into some eye-catching form, of course. That means graphic.


Now, all this time I've been doing, I've been doing it text console. So, certainly there's no graphic screen involved. But not really. If you look at my Point and Figure chart program, you see graphic! Very crude, I admit, but graphic! So, what kind of other graphic can I do with a text screen? 


There are two kinds of easy chart that comes to mind: Bar chart, and Line chart. Both are easy to do. In fact, some of the early program actually charted Biorhythm. That's graphic. As per usual, I chart it sideways as to allow numerous entries. The range of the charts is fixed, but the number of entries aren't. Therefore, I let the screen scroll to as many entries as available.


The difference between the Line chart and the Bar chart is that the Line chart is only a single character, while the Bar chart is filled with graphing character. That's easy enough to do where the programming language allows you to multiply a character and come up with a string with that number of character. C doesn't have that, so I simply made my own. 


Here is the code for the Bar chart. As you can see, I use a lot of supporting functions. This would be included in libraries, had I use a more sophisticated platform. But I don't, and I don't mind. They're easy enough to do by myself.


void GraphBar(char LineMark) {
  int i,j;
  int spacer;
  DrawHeader();
  for (i=minEntry;i<=maxEntry;i++) {
    printf("%4d%5d  ",i,Data[i]);
    spacer=(int)map((float)minValue
      ,(float)Data[i],(float)maxValue
      ,0.0,72.0);
    DrawHLine(0,spacer,'X'); putchar('\n');
  }
}


As you can see, I use a function called DrawHLine() to draw horizontal line, from start to stop, with a particular character. That's easy enough to do. The current demand for graphic can be easily met just by using a horizontal drawing technique.


And here is the Line chart. As you can see, the drawing routine is slighty more complex.


void GraphLine(char LineMark) {
  int i,j; int spacer; int g0,g1; int l0,l1;
  DrawHeader();
  for (i=minEntry;i<=maxEntry;i++) {
    if (i==minEntry)
      g0=(int)map((float)minValue,
         (float)Data[i],(float)maxValue,0.0,72.0);
    g1=(int)map((float)minValue,(float)Data[i],
       (float)maxValue,0.0,72.0);
    printf("%4d%5d  ",i,g1); l0=g0; l1=g1;
    if (l0>l1) { j=l0;l0=l1;l1=j; }
    DrawHLine(0,l0,' '); DrawHLine(l0,l1,'+');
    putchar('\n'); g0=g1;
  }
}


The most complicated item in the function is map(). That function basically translate a range into another range. This is very useful in case you want to convert different values such as from meter to kilometer, Fahrenheit to Celcius, or degree to radians. Here, I'm using it to scale the data to fit the screen. It uses a lot of parameters, but conceptually, it's rather simple.


Another thing to consider is that I use different variables, g0-g1 and l0-l1. I totally forgot to separate them, but hey, nobody's perfect! Once I use the variables to separate the graph values and the line drawing values, everything works as is.


And that's about it. Some programming problems are so simple, I wonder why people keep saying that computer programming is hard. It really depends on the problem, you know?


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXENTRY 10000
#define MAXSTR 2560


char Liner[MAXSTR];
int Data[MAXENTRY];
int minEntry=MAXENTRY;
int maxEntry=0;
int minValue=MAXENTRY;
int maxValue=0;

int LoadData() {
  int num;
  char *buf;

  while (buf=fgets(Liner,MAXSTR,stdin)) {
    num=atoi(buf); Data[num]++;
    if (minEntry>num) minEntry=num;
    if (maxEntry<num) maxEntry=num;
  }
  return 0;
}

float map(float x0,float x1, float x2,float y0, float y2) {
  float y1;
  y1=(x1-x0)*(y2-y0)/(x2-x0);
  return y1;
}


void DrawHLine(int start, int stop, char mark) {
  int i;
  if (start>stop) { i=start; start=stop; stop=i; }
  for (i=start;i<=stop;i++) putchar(mark);
}

void DrawHeader() {
  printf("\n\n\n");
  DrawHLine(0,5,' ');
  printf("%5d",minValue);
  DrawHLine(15,(72-5),'-');
  printf("%5d\n",maxValue);
}

void GraphBar(char LineMark) {
  int i,j;
  int spacer;

  DrawHeader();
  for (i=minEntry;i<=maxEntry;i++) {
    printf("%4d%5d  ",i,Data[i]);
    spacer=(int)map(
      (float)minValue,(float)Data[i],(float)maxValue
      ,0.0,72.0);
    DrawHLine(0,spacer,'X'); putchar('\n');
  }
}

void GraphLine(char LineMark) {
  int i,j;
  int spacer;
  int g0,g1;
  int l0,l1;

  DrawHeader();
  for (i=minEntry;i<=maxEntry;i++) {
    if (i==minEntry)
      g0=(int)map(
      (float)minValue,(float)Data[i],(float)maxValue
      ,0.0,72.0);
    g1=(int)map(
      (float)minValue,(float)Data[i],(float)maxValue
      ,0.0,72.0);
    printf("%4d%5d  ",i,g1);
    l0=g0; l1=g1;
    if (l0>l1) { j=l0;l0=l1;l1=j; }
    DrawHLine(0,l0,' ');
    DrawHLine(l0,l1,'+');
    putchar('\n');
    g0=g1;
  }
}

int main (int argc, char *argv[] ) {
  int n,i;

  for (i=0;i<MAXENTRY;i++) Data[i]=0;
  LoadData();
  for (i=minEntry;i<=maxEntry;i++) {
    if (minValue>Data[i]) minValue=Data[i];
    if (maxValue<Data[i]) maxValue=Data[i];
    printf("%d\t%d\n",i,Data[i]);
  }
  printf("\n");
  printf("Entry: %d\t%d\n",minEntry,maxEntry);
  printf("Value: %d\t%d\n",minValue,maxValue);

  GraphBar('X');
  GraphLine('+');
  return 0;
}


One more thing: The program above is customized for the data given. You need to change it a bit to deal with generalized form of data. And if somebody tells you that doing computer graphic in a character terminal is impossible, DON'T BELIEVE IT!