Bsdraw
Source code for 4 principal types of charts/graphs, drawed by fragment and vertex shaders. +Examples
Install / Use
/learn @elijivp/BsdrawREADME
BSDRAW
Source code for 4 principal types of graphs, drawed by fragment and vertex shaders.
- Technology: Qt widgets, inherits QOpenGLWidget (Qt>=5) or QGLWidget (Qt<5) class.
- Shaders: generated as source code and compiled after initializeGl stage on first show.
- Compatibility: tested on qt4.8, qt5.5, qt5.12, qt5.14(win/linux), qt6.3(linux). GLSL version 1.30+
- Features: fast, cross-platform, universal.
- Note: main define called BSGLSLVER prepends each shader with string "#version %BSGLSLVER%". All shaders in bsdraw are compatible with glsl 130, but by default BSGLSLVER is not set. So if you have any issues, add DEFINES+=BSGLSLVER=130 in your .pro file. All examples are taken from the project "example".
Overview
DrawIntensity class allows you to create rectangular 2D draws. Inherits DrawQWidget

This example is supplemented with overpattern feature - shader posteffect over drawed data.
Mechanics: 2d data converts into 2d texture, palette converts into 2d texture; Palette texture applies to data texture, shader overpattern applies to result
<details><summary>Code snippet (see end of the page for includes)</summary><p>LINES = 14;
SAMPLES = 20;
PORTIONS = 1;
overpattern_t dpms[] = {
// row 1
overpattern_off(),
overpattern_thrs_minus(OP_CONTOUR, 0.5f, 0.0f, 3),
overpattern_any(OP_CONTOUR, 0.0f, 3),
// row 2
overpattern_any(OP_LINELEFTBOTTOM, 0.0f),
overpattern_any(OP_DOTLEFTBOTTOM, 1.0f, 3),
overpattern_any(OP_LINELEFTBOTTOM, 0.4f, 2),
// row 3
overpattern_any(OP_LINEBOTTOM, 0.0f, 7),
overpattern_any(OP_SQUARES, 0.0f),
overpattern_any(OP_DOTCONTOUR, 0.0f, 3),
// row 4
overpattern_any(OP_CIRCLEBORDERED2, 0.0f, -20),
overpattern_any(OP_CIRCLESMOOTH, 0.0f, 4),
overpattern_thrs_plus(OP_CIRCLESMOOTH, 0.9f, 0.0f),
// row 5
overpattern_thrs_plus(OP_FILL, 0.8f, 0.0f),
overpattern_any(OP_DOT, 0.0f, 4),
overpattern_thrs_minus(OP_SHTRICHL, 0.3f, color3f(0.5f, 0.5f, 0.5f), 1),
// row 6
overpattern_any(OP_CONTOUR, color3f(0.0f, 0.0f, 0.0f),1),
overpattern_any(OP_CONTOUR, color3f(0.3f, 0.3f, 0.3f),1),
overpattern_any(OP_CONTOUR, color3f(1.0f, 1.0f, 1.0f),1)
};
const int countROWS = 6, countCOLUMNS = 3;
DrawQWidget* pdraws[countROWS][countCOLUMNS];
for (unsigned int r=0; r<countROWS; r++)
for (unsigned int c=0; c<countCOLUMNS; c++)
{
pdraws[r][c] = new DrawIntensity(SAMPLES, LINES, PORTIONS);
pdraws[r][c]->setOverpattern(dpms[r*countCOLUMNS + c]);
pdraws[r][c]->setScalingLimitsSynced(10); // 1 point now is 10x10 pixels (minimum)
}
// adding text as overlay for first draw
pdraws[0][0]->ovlPushBack(new OTextColored("Original", CR_XABS_YREL_NOSCALED, 5.0f, 0.9f, 12, 0x00000000, 0x11FFFFFF, 0x00000000));
for (int c=0; c<countCOLUMN; c++)
{
QVBoxLayout* layV = new QVBoxLayout;
for (int r=0; r<countROW; r++)
layV->addWidget(pdraws[r][c]);
currentHBoxLayout->addLayout(layV);
}
const float* data = someGeneratedData; // at least [LINES x SAMPLES x PORTIONS] of floats
for (int c=0; c<countCOLUMN; c++)
for (int r=0; r<countROW; r++)
pdraws[r][c]->setData(data);
</p></details>
DrawDomain class allows you to create rectangular 2D draws with regions of different size. One value on setData() method fills one region. Inherits DrawQWidget

All draws above have different regions but takes similar data.
Mechanics: full 2d field with region markup converts into 2d texture, region data converts into 1d texture, palette converts into 2d texture; Palette texture applies to region data texture, which applies to field texture
<details><summary>Code snippet (see end of the page for includes)</summary><p>LINES = 200;
SAMPLES = 200;
PORTIONS = 1;
const int countROWS = 2, countCOLUMNS = 2;
DrawQWidget* pdraws[countROWS][countCOLUMNS];
// 1st row, domain 1: little crosses
{
DrawDomain* dd = new DrawDomain(SAMPLES, LINES, PORTIONS, false, OR_LRBT, true);
DIDomain& ddm = *dd->domain();
const int crossRows = SAMPLES/10;
const int crossColumns = LINES/10;
for (int i=0; i<crossRows; i++)
{
for (int j=0; j<crossColumns; j++)
{
int r = LINES/crossRows/2 + i*LINES/crossRows;
int c = int(SAMPLES/crossColumns/2 + j*SAMPLES/crossColumns);
ddm.start();
ddm.includePixel(r-1, c);
ddm.includePixel(r, c);
ddm.includePixel(r+1, c);
ddm.includePixel(r, c+1);
ddm.includePixel(r, c-1);
ddm.finish();
}
}
pdraws[0][0] = dd;
pdraws[0][0]->setScalingLimitsSynced(2); // 1 point now is 2x2 pixels (minimum)
}
// 1st row, domain 2: chessboard
{
DrawDomain* dd = new DrawDomain(SAMPLES, LINES, PORTIONS, false, OR_LRBT, true);
DIDomain& ddm = *dd->domain();
const int cc = 20;
for (int r2=0; r2<LINES/cc/2; r2++)
{
for (int j=0; j<SAMPLES; j+=cc)
{
int r0 = r2*2*cc + ((j / cc) % 2)*cc;
int rs[] = { r0, r0, r0+cc/2, r0+cc/2 };
int cs[] = { j, j+cc/2, j, j+cc/2 };
for (int i=0; i<4; i++)
{
ddm.start();
for (int r=rs[i]; r<rs[i]+cc/2; r++)
for (int c=cs[i]; c<cs[i]+cc/2; c++)
ddm.includePixel(r, c);
ddm.finish();
}
}
}
pdraws[0][1] = dd;
pdraws[0][1]->setScalingLimitsSynced(2); // 1 point now is 2x2 pixels (minimum)
}
// 2nd row, domain 1: spiral
{
DrawDomain* dd = new DrawDomain(SAMPLES, LINES, PORTIONS, false, OR_LRBT, true);
DIDomain& ddm = *dd->domain();
const int maxspiral = SAMPLES*6;
const unsigned int outsider = 18500;
const double wc = 0.15/(2.0*M_PI);
for (int i=0; i<maxspiral; i++)
{
int y = qRound(LINES/2.0 + outsider*sin((i+1)*wc)/(i+1));
int x = qRound(SAMPLES/2.0 + outsider*cos((i+1)*wc)/(i+1));
if (y >= 0 && y < LINES && x >= 0 && x < SAMPLES)
{
ddm.start();
ddm.includePixelFree(y, x);
ddm.finish();
}
}
pdraws[1][0] = dd;
pdraws[1][0]->setScalingLimitsSynced(2); // 1 point now is 2x2 pixels (minimum)
}
// 2nd row, domain 2: multispiral
{
DrawDomain* dd = new DrawDomain(SAMPLES, LINES, PORTIONS, false, OR_LRBT, true);
DIDomain& ddm = *dd->domain();
const int maxspiral = 600;
const unsigned int outsider = 9000;
const double wc = 5.0/(2.0*M_PI);
for (int i=0; i<maxspiral; i++)
{
int y = qRound(LINES/2.0 + outsider*sin((i+1)*wc)/(i+1)), x = qRound(SAMPLES/2.0 + outsider*cos((i+1)*wc)/(i+1));
if (y >= 0 && y < LINES && x >= 0 && x < SAMPLES && ddm.isFree(y, x))
{
ddm.start();
ddm.includePixel(y, x);
ddm.finish();
}
}
pdraws[1][1] = dd;
pdraws[1][1]->setScalingLimitsSynced(2); // 1 point now is 2x2 pixels (minimum)
}
for (int c=0; c<countCOLUMN; c++)
{
QVBoxLayout* layV = new QVBoxLayout;
for (int r=0; r<countROW; r++)
layV->addWidget(pdraws[r][c]);
currentHBoxLayout->addLayout(layV);
}
const float* data = someGeneratedData; // at least [LINES x SAMPLES x PORTIONS] of floats
for (int c=0; c<countCOLUMN; c++)
for (int r=0; r<countROW; r++)
pdraws[r][c]->setData(data);
</p></details>
Portions its about how many draws/graphs painted in one draw. 2 or more portions in 2D draw means colormeshing (mixing) data in each pixel

At the bottom of picture DrawRecorder class allows you to create 2D draws from 1D data. Each setData() call appends 1D data array to current 2D image as new line. Inherits DrawQWidget
<details><summary>Code snippet (see end of the page for includes)</summary><p>LINES = 120;
SAMPLES = 400;
PORTIONS = 3;
const int countDraws= 3;
DrawQWidget* pdraws[countDraws];
pdraws[0] = new DrawIntensity(SAMPLES, LINES/PORTIONS, PORTIONS, OR_LRBT, SP_COLUMN_TB_COLORSPLIT);
pdraws[0]->setDataPalette(&paletteRGB);
pdraws[0]->setDataPaletteDiscretion(true);
pdraws[1] = new DrawIntensity(SAMPLES, LINES, PORTIONS);
pdraws[1]->setDataPalette(&paletteRGB);
pdraws[2] = new DrawRecorder(SAMPLES, LINES, 1000, PORTIONS);
pdraws[2]->setDataPalette(&paletteRGB);
for (int i=0; i<countDraws; i++)
this->layout()->addWidget(pdraws[i]);
const float* data = someGeneratedData; // at least [LINES x SAMPLES x PORTIONS] of floats
for (int i=0; i<countDraws; i++)
pdraws[i]->setData(data);
</p></details>
DrawGraph class allows you to create 1D graphs and histograms. Inherits DrawQWidget
Histograms with 3 portions displayed below

histograms above differ in the ordering of data draw. Histogram bar separation created by overpattern
<details><summary>Code snippet (see end of the page for includes)</summary><p>SAMPLES = 80;
PORTIONS = 3;
const int countDraws = 4;
DrawQWidget* pdraws[countDraws];
pdraws[0] = new DrawGraph(SAMPLES, PORTIONS, graphopts_t::goHistogram(PR_VALUEAROUND));
pdraws[0]->setOverpattern(overpattern_thrs_plus(OP_LINELEFTTOP, 0.0f, color3f(0.3f,0.3f,0.3f)));
pdraws[0]->setScalingLimitsSynced(10); // 1 point now is 10x10 pixels (minimum)
pdraws[1] = new DrawGraph(SAMPLES, PORTIONS, graphopts_t::goHistogramCrossMin(PR_VALUEAROUND));
pdraws[1]->setOverpattern(overpattern_thrs_plus(OP_LINELEFTTOP, 0.0f, color3f(0.3f,0.3f,0.3f)));
pdra
