PHP & More

A technical blog about programming in PHP and about technology in general: programming, workarounds and troubleshooting

A 4D graph can be used for plotting a graph of a complex function, with complex numbers as its input and output. A complex number can be represented as a pair of two real numbers:

  • A real part and an imaginary part.
  • A magnitude and an argument.

An image has a width and a height, but you can add an illusion of depth using a linear transform and changes in hue. A common way to add the fourth dimension is to use colors to represent its value. If you use a color for the fourth dimension, you want to add a legend to tell the viewer how to estimate the value of the fourth coordinate.

A good example of one such graph is the graph of the Lambert function found in the Wikipedia.

The Lambert W function is defined as follows:

x=ye^y\Rightarrow y=W_n(x)

where n \in \mathbb{Z} is the branch number.

In this post, I will discuss the use of Qt5 modules in creating my version of the graph:

The graph is not perfect and complete, but in this post I will explain how to create the components of the graph.

Combining OpenGL and 2D Painting

In Qt5, the OpenGL widget is a painting device like any other widget. So, you can use a painter of class QPainter to paint 2D shapes on it. The painter is useful if you want to add text to an image. Remember to unbind every GL object (vertex array objects, buffers, textures) before painting with the painter.

Before painting with the GL functions, call the painter’s method beginNativePainting. After finishing the use of GL functions, call endNativePainting

If you want the text rotation, you can use SVG. A simple way to add SVG text is using the QSvgRenderer class, that can create an SVG image from a file or a string.

Single Points

You can draw single points in OpenGL. To do so, you must enable GL_PROGRAM_POINT_SIZE using glEnable in your calling program, and define glPointSize in the vertex shader.

Matrices

The main types of matrices used in my example are:

  • Frustum – A perspective matrix defined using the minimum and maximum values of coordinates.
  • Viewport – a matrix that transforms the coordinates used by GL into the coordinates of the input rectangle. That transform will be used for placing the axis ticks snd texts.

The Program

The main window is a Qt Widget, so

The QMake File: qmake.pro:

The following definition will be used for creating the make file.

TARGET=executable
SOURCES=main.cpp
HEADERS=graph.h
HEADERS+=shaders.h
HEADERS+=svg.h
  
DESTDIR=bin
QT=core
QT+=gui
QT+=svg

greaterThan(QT_MAJOR_VERSION, 4): QT+=widgets

The main Header File

The header files ‘graph.h’ contains some definition to be used by the main program and the widget extending the QT Widget:

#include <QtWidgets/QOpenGLWidget>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QPainter>
#include <QtSvg>
#include <QStyleOptionGraphicsItem>

struct graph_point {
  GLfloat coords[3];
  GLfloat hue;
};

struct vertex2D {
  GLfloat coords[2];
  GLfloat hue;
};

struct axis_ticks {
  GLfloat rect_coords[3];
  GLfloat texture_coords[2];
};

class GraphWidget:public QOpenGLWidget{
private:
  QOpenGLVertexArrayObject m_vao;
  QOpenGLBuffer m_gradient_vbo;
  QOpenGLBuffer m_point_vbo;
  QOpenGLBuffer m_ibo;
  
  QOpenGLShader *m_vertexShader;
  QOpenGLShader *m_fragmentShader;
  QOpenGLShaderProgram *m_program;
  QPainter m_painter;
  QFont m_font;
  int fontSize;
  int m_width, m_height;
  float legend_left;
  QMatrix4x4 transform;
  QMatrix4x4 viewport;

  void addLegend(void);
  void populatePointBuffer(void);
  void paintGraph(void);
  void draw3DLine(QVector4D fromVec, QVector4D toVec);
  void addAxes(void);
  void add_x_ticks(void);
  void add_y_ticks(void);
  void add_z_ticks(void);
  void add_z_text(void);
  
public:
  GraphWidget(QWidget *parent=nullptr);
  ~GraphWidget();
  void initializeGL();
  void paintGL();
  void resizeGL(int w, int h);
};

HSV to RGB

The fourth coordinate of our graph represents arg(W(z)), an angle. In the graph we’ll use HSV (Hue, Saturation, Value) because the hue is given by the value of an angle. A shader-language function to covert from HSV to RGB is defined in file “shaders.h“:

#define TO_RGB_FUNC					\
  "#version 140\n"					\
  "vec4 to_rgb(float hue){ \n"				\
  "float s=1.0;\n"					\
  "float v=1.0;\n"					\
  "float c=v*s;\n"					\
  "float   x=c*(1-abs(mod(3*hue,2)-1));\n"		\
  "float   m=v-c;\n"					\
  "vec3  tempRGB=hue< -2./3?  vec3(0,x,c):\n"		\
  "              hue< -1./3?  vec3(x,0,c): \n"		\
  "              hue<     0?  vec3(c,0,x): \n"		\
  "              hue<  1./3?  vec3(c,x,0): \n"		\
  "              hue<  2./3?  vec3(x,c,0): \n"		\
  "		              vec3(0,c,x); \n"		\
  "\n"							\
  "return vec4(tempRGB+vec3(m,m,m),1);\n"		\
  "}\n"

The shader fragment for both the graph points and the legend is defined as follows:

#define FRAGMENT_SHADER				\
  TO_RGB_FUNC					\
  "varying float f_hue;\n"			\
  "void main(void){\n"				\
  "    gl_FragColor=to_rgb(f_hue);\n"		\
  "}\n"

Main Program Includes and Definitions

#include <stdio.h>

#include <graph.h>
#include <QApplication>
#include "shaders.h"
#include "svg.h"
#include <complex>
#include <iostream>


#define MIN_WIDTH 640
#define MIN_HEIGHT 480

using namespace std;

Constructor and Destructor

GraphWidget::GraphWidget(QWidget *parent):
  QOpenGLWidget(parent),
  m_vao(),
  m_gradient_vbo(),
  m_point_vbo(),
  m_ibo(QOpenGLBuffer::IndexBuffer),
  m_font()
{
  setGeometry(0,0,MIN_WIDTH,MIN_HEIGHT);
  setMinimumWidth(MIN_WIDTH);
  setMinimumHeight(MIN_HEIGHT);
}

GraphWidget::~GraphWidget(){
  makeCurrent();
  m_point_vbo.destroy();
  m_gradient_vbo.destroy();
  m_ibo.destroy();
  m_vao.destroy();
  doneCurrent();
  
}

// NUM_ARGS - Args of complex numbers.
// NUM_MAGNS - Number of magnitudes.
#define NUM_ARGS 140
#define NUM_MAGNS 200

Initializing, Populating the Point Buffer

Here we’ll define the variables that are initialize once. The following function, initializeGL, is called once upon the widget’s initization:

void GraphWidget::initializeGL(){
  m_vertexShader=new QOpenGLShader(QOpenGLShader::Vertex);
  m_fragmentShader=new QOpenGLShader(QOpenGLShader::Fragment);
  makeCurrent();
  glEnable(GL_BLEND);
  glEnable(GL_DEPTH_TEST);
  glEnable( GL_PROGRAM_POINT_SIZE );
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  m_vao.create();

  m_gradient_vbo.create();
  m_gradient_vbo.bind();
  m_gradient_vbo.allocate(4*sizeof(vertex2D));
  
  m_point_vbo.create();
  m_point_vbo.bind();
  m_point_vbo.allocate(NUM_MAGNS*NUM_ARGS*sizeof(graph_point));
  populatePointBuffer();

  GLshort indices[6]={0,1,2,2,3,0};
  m_ibo.create();
  m_ibo.bind();
  m_ibo.allocate(indices,sizeof(indices));
  m_ibo.release();
  m_gradient_vbo.release();
  m_point_vbo.release();
  m_program=new QOpenGLShaderProgram();
}

A call to glEnable( GL_PROGRAM_POINT_SIZE ); enables drawing single points.

The function populatePointBuffer uses the method ‘write‘ of class QOpenGLBuffer to write points to the vertex buffer. I use the buffer this way so I can use functions of complex variables.

void GraphWidget::populatePointBuffer(void){
  int vboOffset=0;
  graph_point vec[NUM_MAGNS];
  for (float magn=0;magn<1.4;magn+=1.4/NUM_MAGNS){
    int pos=0;
    for (float arg=-1;arg<1;arg+=2./NUM_ARGS){
      // The arg of W(Z) divided by pi.
      complex<float> iarg;
      iarg=complex<float>(0,M_PI*arg);
      complex<float> w=magn*exp(iarg);
      complex<float> z=w*exp(w);
      vec[pos].hue=arg;
      vec[pos].coords[0]=imag(z);
      vec[pos].coords[1]=magn;
      vec[pos].coords[2]=real(z);
      pos+=1;
    }
    m_point_vbo.write(vboOffset,vec,sizeof(vec));
    vboOffset+=sizeof(vec);
  }
}

Resizing

The function resizeGL is called as a response to a widget resize event. In this function the values that change due to a resize event are set. The properties set here are:

  • The font size for the painter
  • The transform matrix
  • The viewport matrix
void GraphWidget::resizeGL(int w, int h){
  m_width=w;
  m_height=h;
  fontSize=12.* min((float)m_width/MIN_WIDTH,(float)m_height/MIN_HEIGHT);
  m_font.setPixelSize(fontSize);
  float z_far=0.05;
  float z_near=0.01;
  float a=2./(m_height-2*fontSize);
  float b=1.8-a*m_height;
  float y_floor=b-2.*a*fontSize;
  
  // leftInPixels,RightInPixels - left and right boundaries
  //                              of the graph.
  float leftInPixels=12*fontSize+7;
  float rightInPixels=m_width - 12 - 15*fontSize;
  a=2/(rightInPixels-leftInPixels);
  b=-1-a*leftInPixels;
  float x_left=b;
  float x_right=a*m_width+b;
  
  transform.setToIdentity();

  transform.frustum(x_left,x_right,y_floor,1.8,z_near,z_far);
  transform.translate(0,0,-(z_far+z_near)/2);
  transform.scale(1,1,(z_far-z_near)/2);
  viewport.setToIdentity();
  viewport.viewport(rect());
}

Painting

The function paintGL is called as a response to paint events. Following is the code of paintGL:

void GraphWidget::paintGL(){
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  m_painter.begin(this);
  m_painter.setPen(QColor(255,255,255,255));
  m_painter.setFont(m_font);
  addLegend();
  addAxes();

  paintGraph();

  m_painter.end();
}

Let us look at the functions called by the paint event handler:

Add Legend

The function addLegend uses OpenGL function to create the HSV gradient and the painter to add ticks and text values:

void GraphWidget::addLegend(void){
  QString str="arg(W(Z))";

  // Drawing the header

  QRect boundingRect=m_painter.boundingRect(rect(),0,str);

  m_painter.beginNativePainting();

  m_vao.bind();
  QMatrix4x4 mat;
  mat.ortho(rect());
  GLfloat top=boundingRect.height()+20;
  GLfloat bottom=top+0.75*m_height;
  GLfloat left=m_width-boundingRect.width()-10;
  GLfloat right=left+0.25*boundingRect.width();
  legend_left = left; // Add it to the private members.
  vertex2D rect_vertices[4]={{{left,top},1},{{right,top},1},
  			     {{right,bottom},-1},{{left,bottom},-1}};
  m_gradient_vbo.bind();
  m_ibo.bind();
  GLshort indices[6]={0,1,2,2,3,0};
  m_ibo.write(0,indices,sizeof(indices));
  m_gradient_vbo.write(0,rect_vertices,sizeof(rect_vertices));

  m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,LEGEND_VERTEX_SHADER);
  m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,FRAGMENT_SHADER);
  m_program->bind();
  m_program->link();
  m_program->setAttributeBuffer("legend_coords",
				GL_FLOAT,
				0,
				2,
				sizeof(vertex2D));
  m_program->setAttributeBuffer("legend_hue",
				GL_FLOAT,
				sizeof(rect_vertices[0].coords),
				1,
				sizeof(vertex2D));
  m_program->setUniformValue("ortho",mat);
  
  m_vao.release();
  m_vao.bind();
  m_program->enableAttributeArray("legend_coords");
  m_program->enableAttributeArray("legend_hue");
  glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_SHORT,0);

  m_vao.release();
  m_program->disableAttributeArray("legend_coords");
  m_program->disableAttributeArray("legend_hue");
  m_program->release();
  m_program->removeAllShaders();
  m_gradient_vbo.release();
  m_ibo.release();

  m_painter.endNativePainting();
  m_painter.drawText(m_width-boundingRect.width()-10,boundingRect.height()+10,str);

  // Adding legend ticks
  QString vals[7]={"pi","2pi/3","pi/3","0","-pi/3","-2pi/3","-pi"};
  float curr_height=top;
  float dist=(bottom-top)/6;
  for (int i=0;i<7;i++){
    m_painter.drawLine(right,curr_height,right+10,curr_height);
    m_painter.drawText(right+15,curr_height+0.4*boundingRect.height(),vals[i]);
    curr_height+=dist;
  }
}

Following is the macro in shaders.h, which defines the vertex shader for the legend:

#define LEGEND_VERTEX_SHADER					\
  "attribute vec2 legend_coords;\n"				\
  "attribute float legend_hue;\n"				\
  "uniform mat4 ortho;\n"					\
  "varying float f_hue;\n"					\
  "void main(void){\n"						\
  "    gl_Position=ortho*vec4(legend_coords,0,1);\n"		\
  "    f_hue=legend_hue;\n"					\
  "}"

Add Axes

The function addAxes uses the painter and the viewport matrix to draw axes and add the ticks. You hove noticed that the matrices are of size 4×4 and the vectors are of length 4; when a matrix is multiplied by 4, you should divide coordinates x,y by coordinate w to find the vector’s location on the 2D window. Special treatment is added to the slanted z-axis, where SVG is used:

void GraphWidget::addAxes(void){

  QVector4D startVec,endVec;

  // Draw the x-axis
  startVec=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  endVec=viewport*transform*QVector4D(1,-0.2,1.1,1);
  draw3DLine(startVec,endVec);
  add_x_ticks();

  // Draw the y-axis
  startVec=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  endVec=viewport*transform*QVector4D(-1,1.4,1.1,1);
  draw3DLine(startVec,endVec);
  add_y_ticks();

  // Draw the z-axis
  startVec=viewport*transform*QVector4D(1,-0.2,-1,1);
  endVec=viewport*transform*QVector4D(1,-0.2,1.1,1);
  draw3DLine(startVec,endVec);
  add_z_ticks();
}

void GraphWidget::draw3DLine(QVector4D fromVec, QVector4D toVec){
  m_painter.drawLine(fromVec.x()/fromVec.w(),
		     m_height-fromVec.y()/fromVec.w(),
		     toVec.x()/toVec.w(),
		     m_height-toVec.y()/toVec.w());
}

void GraphWidget::add_x_ticks(){
  QVector4D start_point,end_point;
  QString vals[]={"-1","-0.5","0","0.5","1"};
  int i=0;
  for (float loc=-1;loc<=1;loc+=0.5){
    start_point=viewport*transform*QVector4D(loc,-0.2,1.1,1);
    start_point.setX(start_point.x()/start_point.w());
    start_point.setY(m_height-start_point.y()/start_point.w());
    end_point=start_point;
    end_point.setY(end_point.y()+5);
    m_painter.drawLine(start_point.x(),start_point.y(), end_point.x(),end_point.y());
    m_painter.drawText(start_point.x(),end_point.y()+fontSize+1,vals[i++]);
  }
  const char *text="Im(Z)";
  QVector4D start_vector=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  QVector4D end_vector=viewport*transform*QVector4D(1,-0.2,1.1,1);
  float text_start=(start_vector.x()+end_vector.x())/(2.*end_vector.w())-2*fontSize;
  float bottom=m_height-end_vector.y()/end_vector.w()+2*fontSize+5;
  m_painter.drawText(text_start,bottom,text);
}

void GraphWidget::add_y_ticks(){
  QVector4D start_point,end_point;
  QString vals[]={"0.0","0.2","0.4","0.6","0.8","1.0","1.2","1.4"};
  int i=0;
  for (float loc=0;loc<=1.41;loc+=0.2){
    start_point=viewport*transform*QVector4D(-1,loc,1.1,1);
    start_point.setX(start_point.x()/start_point.w());
    start_point.setY(m_height-start_point.y()/start_point.w());
    end_point=start_point;
    end_point.setX(end_point.x()-5);
    m_painter.drawLine(start_point.x(),start_point.y(), end_point.x(),end_point.y());
    m_painter.drawText(end_point.x()-2*fontSize,end_point.y()+0.5*fontSize,vals[i++]);

  }
  const char *text="|W(z)|";
  QVector4D start_vector=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  QVector4D end_vector=viewport*transform*QVector4D(1,1,1.1,1);
  float text_y_position=(start_vector.y()+end_vector.y())/(2.*end_vector.w())-2*fontSize;
  float text_rightmost=start_vector.x()/start_vector.w()-6*fontSize;
  m_painter.drawText(text_rightmost,text_y_position,text);
}

void GraphWidget::add_z_ticks(){
  QVector4D start_point,end_point;
  QString vals[]={"-1","-0.5","0","0.5","1"};
  int i=0;
  for (float loc=-1;loc<=1;loc+=0.5){
    start_point=viewport*transform*QVector4D(1,-0.2,loc,1);
    start_point.setX(start_point.x()/start_point.w());
    start_point.setY(m_height-start_point.y()/start_point.w());
    end_point=start_point;
    end_point.setY(end_point.y()-5);
    m_painter.drawLine(start_point.x(),start_point.y(), end_point.x(),end_point.y());
    if (i!=1){
      m_painter.drawText(end_point.x()-fontSize/2.,end_point.y()-1,vals[i]);
    }
    i+=1;
  }
  add_z_text();
}

void GraphWidget::add_z_text(){
  char *svg_str=(char *)calloc(strlen(SVG_TEXT_STR)+1,sizeof(char));
  const char *text="Re(Z)";
  QVector4D start_point,end_point;
  start_point=viewport*transform*QVector4D(1,-0.2,1,1);
  start_point.setX(start_point.x()/start_point.w());
  start_point.setY(m_height-start_point.y()/start_point.w());
  end_point=viewport*transform*QVector4D(1,-0.2,-1,1);
  end_point.setX(end_point.x()/end_point.w());
  end_point.setY(m_height-end_point.y()/end_point.w());
  qreal inRadians=qAtan2(end_point.y()-start_point.y(),end_point.x()-start_point.x());
  qreal inDegrees=qRadiansToDegrees(inRadians)+180;
  float centerX=(start_point.x()+end_point.x())/2.;
  float centerY=(start_point.y()+end_point.y())/2.;
  float lenOfText=(strlen(text)-1.5)*fontSize;
  float textX=centerX-lenOfText;
  float textY=centerY+fontSize;
  
  
  sprintf(svg_str,SVG_TEXT_STR,textX,textY,fontSize,inDegrees,centerX,centerY,text);
  cout<<svg_str<<endl;
  cout<<"Length of svg_str"<<strlen(svg_str)<<endl;
  cout<<"Lenght of SVG_TEXT_STR"<<strlen(SVG_TEXT_STR)<<endl;
  
  QSvgRenderer renderer((QByteArray(svg_str)));
  renderer.render(&m_painter,rect());

}

The function add_z_text renders the SVG text using an object of call QSvgRendered. It accepts a byte array made from a string. It can also take a file name. The macro SVG_TEXT_STR is defined in file “svg.h” as follows:/code

#define SVG_TEXT_STR							\
  "<svg>"								\
  "    <text x='%.0f'" 							\
  "          y='%.0f'"							\
  "          font-size='%dpx'"						\
  "          stroke='white'"						\
  "          fill='white'"						\
  "          transform='rotate(%.0f ,  %.0f ,  %.0f)'>%s</text>"	\
  "</svg>"

Painting the Graph

Following is the function that paints the graph:

void GraphWidget::paintGraph(){
  m_painter.beginNativePainting();
  m_vao.bind();
  m_point_vbo.bind();
  m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, GRAPH_VERTEX_SHADER);
  m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, FRAGMENT_SHADER);
  m_program->bind();
  m_program->link();
  m_program->setAttributeBuffer("graph_coords",
				GL_FLOAT,
				0,
				3,
				sizeof(graph_point));
  m_program->setAttributeBuffer("hue",
				GL_FLOAT,
				3*sizeof(GLfloat),
				1,
				sizeof(graph_point));
  m_program->setUniformValue("transform",transform);
  m_vao.release();
  m_vao.bind();
  m_program->enableAttributeArray("graph_coords");
  m_program->enableAttributeArray("hue");
  glDrawArrays(GL_POINTS,0,NUM_ARGS*NUM_MAGNS);
  m_program->removeAllShaders();
  m_program->release();
  m_point_vbo.release();
  m_vao.release();
  m_painter.endNativePainting();
}

The Main Function

Following is the code of the main function:

int main(int argc, char *argv[]){
  QApplication app(argc, argv);
  GraphWidget w;
  w.show();

  return app.exec();
}

Adding some items to an OpenGL figure using a vertex buffer is difficult. Text is one example, if you don’t want it just across the screen. You can use the OpenGL widget as a simple 2D canvas, but you might want to rotate it around the y-axis for example and add varying depth to it as in the following figure:

To add texts and other images to surfaces, OpenGL offers the textures. In this post, I will show how to create and use one and apply transforms.

Images and Textures

A texture is another object to bind, which means telling OpenGL to use that texture. When drawing the image, the Fragment Shader will use a sampler to take the pixel color at the coordinates defined by the Vertex Shader.

Qt5 offers an OpenGL texture class named QOpenGLTexture. The texture can be generated from an image passed to the constructor. For example:

m_texture=new QOpenGLTexture(img);

The image is an object of class QImage, if you choose to draw it, instantiate the class QPainter passing the image to the constructor. For example:

QPainter painter(&img);

Note: choose the right size for your image and the text fonts because you create a raster image, and by scaling it you may get an image of poor quality.

Texture Coordinates

The values of each coordinate of the texture is a floating point number between 0 and 1. When passed to the Vertex Shader, each texture vector of coordinates (usually x and y coordinates) should match a vertex coordinate.

For example: (0,0) is the top-left coordinate and (1,1) is the bottom-right.

Transforms

Transforms are done by multiplying a 4X4 matrix by each vertex coordinate vector with a fourth coordinate added. It’s value is usually 1. The reason to add a coordinate is to apply more transforms than just scaling and rotating around the origins.

Interactivity

Sometimes we add an input widget, for example: push button and spin boxes. The input widget should react to operations done by the user, so it emits a signal. Another widget has to react to the signal, so it has a special function known as a slot, and the developer’s job is to connect the signal and the slot. The signal should have at least as many arguments as the slot. And a type in the slot should be implicitly converted to the corresponding one in the signal (for example: short to integer or float.

The connection is done using the static method ‘connect‘ of class QObject.

Here, for example is a connection of the SpinBox’s event ‘valueChanged‘ with the extended OpenGL Widget’s ‘changeVerticalAngle‘:

QObject::connect(mainObject.verticalAngleField,
                  SIGNAL(valueChanged(int)),
                  mainObject.imageWindow,
                  SLOT(changeVerticalAngle(int)));

A class that has a slot method should have the macro Q_OBJECT at the beginning of the private section. After adding that macro, run the command ‘qmake‘.

The Code

Following is the code of a program that draw text with a varying depth. It uses a spin box to change the vertical angle of the perspective transform.

The shaders

Following is the code of the vertex shader:

attribute vec3 vertex_coords;
attribute vec2 texture_coords;
varying vec2 f_texture_coords;
uniform mat4 ortho;
uniform mat4 perspective;
void main(void){
   gl_Position=perspective*ortho*vec4(vertex_coords,1.0);
   f_texture_coords=texture_coords;
}

The main function multiplys two transform matrix by the input vector. The global variable names are names of locations defined by the functions setAttributeByffer‘ and ‘setUniformLocation‘.

Following is the code pf the fragment shader:

varying vec2 f_texture_coords;
uniform sampler2D m_texture;
void main(void){
    gl_FragColor=texture2D(m_texture, f_texture_coords);
}

The name ‘m_texture‘ is not set by the main program; it is just a name associated with the location of the bound texture. You canlearn more about the shading language here; Choose a book according to the version of shading language. You an find the version by printing the value of the string GL_SHADING_LANGUAGE_VERSION using the C++ command:

cout<<"Version: " << glGetString( GL_SHADING_LANGUAGE_VERSION )<< endl;

But it seems that the program links with an earlier version by default, so you better specify your version (if your graphical card supports it) using the directive “#version” at the very beginning of the shader’s code. For example: version 140 for SL version 1.40 Good example of shaders can be found in the Wikibooks OpenGL tutorial.

The extended OpenGL Widget

Following is the class extending QOpenGLWidget. It includes the vertical angle, aspect ratio, he slot function and other members:

#include <QtWidgets/QOpenGLWidget>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLShader>
#include <QOpenGLTexture>

class ExtendedOpenGLWidget:public QOpenGLWidget {
     Q_OBJECT
     QOpenGLVertexArrayObject m_vao;
     QOpenGLBuffer m_vbo;
     QOpenGLBuffer m_ibo;
     QOpenGLShaderProgram m_program;
     QOpenGLTexture *m_texture;
     QOpenGLShader *m_vertHexShader, *m_fragmentShader;
     float aspectRatio;
     int verticalAngle;
 public:
     ExtendedOpenGLWidget(QWidget *parent=nullptr);
     ~ExtendedOpenGLWidget();
     void initializeGL();
     void resizeGL(int w, int h);
     void paintGL();
 public slots:
     void changeVerticalAngle(int value);
 };

So, you can see how to add a slot.

Global Variables

The global variables of the main file are used for defining the data in the buffers:

  • vertex coordinates and their matching texture coordinates side by side for the Vertex Buffer.
  • vertex indices for the index buffer

The index buffer consists of the indices of vertices and their attributes that reside in the vertex buffer. The indices starting with 0,

struct vertex { float vertex_coords[3];
                 float texture_coords[2];
 };
 vertex vertices[]={{{0.0,0.0,1},{1.0,0.0}},
                    {{0.0,1,1},{1.0,1.0}},
                    {{0.0,1,0.5},{0.0,1.0}},
                    {{0.0,0.0,0.5},{0.0,0.0}}};
 unsigned short vertex_indices[]={0,1,2,2,3,0};

Constructor and Destructor

In the constructor you can see how to initialize the index buffer object (m_ibo), which is not a pointer.

Following is the code of both the constructor and the destructor:

ExtendedOpenGLWidget::ExtendedOpenGLWidget(QWidget *parent):QOpenGLWidget(parent),
                                         m_vbo(QOpenGLBuffer::VertexBuffer),
                                         m_ibo(QOpenGLBuffer::IndexBuffer){
}
ExtendedOpenGLWidget::~ExtendedOpenGLWidget(){
     makeCurrent();
     m_vao.destroy();
     m_vbo.destroy();
     m_texture->destroy();
     doneCurrent();
     delete m_vertexShader;
     delete m_fragmentShader;
     delete m_texture;
}

Initializing

The function initializeGL defines the vertex array object, buffers, texture, shaders and shader program. In addition it creates and image, draws the text on it using the class QPainter. A texture is then instantiated using the image and created. Following is the code of the functiom:

void ExtendedOpenGLWidget::initializeGL(){
     glEnable(GL_BLEND);
     glEnable(GL_DEPTH_TEST);
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     m_vao.create(); if (m_vao.isCreated()){
         m_vao.bind();
     }
     m_vbo.create();
     m_vbo.bind();
     m_vbo.allocate(vertices,sizeof(vertices));

     m_ibo.create();
     m_ibo.bind();
     m_ibo.allocate(vertex_indices,sizeof(vertex_indices));

    // Create an Image
    QImage img(400,200,QImage::Format_RGBX8888);
    QPainter painter(&img);
    painter.fillRect(0,0,200,200,QColor(0,0,255,255));
    painter.setPen(QColor(255,255,0,255));
    QFont font=painter.font();
    font.setPixelSize(48);
    painter.setFont(font);
    painter.drawText(5,75,"Hello!");
    painter.end(); m_texture=new QOpenGLTexture(img);
    m_texture->create();

    m_vertexShader=new QOpenGLShader(QOpenGLShader::Vertex);
    m_vertexShader->compileSourceFile("../vertex_shader.txt");
    m_fragmentShader=new QOpenGLShader(QOpenGLShader::Fragment);
    m_fragmentShader->compileSourceFile("../fragment_shader.txt");
    m_program.addShader(m_vertexShader);
    m_program.addShader(m_fragmentShader);
    m_program.link();
    m_program.bind();
 }

Painting

The painting is done by the method ‘paintGL‘. In the example, the function draws the textured surface by calling ‘glDrawElemts‘. The function ‘glDrawElements‘ uses an index buffer with the indices to the vertices, so you won’t have to define a vertex twice. Associating the texture with a variable in the shaders – using a function such as ‘setUniformLocation‘ or ‘setAttributeBuffer‘ – is not require, so a function to do it does not exists.

Following is the function’s code

void ExtendedOpenGLWidget::paintGL(){
     QMatrix4x4 orthoMat;
     orthoMat.ortho(QRectF(0,0,aspectRatio,1));
     QMatrix4x4 perspective;
     perspective.perspective(verticalAngle,aspectRatio, .2,10);
     m_program.setAttributeBuffer("vertex_coords",GL_FLOAT,0,3,sizeof(vertex));
     m_program.setAttributeBuffer("texture_coords",GL_FLOAT,sizeof(vertices[0].vertex_coords),2,sizeof(vertex));
     m_program.enableAttributeArray("vertex_coords");
     m_program.enableAttributeArray("texture_coords");
     m_program.setUniformValue("ortho",orthoMat);
     m_program.setUniformValue("perspective",perspective);
     m_vao.release();
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     m_vao.bind();
     m_texture->bind();
     m_program.bind();
     glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_SHORT,0);
     m_vao.release();
     m_program.disableAttributeArray("vertexcoords");
     m_program.disableAttributeArray("texture_coords");
 }

Resizing

The method ‘resizeGL‘ is an event handler called when the OpenGL widget is resized. In the example it changes the aspect ratio of the widget. Following is the code:

void ExtendedOpenGLWidget::resizeGL(int w, int h){
     aspectRatio=(float)w/h;
}

The Slot

The function ‘changedVerticalAngle‘ is a user-defined slot to be connected with the value-change event. It changes the vertical angle of the perspective transform, and calls update.The function ‘update‘ is a slot that accepts no arguments, and its role is to add a repaint event to the event loop. Using ‘repaint‘ might be risky if called by a function called by ‘paintGL‘ because the result of using it may cause endless recursion. So, better avoid using ‘repaint‘.

Following is the code:

void ExtendedOpenGLWidget::changeVerticalAngle(int value){
     verticalAngle=value;
     update();
}

The Main Function

The important part of the main function is that it connects signals sent by the spin box to the OpenGL widget:

int main(int argc, char *argv[]){
     QApplication app(argc, argv);;
     QMainWindow mainWindow;
     Ui_MainWindow mainObject;
     mainObject.setupUi(&mainWindow);
     QObject::connect(mainObject.verticalAngleField,
                      SIGNAL(valueChanged(int)),
                      mainObject.imageWindow,
                      SLOT(changeVerticalAngle(int)));
     mainObject.imageWindow->changeVerticalAngle(mainObject.verticalAngleField->value());
     mainWindow.show();
     return app.exec();    
 }

Now, to check if your image has been added, change the vertical angle to 0. A perspective matrix generated with a vertical angle zero is an identity matrix.

Drawing and painting a 3D image with GL – the Graphic Library – is not as trivial as doing the same on a 2D canvas: no 3D versions of the 2D canvas drawing methods are defined in GL classes. Instead, programs written with GL send data to the GPU (Graphic Processing Unit), and directions for organizing the data in data structures and for the rendition of data to the screen. The data is stored in buffers, and processed by shader programs.

Once you know how to use the OpenGL API, drawing the following image is simple:

The way Qt simplifies the use of GL are as follows:

  • it offers classes instead of integers to wrap GL resources: buffers, textures, vertex array and more.
  • the classes include overloaded functions.
  • the developer can create shaders without specifying the GL version.
  • Qt manages the main loop.

The following sections will describe the process of painting a 3D surface.

Initializing

To start working with GL, create an object of class QOpenGLWidget. Better create a subclass of QOpenGLWidget, if you work with Qt 5.4 or a later version. Don’t perform any GL operations before the method initializeGL is called. If you extend OpenGLWidget, any GL operation should be performed by one of the following methods: initializeGL, paintGL and resizeGL.

In initializeGL place operation to be performed once only at the initialization time. Some examples of initializeGL can be found in

Qt Assistnat->Qt Widgets->C++ Classes->QOpenGLWidget

click the code examples link.

Following is the initialization stage of my program to paint the triangle:

The Class

Following is a definition of the ExtendedOpenGLWidget class:

#include <QtWidgets/QOpenGLWidget>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLShader>
#include <QOpenGLContext>


class ExtendedOpenGLWidget:public QOpenGLWidget {
    private:
        QOpenGLVertexArrayObject m_vao;
        QOpenGLBuffer m_vbo;
        QOpenGLShaderProgram *m_program;
        QOpenGLShader *m_fragmentShader, *m_vertexShader;
    public:
        ExtendedOpenGLWidget(QWidget *parent=nullptr);
        ~ExtendedOpenGLWidget();
        void initializeGL();
        void paintGL();
};

The class is defined in file “ui_opengl.h”.

Include Headers

#include <ui_opengl.h>
#include <iostream>

using namespace std;

The file “ui_opengl” is the header file generated by th Qt Designer. The rest can be used for debug printing.

The vertices

Vertices coordinates and colors are defined in my program as follows:

struct vertex {
float coords[3];
float colors[4];
};

struct vertex {
     float coords[3];
     float colors[4];
 };
 vertex vertices[]={
     {{-0.9, -0.9, 0.0}, {0.0, 0.0, 1.0, 1.0}},
     {{ 0.9, -0.9, 0.0}, {0.0, 1.0, 0.0, 1.0}},
     {{ 0.0,  0.9, 0.0}, {1.0, 0.0, 0.0, 1.0}}
 };

The above is a definition of a global variable. If you put it in a class, you will have to specify array sizes.

Constructor

The only thing the constructor does is call the parent constructor:

ExtendedOpenGLWidget::ExtendedOpenGLWidget(QWidget *parent):QOpenGLWidget(parent){
 }

The initializeGL method

This function creates the vertex array object(VAO). If the VAO cannot be created, it cannot be bound to the context. Your program can draw with or without it. In my example, I bind the VAO.

What actually does the drawing is the shader program. For it to draw you should link it at run time with the shaders. If you use Qt 5, the class QOpenGLShaderProgram exists with a method addShaderFromSourceCode, so you can create program in separate text files without the need to recompile your GL program when the shader code changes.

The example in Qt Assistant->Qt GUI->C++ Classes->QOpenGLShaderProgram does not complete the example in Qt Assistnat->Qt Widgets->C++ Classes->QOpenGLWidget; Don’t use it if you bind a buffer, because the coordinates and colors defined by setAttributeArray and setUniformValue will not set the correct locations of the colors of coordinates for the drawing method. In addition, only use coordinates specified in pixel if the GL Widget is of fixed size.

Following is an initializeGL method for coordintes whose values are between -1 and 1, and the color RGBA values are floats between 0 and 1:

void ExtendedOpenGLWidget::initializeGL(){
     glEnable(GL_BLEND);
     glEnable(GL_DEPTH_TEST);
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     m_vao.create();
     if (m_vao.isCreated()){
         cout<<"Created"<<endl;
         m_vao.bind();
     }
     m_vbo.create();
     m_vbo.bind();

     // Allocating buffer memory and populating it
     m_vbo.allocate(vertices, sizeof(vertices));

     m_fragmentShader=new QOpenGLShader(QOpenGLShader::Fragment);
     m_fragmentShader->compileSourceFile("../fragmentshader.txt");
     m_vertexShader=new QOpenGLShader(QOpenGLShader::Vertex);
     m_vertexShader->compileSourceFile("../vertexshader.txt");
     m_program=new QOpenGLShaderProgram();
     m_program->addShader(m_fragmentShader);
     m_program->addShader(m_vertexShader);
     m_program->link();
     m_program->bind();

     // Arguments of setAttributeBuffer in this example:
     //    1: A string, the corresponding attribute in the shaders.
     //    2: the type of each array element.
     //    3: the start point relative to the beginning of the current buffer.
     //    4: the number of elements in each vector.
     //    5: stride: the difference in bytes between two successive vectors
     m_program->setAttributeBuffer("vertex", GL_FLOAT,0,3,sizeof(vertex));
     m_program->setAttributeBuffer("color", GL_FLOAT,sizeof(vertices[0].coords),4,sizeof(vertex));
     m_program->enableAttributeArray("vertex");
     m_program->enableAttributeArray("color");
     m_vao.release();
 }

Shaders

The program uses two shaders:

  • a vertex shader – outputs the coordinates of which point
  • a fragment shader – outputs the color of which point.
  • Following are the files:

Vertex shader:

 attribute highp vec3 vertex;
 attribute highp vec4 color;
 varying highp vec4 f_color;
 void main(void){
     gl_Position=vec4(vertex,1.0);
     f_color=color;
 }

Fragment shader:

 varying mediump vec4 f_color;
 void main(void){
     gl_FragColor = f_color;
 }

Drawing

The method paintGL draws the image. In this example, it uses the function glDrawArrays with the mode GL_TRIANGLES. In 3D the only 3D polygon type a graphical program always knows how to fill because the vertices are on the same plan.

Following is the paintGL method:

void ExtendedOpenGLWidget::paintGL(){
     cout<<"paint"<<endl;
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     m_vao.bind();
     // Arguments of setAttributeBuffer in this example:
     //    1. mode - how the shaders will draw the array
     //    2. start - first enabled array
     //    3. count - the number of indices.
     glDrawArrays(GL_TRIANGLES,0,3);
     m_vao.release();
}

The main function

An Open GL Widget may be a top-level window or a widget inside another container. In the following main function, the widget is inside a main window:

int main(int argc, char *argv[]){
     QApplication app(argc, argv);
     QMainWindow mainWindow;
     Ui_MainWindow mainObject;
     mainObject.setupUi(&mainWindow);
     mainWindow.show();
     return app.exec();
}

A Destructor

If you want to avoid warnings at the end of the execution, you better define a destructor to clean up the allocated objects.

Following is the code of one:

ExtendedOpenGLWidget::~ExtendedOpenGLWidget(){
     makeCurrent();
     m_vao.destroy();
     m_vbo.destroy();
     delete m_fragmentShader;
     delete m_vertexShader;
     delete m_program;
     doneCurrent();
 }

Learn more about OpenGL here

Qt allows you to develop a multi-platform application, that you can develop a GUI application on one platform and deploy to many others (if you avoid system-specific extra modules). Qt contains modules for creating widgets from elementary ones to OpenGL 3D, which I will discuss later. In addition it contains module with other functionalities an application may need such as SQL, XML, web channels, web views and web sockets.

Qt can be downloaded from https://www.qt.io/ or installed using the UNIX/Linux package installers.

In addition to the QT libraries and header files, there are 3 useful resources:

  • Qt Designer – A GUI tool for the generation of Python and C++ headers from a form graphically designed.
  • Qt Assistant – A reference to the Qt classes, Example, the QMake tutorial, etc.
  • qmake – an easy-to-use utility that creates the Makefile.

In the following sections, I will show how to create a project using the three tools:

Creating the Main Window

Qt Designer is an editor with which GUI designers and developers can communicate. With the designer you can create, save and edit Designer UI Files (*.ui) and create class files from them for Python and C++ programs. The QMake program discussed later can use the “.ui” file to generate a header file from it. The file can be edited by creating top level windows such as widows and dialog, and dragging and dropping widgets into them. You can also access and modify their properties; if you are a developer and have a Desiger UI file, you can find names of object in your program.

Starting:

As you start the QT Designer, the following dialog pops up:

If you don’t see the dialog, choose File->New from the editor’s menu bar, to make the dialog pop up.

Choose your top-level widget from the “templates/forms” menu, and click Create; a window will be created:

Now, from the right-hand Property Editor, you can change some properties, for example: let us change the window title:

Adding widgets and promoting them

Now, you can choose widgets from the left-hand side of the editors and drag-and-drop them into the window:

For example, let us add a push button, and change its QAbstractButton property “text” to “Push me!”.

One of the other properties we can change is the QObject property “objectName”, which will be the name of the appropriate field of class QPushButton in the created class.

By right-clicking inside the widget and choosing “Promote to..” from the context-menu, you can choose to use a class that inherits from the gadget’s class. Don’t forget to define the new class and its constructor and methods.

Previewing

You can view your design by clicking Form->Preview from the editor’s menu bar.

Saving the created class

If you don’t want the Make program to generate a header file, you can save it from the Qt Designer tool.

  1. Choose Form->View C++ Vode/View Python Code from the editor’s menu bar.
    a dialog will open.
  2. Click the “save” icon to save the created header file.

Adding Functionality

This section will describe a little Hello World program – in C++ – that uses a header file created wit QtDesigner. The purpose of the program is to print “Hello, world” to the standard output, when the “Push Me” button is clicked. To add the mouse event handling the class QPushButton has been promoted to Extended Button. This section will describe the program, and will help you train yourself using Qt Assistant.

The main function

To learn what a the main function of a Qt Application with widgets should look like:

  1. Start the Qt Assistant if not started yet.
  2. Click the Contents tab
  3. Click Qt Widgets (highlighted in the following image:

You” see a heading reading “Getting Started Programming with Qt Widgets”. Scroll down and see the contents of a main source line.

In the Hello World program the code is as follows:

 #include "ui_example.h"
 #include <iostream>

 using namespace std;

 ExtendedButton::ExtendedButton(QWidget *parent):QPushButton(parent) {
 }
 void ExtendedButton::mouseReleaseEvent(QMouseEvent *event){
     cout<<"Hello, world!"<<endl;
 }
 int main(int argc, char **argv){
     QApplication a(argc,argv);
     Ui_MainWindow mainObject;
     QMainWindow mainWindow;
     mainObject.setupUi(&mainWindow);
     mainWindow.show();
     return a.exec();
 }

The main file includes the constructor of the class ExtendedButton. Its role is to call the parent’s constructor. Another function implemented is mouseReleaseEvent, which is an event handler. You can learn from Qt Assistant, that the method mouseReleaseEvent is an event handler by clicking Qt Widget->QWidget in the right-hand Contents tab, and then click the link reading “events” under detailed description in the pages contents.

The class ExtendedButton is defined in “extendedbutton.h”. Following is the definition:

#include <QtWidgets/QPushButton>

 class ExtendedButton:public QPushButton {
     public:
         ExtendedButton(QWidget *parent=nullptr);
         void mouseReleaseEvent(QMouseEvent *event);
 };

The main object of the application is defined in “ui_example.h”, the file generated by “Qt Designer”, you better avoid changing it manually if you want to modify the “.ui” file from which it was generated. You’ll see a warning at the beginning. The role of the Class Ui_MainWindow is to contain main window (or central widget) and its underlying widgets as public members. The method setupUi binds them.

Creating the qmake file

“qmake” is an easy-to-use utility that generates a Makefile to be used by the make command to generate binaries, objects, libraries. etc. Qt Assistant includes a Qt Manual. Following is the content of the example’s qmake file named ‘qmake.pro’:

TARGET=executable
SOURCES+=main.cpp
HEADERS+=extendedbutton.h
FORMS+=example.ui
DESTDIR=bin
QT = core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

If you want the Make program to create the main header file, you can include the “.ui” file generated by Qt Designer in a variable named “FORMS”. The “make” program in turn will generate a header file from it with the prefix “ui_” added to the “.ui” file name. For example, from a file named “example.ui” the “make” program will generate a header file named “ui_example.h”. In this case, don’t add “ui_example.h” to the variable “HEADERS”.

If you want to know what to add to QT, go to the <Class Category>->C++ Classes and click the Detailed Description link.If you want to run qmake without command line arguments, call the qmake file ‘qmake.pro’.

The other day, I participated in a discussion on GimpNet IRC (irc://irc.gimp.org). I saw there a message in which some words were colored. Since my client is Pidgin, I cannot add colors simply by clicking on the Font button, and selecting a color: all the menu options are disabled.

Searching the web for IRC colors, I found that mIRC supports adding colors to the text. If you are not a mIRC user, at least you can find the list of color codes here. The page does not describe how to add colors and other text attributes in other clients, but to learn at least about the sequences to be sent to the server, writing a script is recommended. To find the way to write scripts using the IRC protocol, I recommend using the PERL language. A great place to find PERL modules is the CPAN (Comprehensive PERL Archive Network) site. Search for IRC. A good class to create mIRC color strings is String::IRC – add color codes for mIRC compatible client.

Following is a script I’ve written to print the sequences:

use String::IRC;
use strict;

sub print_char {
    my $ch=shift;
    my $ord=ord($ch);
    if ($ord<32 or $ord>126){
        printf "\033[37;1m<%x>\033[0m", $ord;
    } else {
        print $ch;
    }
}

sub print_string {
    my $str=shift;
    my $len=length($str);
    for (my $i=0; $i<$len;$i++){
        print_char(substr($str,$i,1));
    }
    print "\n";
}

my $red=String::IRC->new("Red")->red;
print_string "Red: $red";
my $red_underlined=String::IRC->new("Red Underlined")->red->underline;
print_string "Red Underlined $red_underlined";
my $red_bold=String::IRC->new("Red Bold")->red->bold;
print_string "Red Bold: $red_bold";
my $red_inverse=String::IRC->new("Red Inverse")->red->inverse;
print_string "Red Inverse: $red_inverse";

The output of the script is in the following image:

The hex codes of control characters appear in white. They can be added as Unicode characters.

So,

  • Unicode character 0x02 begins a bold string or sub-string.
  • Unicode character 0x1f begins an underlined string or sub-string.
  • Unicode character 0x03 begins a colored string or sub-string when followed by:
    • A foreground code
    • Foreground and background codes separated by a comma.
  • Unicode character 0x0f resumes to the default text format.

Now, to add a Control Character in Pidgin

First, check that the input method is “System”. To do it, left-click inside the input area and choose the input method:

Use your system’s method to insert a Unicode characterFor example, In Linux/UNIX, type <Ctrl>+<shift>+U followed by the hexadecimal code of the character, and the space or Enter.

The other day, I was invited to a zoom meeting. Zoom is a video conference application also available for computers. It’s address is https://zoom.us.

When the time of our meeting came, I couldn’t hear anything when connecting from my Firefox browser, and saw the following message:

.

After a short IRC conversation on #freebsd@freenode.org, I installed the User-Agent Switcher and Manager add-on on my Firefox.

This add-on adds an icon to the toolbar, Following is the icon as seen in the dark theme:

Now, when you click the icon, you will see the following popup:

Great! Now you can select a browser and operating system using the drop down lists, and then select the browser’s version using one of the radio button. Then, click the “Apply” button to save your changes and enable the add-on. Click the “Test” button to see what browser details will be passed to the remote server.

Good luck!

Having upgraded my FreeBSD system and VirtualBox, I found that VirtualBox does not work when invoked from the window manager’s menu. So, I tried running it from the command line, and got the following message:

/usr/local/lib/virtualbox/VirtualBox.so: Undefined symbol “_ZNK6QColor6darkerEi@Qt_5”

I tried to search the web for the cause, and found a link to a compatibility report including added symbols. My symbol was nott found there, but at least I got a clue about what I had to do: upgrade Qt5.

From some reasons I’m not notified that it’s time to upgrade my Qt5.

I found a great option for command pkg:I

pkg version| grep qt5

The output reveals the current version of a package, and if there are later versions; following is a typical output line:

qt5-printsupport-5.12.2 <

The less-then symbols indicates that there is a more recent version.

I started upgrading Qt5 packages, first qt5-core, then qt5-gui and saw a little progress: new undefined symbols. Finally, VirtualBox started working.

Another application affected by the failure to upgrade Qt5 is VLC , the famous media player from https://www.videolan.org

VLC does not show any error message, and you can run it from the command line with no problems. Specifying the path of the media device or file. For example by typing the command:

vlc cdda:///dev/cd0

if you want to listen to a compact disc.

Now, upgrading Qt5 has made VLC displaying its GUI window.

Matplotlib is a great python library for plotting and graphics. Graphics include formatted text, text paths and tabular data. Mathematical expressions are a good reason to use Matplotlib for rendering text. Matplotlib also supports some widgets one can use for input. If you use Matplotlib widgets, you vetter know how to size and position them unless – for example – you write a program for yourself.

Following is an example of bad code:

from matplotlib import pyplot as plt
from matplotlib.widgets import TextBox
from matplotlib.widgets import Button

fig,ax=plt.subplots()

teextbox=TextBox(ax,"Label:")
button=Button(ax,'Submit')

plt.show()

The code above create to widgets that fill up the same plotting area inside defined Axes object. In the following image, you can see that both the text entered by the user and the button text overlap. In addition. the text entered by the user exceeds the limit of the plotting area.

Get More Control over Your Widgets

For better results, there are 3 things to do:

  • Use separate plotting area for your widgets.
  • Set the plotting areas’ positions and sizes.
  • Using Event Handlers to control the input length in a TextBox and perform operations.

Separate Plotting Areas

The command plt.subplots() creates a figure, and a single plotting area, a uni-dimensional array of plotting areas or a bi-dimensional array of plotting areas. According to the number of rows and columns. The default is one row and one column. For example:

fig,ax=plt.subplots(nrows=2)

Returns a column of two plotting areas, To set the number of columns use the keyword argument ncols.

Let us see what happens if we set the number of colums (not adding widgets. yrt) by the following code:

from matplotlib import pyplot as plt
from matplotlib.widgets import TextBox
from matplotlib.widgets import Button

fig,ax=plt.subplots(nrows=2)

plt.show()

The code generates two Axes rectangles as follows:

Resizing and Positioning a Plotting Area

The Axes rectangle can be resized and positioned using the function matplotlib.axes.Axes.set_position. One of its arguments can be an array whose members are left,bottom,width and height. The position and size is relative to the figure. :

  • left=0 means that the Axes begin at the left side of the figure
  • left=1 means that the Axes begin at the right side of the figure (which makes them invisible).
  • bottom=0 means that the Axes begin at the bottom of the figure
  • bottom=1 means that the Axes begin at the top of the figure

The following code resizes the Axes rectangles, and adds the widgets:

from matplotlib import pyplot as plt
from matplotlib.widgets import TextBox
from matplotlib.widgets import Button

fig,ax=plt.subplots(nrows=2)

ax[0].set_position([0.2,0.85,0.7,0.08])
ax[1].set_position([0.495,0.6,0.1,0.1])
teextbox=TextBox(ax[0],"Label:",label_pad=0.01,color='cyan',hovercolor='red')
button=Button(ax[1],'Submit')
plt.show()

ax[0] is the rectangle containing the TextBox

ax[1] is the rectangle containing the box. It’s width is 0.1(10% of that of the figure), and its left edge is position at 0.495, which is 0.5+0.1/2. This makes its horizontal alignment centered.

In the following image you’ll see that the background color of the text box is ‘cyan’, and hovercolor defines the background color when the mouse pointer is over the text box.

Setting the Input Text’s Maximal Length and Event Handling

Event handling functions can be attached to widgets. Event handling functions can react to button clicks, text changes, submitions by pressing the Enter key, etc.

If you want to restrict the length of the input text in a TextBox, attach an event handling function as follows:

tb.on_text_change(func)

Where func is a function that gets one argument, the text. In this function, you can restrict the number of character. You better set the cursor position as well, because it increases whenever the user types a character, even if the text is changed by the event handler. Following is an example of how to check that the input matches a pattern all the way:

def tc_func(self,inp):
    if (len(inp)>self.maxlen):
        self.tb.cursor_index=self.curpos
        self.tb.set_val(self.val)
        return
    if (self.decpoint and inp.find('.')<0 and len(inp)>self.maxlen-1):
        self.tb.cursor_index=self.curpos
        self.tb.set_val(self.val)
        return
    if (not self.pattern.match(inp)):
        self.tb.cursor_index=self.curpos
        self.tb.set_val(self.val)
        return
    self.val=inp
    self.curpos=self.tb.cursor_index

From the argument self you can learn that the above function is a member of a class. Wrapping widgets in objects is recommended.

The member ‘cursor_index‘ is the position of the cursor after the event handler finishes its work. set_val sets a new value (or resets it).

The full source from which the code above is taken from my Square Root Calculator found at https://github.com/amity1/SquareRootCalculator

To handle button click events , use the function on_clicked, as follows:

button.on_clicked(click_handler)

The argument passed to the click_handler is an object of type matplotlib.backend_bases.MouseEvent. You can see in the following code how you can learn it:

from matplotlib import pyplot as plt
from matplotlib.widgets import TextBox
from matplotlib.widgets import Button

def click_handler(evt):
    print(type(evt))
    print("Button clicked with:"+ str(evt.button))

fig,ax=plt.subplots(nrows=2)

ax[0].set_position([0.2,0.85,0.7,0.08])
ax[1].set_position([0.495,0.6,0.1,0.1])
teextbox=TextBox(ax[0],"Label:",label_pad=0.01,color='cyan',hovercolor='red')
button=Button(ax[1],'Submit')
button.on_clicked(click_handler)
plt.show()

The event handler above prints the type of mits argument and the mouse button with which the button widget was clicked. Following is the output:

Button clicked with:MouseButton.LEFT

Button clicked with:MouseButton.MIDDLE

Button clicked with:MouseButton.RIGHT
<class 'matplotlib.backend_bases.MouseEvent'>
Button clicked with:MouseButton.LEFT
<class 'matplotlib.backend_bases.MouseEvent'>
Button clicked with:MouseButton.MIDDLE
<class 'matplotlib.backend_bases.MouseEvent'>
Button clicked with:MouseButton.RIGHT

Tables

A table is a widget that can be added to an Axes object in addition to other Artists.

There are two ways to create a table:

If you just choose to create a table without specifying loc, the table location in respect to the Axes, chances are you will not be satisfied.

The following code creates such a default table using the factory:

import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib.widgets import TextBox
from matplotlib.widgets import Button

fig,ax=plt.subplots()
tab=mpl.table.table(ax,cellColours=[['red','green'],['yellow','blue']])

plt.show()

In the following image generated by the code, you will see that the table is created just under the Axes, and it overlaps the frame x-ticks.

You can create the table somewhere else by setting the loc parameter, you can set a cell’s width and height, set a column width automatically, and align text.

Setting The Table’s Location and Modify Cells

To set a table location in respect to the Axes, pass the parameter loc with one of the valid codes, for example:

tab=mpl.table.table(ax,cellText=[['Red','Green'],['Yellow','Blue']],loc='upper left'

A default text alignment in a table cell can be defined by passing the parameter cellLoc when creating a new table. When adding a cell, the argument name is loc. The valid values for loc are: ‘left’, ‘center’ and ‘right’

Accessing a table cell is easy as ABC: access the cells as if the table were a bi-dimensional array whose elements are objects of type matplotlib.table.Cell. For example:

tab[row,col]

You can modify text properties using the function set_text_props of the cell object. And you can change its position and size by modifying properties inherited from class matplotlib.patches.Rectangle.

The following code creates a table near the upper left corner of the Axes, sets the column widths to be automatic, changes the color of text cells, and enlarges one of the cells.

import matplotlib as mpl
from matplotlib import pyplot as plt

fig,ax=plt.subplots()
tab=mpl.table.table(ax,cellText=[['Red','Green'],['Yellow','Blue']],
                    cellColours=[['red','green'],['yellow','blue']],
                    loc='upper left',cellLoc='left' )
tab.auto_set_column_width(0)
tab.auto_set_column_width(1)
tab[1,1].set_height(0.5)
for i,j in ((0,0),(0,1),(1,1)):
    tab[i,j].set_text_props(color='white')

plt.show()

The code above produces the following image:

Adding a Cell

You can add single cells to a table using the function add_cell of the table.

the function should be called with the row number and column number. The caller has to specify the keyword arguments ‘width’ and ‘height’.

The new cell should be connected to the table, and may influence the heights and widths of celles in the same row or column.

The following code adds a cell in a new column and row:

import matplotlib as mpl
from matplotlib import pyplot as plt

fig,ax=plt.subplots()
tab=mpl.table.table(ax,cellText=[['Red','Green'],['Yellow','Blue']],
                    cellColours=[['red','green'],['yellow','blue']],
                    loc='upper left',cellLoc='left')
print ("Table Created")
tab.auto_set_column_width(0)
tab.auto_set_column_width(1)
tab[1,1].set_height(0.5)
for i,j in ((0,0),(0,1),(1,1)):
    tab[i,j].set_text_props(color='white')
tab[1,0].set_xy((0,0.8))
tab.AXESPAD=0
cell=tab.add_cell(2,2,height=0.1,width=0.3,text='New Cell',loc='center',facecolor='orange')
plt.show()

In the following image, you can see a new orange cell that has been added to an existing table:

I recently installed VirtualBox configured internet connection to the internet for a virtual windows 7 machine I’ve installed using Vagrant, tried to setup USB, but got the following message: “Failed to save settings – Empty or null HostOnly interface name is not valid.”. Looking at the details, I saw the following “Result Code: E_FAIL (0x80004005) Component: NetworkAdapterWrapper …”.I don’t remember when I started to get that popup message. Couldn’t find any HostOnly adapter using the VirtualBox GUI. Finding the solution in the web is too hard. and I can’t find anything in the user guide either. But, one useful thing I know: in Linux and Unix-like system, I can find configuration files. They are usually found in directories whose names begin with ‘.’. followed by the program’s name or under “${HOME}/.config’ or directories created by the software. This time, the file was found in “${HOME}/VirtualBox VMs/<machine-name>”.
In this case: “${HOME}/VirtualBox VMs/windows7_default_1563689015423_90469/”. In this directory there are files with the suffix ‘.vbox’. They are XML files. In one of them, named “windows7_default_1563689015423_90469.vbox”, I find the following element:

        <Adapter slot="1" MACAddress="08002718154D" cable="true" type="82540EM">
          <DisabledModes>
            <InternalNetwork name="intnet"/%gt;
            <NATNetwork name="NatNetwork"/>
          </DisabledModes>
          <HostOnlyInterface name="VirtualBox Host-Only Ethernet Adapter #2"/>
        </Adapter>

Comment it out.
Problem solved.

HTML5 includes some features that allows developers to draw 3D shapes by drawing bi dimensional shapes and applying 3d transform, such as rotateX, rotateY and rotateZ. For convenience, you can shift the origin of axes using the style property If you don’t want the 3D image too flat (for example, all the faces of a cube having the same size) use perspective and perspective-origin style properties.

You can use the matrix3d style properties instead of the named transforms if, for example, you don’t want to compute angles.

The Style Properties

A style property can be defined by adding the attribute style to an HTML element, defining a CSS class or accessing a DOM node.

In this section I will explain the properties using a little Javascript program that draws a regular tetrahedron.

enter image description here

Drawing a tetrahedron is done by drawing 4 isosceles triangles and rotating each of them once or twice.

“perspective” and “perspective-origin”

The distance and angle from which the shape is viewed.

“perspective” holds the distance

“perspective-origin” – a position value.

For example:

var main_div = d3.select('body')
 .append('div')
 .style('position','absolute')
 .style('top','50px')
 .style('left','50px')
 .style('perspective','50px')
 .style('perspective-origin','bottom left');
 

Transform Values: “rotateX”, “rotateY”, “rotate” and “transform-origin”

Rotate an axis. Keep the value of the rotated axis coordinate unchanged, and change the rest. The axis is rotated around the position defined by “transform-origin”

The following code adds the data for creating 4 triangles, and rotates 1 triangle 120 degrees to the right and 1 triangle 120 degrees to the left. Rotation is done around the bottom face’s centroid.

main_div.selectAll('div').
 data([{color: 'red', transform: null,upperVertexInd: true},
 {color: 'black', transform: 'rotateX(90deg)', 'origin':'
 100px 100px 0'},
 {color: 'blue', transform: 'rotateY(120deg)',origin: cent
 roidString,upperVertexInd: true},
 {color: 'green', transform: 'rotateY(-120deg)',origin: ce
 ntroidString,upperVertexInd: true}])
 .enter()
 .append('div')
 .style('position','absolute')
 .style('top',0)
 .style('left',0)
 .style('transform',d=>d.transform)
 .style('transform-origin',d=>d.origin)
 .style('transform-style','preserve-3d')
 



(To be more precise, it rotates the DIV elements)

enter image description here

Tarnsform Values: “matrix3d”

This matrix is used if you want to use a transformation out of the comfort zone. For example, a rotation transform with cosines and sines of the angle. The argument list contains 16 values, which are the cells of a square matrix of order 4 (4 rows and 4 columns).

This matrix will be applied on (x,y,z,w) vector to get the target vector. When rotating a 2d vector )point), our original z-coordinate will be 0, and w will be 1.

To specify the matrix:

\left( \begin{matrix} a_0 \ a_4 \ a_8 \ a_{12} \\a_1 \ a_5 \ a_9 \ a_{13} \\a_2 \ a_6 \ a_{10} a_{14} \\a_3 \ a_7 \ a_{11} \ a_{15}\end{matrix} \right)

use

matrix3d(a_0,a_1,a_2,...,a_{15})

In my example, I will rotate 3 triangles, so their top vertex will go to a line perpendicular to the tetrahedron base, and passing through the base’s median.

The median of a triangle is the point where median cross its other, dividing each median at the ratio 1:2.

So, if each side of a triangle is of length 1. The height is \sqrt(3)\over2

Since, the height is the length of the median, the distance from a side to the centroid is the height divided by 3, and the requested sine is latex13latex 1 \over 3

The cosine is \sqrt {1 - {1 \over 3}^2} = {\sqrt 8 \over 3}

enter image description here

so, we will compute the matrix as follows:

var rotateXCos = Math.sqrt(8) / 3;
 var rotateXSin = 1 / 3;
 var rotateXMat3d = [1,0,0,0,
 0,rotateXCos,rotateXSin,0,
 0,-rotateXSin,rotateXCos,0,
 0,0,0,1];
 var matrixTransformString = 'matrix3d(' + rotateXMat3d + ')';
 

Now, the code to draw the tetrahedron with *d3.js( is:

 var side=100;
 var len=100;
 var height=side * Math.sqrt(3)/2;
 var centroidZValue = -height / 3; // The point where medians meet.
 var rotateXCos = Math.sqrt(8) / 3;
 var rotateXSin = 1 / 3;
 var rotateXMat3d = [1,0,0,0,
 0,rotateXCos,rotateXSin,0,
 0,-rotateXSin,rotateXCos,0,
 0,0,0,1];
 var matrixTransformString = 'matrix3d(' + rotateXMat3d + ')';
 var centroidString = '150px 0 ' + centroidZValue + 'px';
 var main_div = d3.select('body')
 .append('div')
 .style('position','absolute')
 .style('top','50px')
 .style('left','50px')
 .style('perspective','50px')
 .style('perspective-origin','bottom left');
 main_div.selectAll('div').
 data([{color: 'red', transform: null,upperVertexInd: true},
 {color: 'black', transform: 'rotateX(90deg)', 'origin':'
 100px 100px 0'},
 {color: 'blue', transform: 'rotateY(120deg)',origin: cent
 roidString,upperVertexInd: true},
 {color: 'green', transform: 'rotateY(-120deg)',origin: ce
 ntroidString,upperVertexInd: true}])
 .enter()
 .append('div')
 .style('position','absolute')
 .style('top',0)
 .style('left',0)
 .style('transform',d=>d.transform)
 .style('transform-origin',d=>d.origin)
 .style('transform-style','preserve-3d')
 .append('div')
 .style('transform-style','preserve-3d')
 .style('position','absolute')
 .style('top',0)
 .style('left',0)
 .style('transform',function(d){
 return d.upperVertexInd?matrixTransformString:false;
 })
 .style('transform-origin',function(d){
 return d.upperVertexInd?'0 100px 0':false;
 })
 .append('svg')
 .append('polygon')
 .attr('points',[100,100,150,100-height,200,100])
 .style('fill','none')
 .style('stroke',d=>d.color);