chart library, kind of

Code snippets illustrating how LBB can be used
tsh73
Posts: 44
Joined: Fri Apr 06, 2018 7:58 pm

chart library, kind of

Post by tsh73 »

Chart library
started with JB long long time ago
made work with LB (today)
made work with LBB (kind of)
differences encountered:
*in LBB, handle variables with a dot ("topName.handle$") not supported
*in LBB, overfill of USING produces "???" instead of %123.4

Code: Select all

print using ("##.#", 12.3)
print using ("##.#", -12.3)
LBB

Code: Select all

????
LB

Code: Select all

%-12.3

Code: Select all

'chart library kind-of-header 
'(that is, should be included to top)
'====================================================
global Chart.xmin, Chart.xmax, Chart.ymin, Chart.ymax
global Chart.winW, Chart.winH
global Chart.slackX, Chart.slackY
global Chart.nPoints, Chart.nSeries, Charthandle$
global Chart.windowTitle$
global Chart.colors$, Chart.widths$
dim Chart.data(1,1)
'-----------------------------------------------------
'include chart.hb to the top
'include chart.lb at the end

'Chart library demo code

data "Hello there"
data "This is Chart library demo code"
data "It tries to show what this library can and how to use it"
data "PAUSE"
gosub [showMsg]

data "So. This library allows to use from a mainwin programs"
data "You call a function, program opens modal window"
data "Your program blocked till your close window"
data "(so at once you can have only one chart window)"
data "(Left drag shows math coords in a textbox - you can copy and use them"
data ""
data "Now, the easiest thing:"
data "sub chartByFunc f$,xmin,xmax"
data "call chartByFunc '2*cos(x)-cos(2*x)',-3,5"
data "Of course it uses EVAL, so it needs LB"
data "(Close window to continue)"
data "NEXT"
gosub [showMsg]
call chartByFunc "2*cos(x)-cos(2*x)",-3,5

data "You can change graph color by setting Chart.colors$"
data "	Chart.colors$ = 'red'"
data "NEXT"
gosub [showMsg]
Chart.colors$ = "red"
call chartByFunc "2*cos(x)-cos(2*x)",-3,5

data "You can change number of points by setting Chart.nPoints"
data "default is 300 (1000 for parametric function)"
data "	Chart.nPoints = 15"
data "NEXT"
gosub [showMsg]
Chart.nPoints = 15
call chartByFunc "2*cos(x)-cos(2*x)",-3,5

data "You can change chart title by setting Chart.windowTitle$"
data "	Chart.windowTitle$ = 'Chart test'"
data "NEXT"
gosub [showMsg]
Chart.nPoints = 0 'reset to default
Chart.windowTitle$ = "Chart test"
call chartByFunc "2*cos(x)-cos(2*x)",-3,5

data "You can change window size by ordinary LB means"
data "WindowWidth, WindowHeight" 
data "	WindowWidth = 500"
data "	WindowHeight = 500"
data "(position of modal windows is tricky thing)"
data "NEXT"
gosub [showMsg]
WindowWidth = 500
WindowHeight = 500
call chartByFunc "2*cos(x)-cos(2*x)",-3,5

data "Did I said 'Parametric'?"
data "  parametricChartByFunc x$,y$,tmin,tmax" 
data "it uses two functions for x(parameter) and y(parameter)"
data "(sorry both functions are defined as functions of (x))"
data "NEXT"
gosub [showMsg]
call parametricChartByFunc "2*cos(x)-cos(2*x)", "2*sin(x)-sin(2*x)", 0, 6.28

data "Now, that was easy part"
data ""
data "But chartByFunc/parametricChartByFunc bas 'ByArray' counterpart"
data "Of course that means you have to fill that array"
data "BUT"
data "you can draw several lines on same chart that way"
data "PAUSE"
gosub [showMsg]

data "So for ordinary functions:"
data "set Chart.nSeries"
data "set Chart.nPoints"
data "redim Chart.data(Chart.nSeries, Chart.nPoints)"
data "fill Chart.data(,) array"
data "   (col0 for X, col1 etc to Y1 etc)"
data "then call chartByArray"
data "NEXT"
gosub [showMsg]

WindowWidth = 500
WindowHeight = 300
Chart.nSeries = 3    'for ordinary graphs, that will be
    'col0 for X, col1 etc to Y1 etc
Chart.colors$ = ""	'reset colors to default
Chart.nPoints = 100
redim Chart.data(Chart.nSeries, Chart.nPoints)
xmin=-2: xmax=3
Chart.xmin = xmin
Chart.xmax = xmax
for i = 1 to Chart.nPoints
    x = i*(Chart.xmax-Chart.xmin)/Chart.nPoints+Chart.xmin
    Chart.data(0,i)=x
    Chart.data(1,i)=sin(x)
    Chart.data(2,i)=2*cos(3*x)
    Chart.data(3,i)=cos(1/(x+.0001))' small offset so not to divide by 0
next

call chartByArray


data "Remember Chart.colors$? "
data "You can set several colors - for different lines"
data "and even change line widths with Chart.widths$"
data "	Chart.colors$ = 'pink darkgreen cyan'"
data "	Chart.widths$='3 2 1'"
data "NEXT"
gosub [showMsg]

Chart.colors$ = "pink darkgreen cyan "
Chart.widths$="3 2 1"
call chartByArray

Chart.widths$=""	'reset to default
Chart.colors$ = ""	'reset to default

data "For several parametric plots:"
data "redim Chart.data(Chart.nSeries*2, Chart.nPoints)"
data "fill Chart.data(,) array"
data "  (col1 for X1, col2 etc to Y1, col3 for X2, col4 etc to Y2 etc)"
data "then call parametricChartByArray"
data "NEXT"
gosub [showMsg]
Chart.nSeries = 2 
Chart.nPoints = 1000	'need defined for REDIM
redim Chart.data(Chart.nSeries*2, Chart.nPoints)
for i = 1 to Chart.nPoints
    t = 2*355/113 /Chart.nPoints*i
    Chart.data(1,i)=sin(t)
    Chart.data(2,i)=cos(t)    'first plot will be a circle
    t = 3/Chart.nPoints*i
    Chart.data(3,i)=t*sin(t*3)
    Chart.data(4,i)=t*cos(t*3)    'second - spiral
    'print i,Chart.data(0,i),Chart.data(1,i),Chart.data(2,i)
next

call parametricChartByArray

data "Last thing"
data "Functions we tried just opened dialog and waited there"
data "But there is a functions that draw graph, (FLUSH) and RETURN immediately"
data "they are ended by 'InHandle', like"
data "	chartByFuncInHandle"
data "	parametricChartByFuncInHandle"
data "They need graphic handle passed"
data "You will not get point tracking but you can use it in your GUI"
data "draw/redraw as needed"
data "(and may be several ones)"
data "See: we draw a graph, and after 3 sec change it to another one"
data "PAUSE"
gosub [showMsg]

'Now, just draw in a given handle
'function returns immediately
graphicbox #main.gr1 1, 1, 200, 200
button #main.btn1 "just button", [btnTest1], UL, 210, 1
open "Chart in a GUI" for window as #main
#main "trapclose [quit]"
    'call chartByArrayInHandle "#main.gr1"
    Chart.nPoints = 20
    call parametricChartByFuncInHandle "#main.gr1", "2*cos(x)-cos(2*x)", "2*sin(x)-sin(2*x)", 0, 6.28
    Chart.nPoints = 0
    timer 3000, [timeUp1]
wait
[timeUp1]
    timer 0
    #main.gr1 "cls"
	f$="1.5*x^2-2*sin(5*x)"
    call chartByFuncInHandle "#main.gr1", f$,xmin,xmax
wait

[btnTest1]
    notice "just button pressed"
    wait

[quit]     
    timer 0
    close #main
print "*** End of demo ***"
    end

end


data ""
data ""
data ""
data ""
data "NEXT"

print 
print "*** End of demo ***"
end

[showMsg]
    msg$ = ""
    while msg$<>"NEXT" and msg$<>"PAUSE" 
        read msg$
        if msg$<>"NEXT" and msg$<>"PAUSE" then print repl39to34$(msg$)
    wend
    print 
    if msg$<>"NEXT" then input "(Press Enter to continue)"; dummy$
return

function repl39to34$(s$)	' (') to (")
	for i = 1 to len(s$)
		c$=mid$(s$,i,1)
		if c$="'" then c$=chr$(34)
		repl39to34$=repl39to34$+c$
	next
end function
'>>include chart.lb at the end
'chart library body
'to be included after main program
'(together with header included at top)

'-----------------------------------------------------
sub parametricChartByArrayInHandle handle$
'this sub RETURNS AFTER DRAWING
    Charthandle$ = handle$
    #Charthandle$ "down"
    'get a chart size
    #Charthandle$ "home"
    #Charthandle$ "posxy w h"
    Chart.winW=2*w: Chart.winH=2*h

    'now, to get Chart.ymin, Chart.ymax we have to loop
    Chart.ymin=Chart.data(2,1)
    Chart.ymax=Chart.ymin
    for s = 1 to Chart.nSeries
        for i = 1 to Chart.nPoints
            y=Chart.data(s*2,i)
            if Chart.ymin > y then Chart.ymin = y
            if Chart.ymax < y then Chart.ymax = y
        next
    next
    'for parametrics, xmax xmin should be calculated too
    Chart.xmin=Chart.data(1,1)
    Chart.xmax=Chart.xmin
    for s = 1 to Chart.nSeries
        for i = 1 to Chart.nPoints
            x=Chart.data(s*2-1,i)
            if Chart.xmin > x then Chart.xmin = x
            if Chart.xmax < x then Chart.xmax = x
        next
    next

    colorList$ = Chart.colors$
    if colorList$="" then colorList$ = "blue green red cyan pink yellow black lightgray darkblue darkgreen darkred darkcyan darkpink brown darkgray"
'add axis
    call drawAxes
    call drawTicks
    'and labeling
    ', before graphs (so then came over text)
    call drawLabels

    'now we just - plot function
    'for parametric graphs, that will be
    'col1 for X1, col2 etc to Y1, col3 for X2, col4 etc to Y2 etc
    for s = 1 to Chart.nSeries
        colr$=word$(colorList$, s)
        if colr$="" then colr$= "black"
        #Charthandle$ "color ";colr$
        if Chart.widths$<>"" then 
            w$ = word$(Chart.widths$, s)
            if w$<>""then #Charthandle$ "size ";w$
        end if        
        x=Chart.data(s*2-1,1)
        y=Chart.data(s*2,1)
        #Charthandle$ "set ";sx(x);" ";sy(y)  'just set first dot
        for i = 1 to Chart.nPoints
            x=Chart.data(s*2-1,i)
            y=Chart.data(s*2,i)
            #Charthandle$ "goto ";sx(x);" ";sy(y)  'then connect dots
        next
    next

    #Charthandle$ "flush"
end sub

sub chartByArrayInHandle handle$
'this sub RETURNS AFTER DRAWING
    Charthandle$ = handle$
    #Charthandle$ "down"
    'get a chart size
    #Charthandle$ "home"
    #Charthandle$ "posxy w h"
    Chart.winW=2*w: Chart.winH=2*h

    'now, to get Chart.ymin, Chart.ymax we have to loop
    Chart.ymin=Chart.data(1,1)
    Chart.ymax=Chart.ymin
    for s = 1 to Chart.nSeries
        for i = 1 to Chart.nPoints
            y=Chart.data(s,i)
            if Chart.ymin > y then Chart.ymin = y
            if Chart.ymax < y then Chart.ymax = y
        next
    next

    colorList$ = Chart.colors$
    if colorList$="" then colorList$ = "blue green red cyan pink yellow black lightgray darkblue darkgreen darkred darkcyan darkpink brown darkgray"
    'add axis
    call drawAxes
    call drawTicks

    'and labeling
    ', before graphs (so then came over text)
    call drawLabels
    

    'now we just - plot function
    'for ordinary graphs, that will be
    'col0 for X, col1 etc to Y1 etc
    for s = 1 to Chart.nSeries
        colr$=word$(colorList$, s)
        if colr$="" then colr$= "black"
        #Charthandle$ "color ";colr$
        if Chart.widths$<>"" then 
            w$ = word$(Chart.widths$, s)
            if w$<>""then #Charthandle$ "size ";w$
        end if        
        y=Chart.data(s,1)
        #Charthandle$ "set ";sx(Chart.xmin);" ";sy(y)  'just set first dot
        for i = 1 to Chart.nPoints
            x=Chart.data(0,i)
            y=Chart.data(s,i)
            #Charthandle$ "goto ";sx(x);" ";sy(y)  'then connect dots
        next
    next

    #Charthandle$ "flush"
end sub

sub parametricChartByArray
'this sub OPENS MODAL WINDOW and STAYS UNTIL IT CLOSED
	call genericChartByArray 1
end sub

sub chartByArray
'this sub OPENS MODAL WINDOW and STAYS UNTIL IT CLOSED
	call genericChartByArray 0
end sub

sub genericChartByArray isParametric
	oldWindowWidth = WindowWidth
	oldWindowHeight = WindowHeight
	gosub [getSlack]
	WindowWidth=oldWindowWidth:WindowHeight=oldWindowHeight
	textbox #chart.track, 1, 1, WindowWidth-Chart.slackX, 25
    graphicbox #chart.gr, 1, 25, WindowWidth-Chart.slackX, WindowHeight-Chart.slackY-25
    title$=Chart.windowTitle$
    if title$="" then title$="(modal) Chart window"
    open title$ for dialog_modal as #chart
    #chart "trapclose [quitChart]"
    #chart.track "left mouse button move for tracking"
    if isParametric then
	    call parametricChartByArrayInHandle "#chart.gr"
	else
		call chartByArrayInHandle "#chart.gr"
	end if
    #chart.gr "when leftButtonMove [track]"
wait

[track]
	#chart.track ex(MouseX); " "; ey(MouseY)
wait

[getSlack]
    if Chart.slackX <>0 then return
    WindowWidth=200:WindowHeight=200
    open "" for graphics_nsb as #t:#t,"home;posxy x y":close#t
    Chart.slackX=WindowWidth-2*x:Chart.slackY=WindowHeight-2*y
return

[quitChart]
    close #chart
end sub

sub parametricChartByFunc x$,y$,tmin,tmax
'this sub OPENS MODAL WINDOW and STAYS UNTIL IT CLOSED
    nPointsWasEmpty = (Chart.nPoints=0)
    if nPointsWasEmpty then Chart.nPoints=1000 'default
    call fillArrForParametricFunc x$,y$,tmin,tmax
    call parametricChartByArray
    if nPointsWasEmpty then Chart.nPoints=0 'reset
end sub

sub parametricChartByFuncInHandle handle$, x$,y$,tmin,tmax
'this sub RETURNS AFTER DRAWING
    nPointsWasEmpty = (Chart.nPoints=0)
    if nPointsWasEmpty then Chart.nPoints=1000 'default
    call fillArrForParametricFunc x$,y$,tmin,tmax
    call parametricChartByArrayInHandle handle$
    if nPointsWasEmpty then Chart.nPoints=0 'reset
end sub

sub chartByFunc f$,xmin,xmax
'this sub OPENS MODAL WINDOW and STAYS UNTIL IT CLOSED
    nPointsWasEmpty = (Chart.nPoints=0)
    if nPointsWasEmpty then Chart.nPoints=300 'default
    call fillArrForFunc f$,xmin,xmax
    call chartByArray
    if nPointsWasEmpty then Chart.nPoints=0 'reset
end sub

sub chartByFuncInHandle handle$, f$,xmin,xmax
'this sub RETURNS AFTER DRAWING
     nPointsWasEmpty = (Chart.nPoints=0)
    if nPointsWasEmpty then Chart.nPoints=300 'default
   call fillArrForFunc f$,xmin,xmax
    call chartByArrayInHandle handle$
    if nPointsWasEmpty then Chart.nPoints=0 'reset
end sub

sub fillArrForFunc f$,xmin,xmax
    'copy to globals first
    Chart.xmin = xmin
    Chart.xmax = xmax
    'reality/default check
    if Chart.xmin=Chart.xmax then
        Chart.xmin = Chart.xmin-10
        Chart.xmax = Chart.xmax+10
    end if

    Chart.nSeries = 1    'for ordinary graphs, that will be
        'col0 for X, col1 etc to Y1 etc
    redim Chart.data(Chart.nSeries, Chart.nPoints)
    for i = 1 to Chart.nPoints
        x = i*(Chart.xmax-Chart.xmin)/Chart.nPoints+Chart.xmin
        Chart.data(0,i)=x
        Chart.data(1,i)=evalFuncX(f$,x)
    next 
end sub

sub fillArrForParametricFunc x$,y$,tmin,tmax
    'reality/default check
    if tmin=tmax then
        tmin = tmin-10
        tmax = tmax+10
    end if

    Chart.nSeries = 1    'for parametric graphs, that will be
    'col1 for X1, col2 etc to Y1, col3 for X2, col4 etc to Y2 etc
    redim Chart.data(Chart.nSeries*2, Chart.nPoints)
    for i = 1 to Chart.nPoints
        t = i*(tmax-tmin)/Chart.nPoints+tmin
        Chart.data(1,i)=evalFuncX(x$,t)
        Chart.data(2,i)=evalFuncX(y$,t)
    next
end sub

'-----------------------------------------------------

'function f(x) evaluation
function evalFuncX(f$,x)
    'for LB apparently
    'evalFuncX = eval(f$) 
    'exit function
    
    'now, for lowly JB
    select case f$
    case "1.5*x^2-2*sin(5*x)"   'single function testing
        evalFuncX = 1.5*x^2-2*sin(5*x)
    case "2*cos(x)-cos(2*x)"    'parametric, cardioid X of x[0..2pi]
        evalFuncX = 2*cos(x)-cos(2*x)
    case "2*sin(x)-sin(2*x)"    'parametric, cardioid Y of x[0..2pi]    
        evalFuncX = 2*sin(x)-sin(2*x)
    case else
        evalFuncX = x 'so you clearly see it works, and works wrong
    end select
end function

'To translate X from interval [a,b] to [c,d] we'll do (X-a)/(b-a)*(d-c)+c.
'create two functions: sx(x) and sy(y)
function sx(x)
    sx=(x-Chart.xmin)/(Chart.xmax-Chart.xmin)*Chart.winW
end function
function sy(y)
    sy=Chart.winH-(y-Chart.ymin)/(Chart.ymax-Chart.ymin)*Chart.winH   'Y is inverted, so Chart.winH-...
end function
'now, two back functions for mouse tracking
function ex(x)
    ex=(x-0)/Chart.winW*(Chart.xmax-Chart.xmin)+Chart.xmin
end function
function ey(y)
    ey=Chart.ymin-(y-Chart.winH)/Chart.winH*(Chart.ymax-Chart.ymin)   'Y is inverted, so Chart.winH-...
end function

'-----------------------------------------------------
sub drawAxes
    #Charthandle$ "color black"
    #Charthandle$ "size 1"
    #Charthandle$ "line ";sx(Chart.xmin);" ";sy(0);" ";sx(Chart.xmax);" ";sy(0)
    #Charthandle$ "line ";sx(0);" ";sy(Chart.ymin);" ";sx(0);" ";sy(Chart.ymax)
end sub

sub drawLabels
    'default: numbers 7x16 pixels
    CharW=7
    CharH=16
    call textAt sx(0)+5,sy(0)-5,"0,0"
    call textAt sx(Chart.xmax)-20,sy(0)-5,"X"
    call textAt sx(0)+5,sy(Chart.ymax)+20,"Y"

    call numAt sx(Chart.xmin)+20,sy(Chart.ymin)-5,Chart.xmin
    call numRightAt sx(Chart.xmax),sy(Chart.ymin)-5,Chart.xmax

    call numAt sx(Chart.xmin),sy(Chart.ymin)-20,Chart.ymin
    call numAt sx(Chart.xmin),sy(Chart.ymax)+15,Chart.ymax
end sub

sub drawTicks
    'tbd
end sub

'-----------------------------------------------------
sub textAt x,y,text$
    #Charthandle$ "place ";x;" ";y
    #Charthandle$ "\";text$
end sub

sub numAt x,y,n
    text$ = formatG$(n,1)
    call textAt x,y,text$
end sub

sub numRightAt x,y,n
    'default: numbers 7x16 pixels
    CharW=7
    text$ = formatG$(n,1)
    call textAt x-len(text$)*CharW,y,text$
end sub

'-----------------------------------------------------
'scientific USING function.
'(keep in mind that there are no more then 16 digits stored in real number (Double data type)).
function usingS$(n,prec)
  if n = 0 then usingS$="0e+0":exit function
  fmt$ = "#"+left$(".",prec>0)+left$("#################",prec)   'fmt of mantissa
  s$=left$("-",n<0)
  n=abs(n)
  log10=log(n)/log(10)
  e=int(log10)-(log10<0)    'QB like INT. Makes mantissa for negative exponents start from digit (not 0 as JB do)
  p=10^e
  if left$(using(fmt$,n/p),1)="%" then p=p*10:e = e+1
  usingS$=s$+using(fmt$,n/p) +"e"+left$("+",e>0) +str$(e)   'Excel always shows "+" for exponent
end function

'there 'scientific' just not needed?
function formatG$(n,prec)
    a$=usingS$(n,prec)
    fmt$ = "#"+left$(".",prec>0)+left$("#################",prec)   'fmt of mantissa
        if (n <0) and (instr(Version$, "LBB")>0) then fmt$="#"+fmt$
    b$=using(fmt$,n)
    if left$(b$,1)="%" then b$=mid$(b$,2)
        if (instr(Version$, "LBB")>0) and (left$(b$,1)="?") then b$=a$
    formatG$=a$
    if len(b$)<len(a$) _    'nice try but 1e-3 turns to 0.000 with prec=3 - ??
        and abs(n)>1 then formatG$=b$
end function

sarmednafi
Posts: 36
Joined: Fri Apr 06, 2018 6:27 am

Re: chart library, kind of

Post by sarmednafi »

Thank you, Anatoly :)