The program steps:
1. Write some data to file (in the format that gnuplot expects)
2. Create a gnuplot script file (all the plot settings)
3. Call gnuplot with the script file as argument
4. Open and display the created PNG-file in freebasic.
Code so far:
Code: Select all
'gnuplot wrapper, badidea, 2019-05-15
'libs/tools needed: gnuplot, libcairo, libz, libpng, ...
#include once "png12.bi"
#include once "fbgfx.bi"
#include once "string.bi"
const SCR_W = 800, SCR_H = 600, SCR_BPP = 32
declare function writeData(fileName as string, header() as string, myData() as double) as integer
declare function imageread_png(byref filename as string, byval bpp as integer) as FB.IMAGE ptr
const LF = !"\n"
const DQ = chr(34)
function quote(str1 as string) as string
return DQ + str1 + DQ
end function
type range_type
dim as double min, max
end type
type size_type
dim as integer x, y
end type
type font_type
dim as string face
dim as integer size
declare constructor(fontFace as string, fontSize as integer)
end type
constructor font_type(fontFace as string, fontSize as integer)
face = fontFace
size = fontSize
end constructor
type gnuplot_script
dim as string setStr, termStr, plotStr
'labels
dim as string graph_title = "<Graph Title>"
dim as string x_label = "<X label [.]>"
dim as string y_label = "<Y label [.]>"
'axis
dim as integer x_log_scale = 0
dim as integer y_log_scale = 0
dim as integer x_auto_scale = 1
dim as integer y_auto_scale = 1
dim as range_type x_range = type(-1, +1)
dim as range_type y_range = type(-1, +1)
dim as integer show_grid = 1
'output
dim as string term_output_file = "gnuplot01.png"
'terminal
dim as size_type term_size = type(SCR_W, SCR_H)
dim as integer term_transp = 0
dim as font_type term_font = type("arial", 18)
dim as integer term_crop = 0
dim as single term_line_width = 3.0
'plot
dim as string dataFile = ""
dim as string plot_title = "<Plot Title>"
'...
declare sub prepare(fileName as string)
declare sub write(fileName as string)
declare sub show()
end type
'-------------------------------------------------------------------------------
const NUM_COL = 3, NUM_ROW = 10
'some random data
dim as double current(NUM_ROW-1) = {120, 125, 122, 119, 134, 138, 141, 159, 162, 178}
dim as double voltage(NUM_ROW-1) = {250, 210, 180, 155, 140, 125, 110, 100, 90, 80}
dim as double resistance(NUM_ROW-1)
dim as string header(NUM_COL-1) = {"I [A]", "U [V]", "R [Ω]"}
dim as double myData(NUM_COL-1, NUM_ROW-1)
for i as integer = 0 to NUM_ROW-1
myData(0, i) = current(i)
myData(1, i) = voltage(i)
myData(2, i) = voltage(i) / current(i)
next
dim as string dataFile = "gpdata.dat" 'plot data
writeData(dataFile, header(), myData())
dim as string scriptFile = "gpscript.p" 'plot settings
dim as gnuplot_script gs
'adjust settings
gs.graph_title = "<My Title>"
gs.x_label = "Current [A]"
gs.y_label = "Voltage [V]"
gs.x_auto_scale = 0
gs.x_range = type(100, 200)
gs.y_auto_scale = 0
gs.y_range = type(0, 300)
gs.prepare(dataFile)
gs.show()
gs.write(scriptFile)
'call gnuplot
shell("gnuplot " + scriptFile)
'load and show the image
screenres SCR_W, SCR_H, SCR_BPP
dim as FB.IMAGE ptr pImage
pImage = imageread_png(gs.term_output_file, SCR_BPP )
if pImage = 0 then
print "Image error"
else
windowtitle(gs.term_output_file)
put (0, 0), pImage, pset 'alpha
imagedestroy(pImage)
end if
while inkey = "": wend
end 1
'Resource links:
'* http://www.gnuplotting.org/tag/png/
'* https://riptutorial.com/gnuplot/example/27496/fit--basic-linear-interpolation-of-a-dataset
'-------------------------------------------------------------------------------
'creates the gnuplot script. Call before write()
sub gnuplot_script.prepare(fileName as string)
dataFile = fileName
setStr = "# Gnuplot script file (http://gnuplot.info/)" + LF
setStr += "# Generated: " + date() + ", " + time() + LF
setStr += "set title " + quote(graph_title) + LF
setStr += "set xlabel " + quote(x_label) + "" + LF
setStr += "set ylabel " + quote(y_label) + "" + LF
if x_log_scale = 1 then setStr += "set logscale x" + LF
if y_log_scale = 1 then setStr += "set logscale y" + LF
if x_auto_scale = 1 then
setStr += "set autoscale x" + LF
else
setStr += "set xrange [" + str(x_range.min) + ":" + str(x_range.max) + "]" + LF
end if
if y_auto_scale = 1 then
setStr += "set autoscale y" + LF
else
setStr += "set yrange [" + str(y_range.min) + ":" + str(y_range.max) + "]" + LF
end if
if show_grid = 1 then setStr += "set grid" + LF
setStr += "set output " + quote(term_output_file) + LF
'terminal
termStr = "set terminal"
termStr += " pngcairo"
termStr += " size " + str(term_size.x) + "," + str(term_size.y)
if term_transp = 1 then termStr += " transparent"
termStr += " font " + quote(term_font.face + "," + str(term_font.size))
if term_crop = 1 then termStr += " crop"
termStr += " linewidth " + str(term_line_width)
'plot
plotStr = "plot \" + LF
plotStr += " " + quote(dataFile)
plotStr += " using 1:2"
plotStr += " title " + quote(plot_title)
plotStr += " with linespoints"
end sub
'write the gnuplot script to file. Call prepare first()
sub gnuplot_script.write(fileName as string)
dim as integer fileNum = freefile
if open(fileName, for output, as fileNum) = 0 then
print #fileNum, setStr
print #fileNum, termStr
print #fileNum, plotStr
close(fileNum)
else
print "Error opening: " + quote(fileName)
end if
end sub
'print the script file to screen, for debugging
sub gnuplot_script.show()
print setStr
print termStr
print plotStr
end sub
'-------------------------------------------------------------------------------
function writeData(fileName as string, header() as string, myData() as double) as integer
dim as integer fileNum = freefile
if open (fileName, for output, as fileNum) = 0 then
for iCol as integer = 0 to ubound(header)
print #fileNum, header(iCol),
next
print #fileNum, "" 'line break
for iRow as integer = 0 to ubound(myData, 2)
for iCol as integer = 0 to ubound(myData, 1)
print #fileNum, str(myData(iCol, iRow)),
next
print #fileNum, "" 'line break
next
close(fileNum)
else
print "Error opening: " + quote(fileName)
return -1
end if
return 0
end function
'-------------------------------------------------------------------------------
'PNG routine from freeBASIC examples (a bit modified)
function imageread_png(byref filename as string, byval bpp as integer) as FB.IMAGE ptr
dim as ubyte header(0 to 7)
dim as FILE ptr pFile = fopen(filename, "rb")
if pFile = 0 then
print "could not open image file"
return 0
end if
if fread(@header(0), 1, 8, pFile) <> 8 then
print "couldn't read header"
fclose(pFile)
return 0
end if
if(png_sig_cmp(@header(0), 0, 8)) then
print "png_sig_cmp() failed"
fclose(pFile)
return 0
end if
dim as png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0)
if pPng = 0 then
print "png_create_read_struct() failed"
fclose(pFile)
return 0
end if
dim as png_infop pInfo = png_create_info_struct(pPng)
if pInfo = 0 then
print "png_create_info_struct() failed"
fclose(pFile)
return 0
end if
png_init_io(pPng, pFile)
png_set_sig_bytes(pPng, 8)
png_read_info(pPng, pInfo)
dim as integer w, h, bitdepth, channels, pixdepth, colortype, rowbytes
w = png_get_image_width(pPng, pInfo)
h = png_get_image_height(pPng, pInfo)
bitdepth = png_get_bit_depth(pPng, pInfo)
channels = png_get_channels(pPng, pInfo)
pixdepth = bitdepth * channels
colortype = png_get_color_type(pPng, pInfo)
dim as FB.IMAGE ptr pImg = imagecreate(w, h)
dim as ubyte ptr pDst = cptr(ubyte ptr, pImg + 1) '???
png_set_interlace_handling(pPng)
png_read_update_info(pPng, pInfo)
rowbytes = png_get_rowbytes(pPng, pInfo)
dim as ubyte ptr pSrc = callocate(rowbytes)
for y as integer = 0 to h-1
png_read_row(pPng, pSrc, 0)
select case(colortype)
case PNG_COLOR_TYPE_RGB
imageconvertrow(pSrc, 24, pDst, bpp, w)
pDst += pImg->pitch
case PNG_COLOR_TYPE_RGB_ALPHA
select case(bpp)
case 24, 32
for i as integer = 0 to rowbytes-1 step 4
'' FB wants: &hAARRGGBB, that is &hBB &hGG &hRR &hAA (little endian)
'' libpng provides &hAABBGGRR, that is &hRR &hGG &hBB &hAA (little endian)
'' so we need to copy AA/GG as-is, and swap RR/BB
pDst[0] = pSrc[i+2]
pDst[1] = pSrc[i+1]
pDst[2] = pSrc[i+0]
pDst[3] = pSrc[i+3]
pDst += 4
next
case 15, 16
'' No alpha supported, only RGB will be used
imageconvertrow(pSrc, 32, pDst, bpp, w )
pDst += pImg->pitch
end select
case PNG_COLOR_TYPE_GRAY
select case(bpp)
case 24, 32
for i as integer = 0 to rowbytes-1
*cptr(ulong ptr, pDst) = rgb( pSrc[i], pSrc[i], pSrc[i] )
pDst += 4
next
case 15, 16
for i as integer = 0 to rowbytes-1
pset pImg, (i, y), rgb( pSrc[i], pSrc[i], pSrc[i] )
next
case else
'' 8 bpp and less require a proper global palette,
'' which contains the colors used in the image
'for i as integer = 0 to rowbytes-1
' pset pImg, (i, jinfo.output_scanline-1), pSrc[i]
'next
memcpy(pDst, pSrc, rowbytes )
pDst += pImg->pitch
end select
end select
next
deallocate(pSrc)
png_read_end(pPng, pInfo)
png_destroy_read_struct(@pPng, @pInfo, 0)
fclose(pFile)
return pImg
end function
Things to to:
- Multiple plots in one graph
- Control over the plot colors and markers
- Data fitting