Hanaのプログラミング日記

プログラミング関連で勉強したことの記録です。

作業記録を作成する

概要

Power BIデスクトップを使って作業記録の集計を行い円グラフで表示します。

f:id:hana1187:20210418212608p:plain
完成イメージ

使用するデータはExcelで以下のようなデータを作成しました。
開始日時と終了日時とタスクが記載されているだけのシンプルなデータです。

f:id:hana1187:20210418212657p:plain
使用データ

データ読み込み

Power BIは様々なデータ形式に対応しています。今回はExcelデータを読み込みます。
ホームにあるExcelボタンからExcelファイルを読み込みます。
ナビゲーターウィンドウから任意のファイル(ここでは作業記録.xlsx)を選び、 読み込むシートを指定し(ここではSheet1)、読み込みボタンを押します。

f:id:hana1187:20210418212819p:plain
ホーム画面のExcelボタンを押してデータ読み込み

f:id:hana1187:20210418212903p:plain
Sheet1を選択して読み込み

画面左側にある「データ」ボタンを押してデータ画面に切り替えます。
開始日時、終了日時、タスクのデータが入っていることが確認できます。

f:id:hana1187:20210418213003p:plain
データ画面

所要時間の計算

各作業にかかった時間を集計したいのですが、読み込んだデータだけでは各作業の所要時間がわかりません。
そこで、新たに列を追加し、開始日時と終了日時から所要時間を計算した結果を格納します。
「新しい列」ボタンを押し、列を作成します。
DAX式には以下を入力します。

所要時間 = DATEDIFF([開始日時], [終了日時], MINUTE)

f:id:hana1187:20210418213515p:plain
列の追加

f:id:hana1187:20210418213650p:plain
DAX

新しく追加された列「所要時間」に開始日時と終了日時の差分が分単位で格納されていることが確認できます。

円グラフ作成

左側のメニューから「レポート」画面に戻り円グラフを作成していきます。
視覚化メニューから円グラフを選択し、「凡例」にタスクを、「値」に所要時間を設定して完成です。

f:id:hana1187:20210418213923p:plain
「凡例」にはタスク、「値」には所要時間を指定


Power BIは簡単な操作できれいな見た目を作ることができます。
今回作成したレベルのものであればスマホアプリで十分ですが、これを拡張して作業カテゴリを追加したり、計画値と実績値を比較したりと自分好みにカスタマイズできるので、今後も勉強したことを記事にしていく予定です。

QtとOpenGLで三角形を描画する

概要

QtとOpenGLを使って三角形を描画します。
QtにはOpenGLの関数を使うための便利クラスが用意されているのでそれらを使います。

f:id:hana1187:20210321170850p:plain
三角形の描画

実行環境

準備

QOpenGLWidgetとQOpenGLFunctionsを継承したGLWidgetクラスを作成します。
この詳細は前回の記事をご覧ください。

手順

  1. シェーダーのセットアップ
  2. Vertex Buffer Objectのセットアップ
  3. Vertex Array Objectのセットアップ
  4. 頂点情報の書式を設定
  5. 描画

ソースコード

<glwidget.h>

#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLShaderProgram>

class QOpenGLShaderProgram;
class GLWidget : public QOpenGLWidget, public QOpenGLFunctions
{
    Q_OBJECT
public:
    GLWidget(QWidget *parent = nullptr);
    virtual ~GLWidget();

    void initializeGL();
    void paintGL();
    void resizeGL(int w, int h);

private:
    QOpenGLShaderProgram program_;
    QOpenGLBuffer vbo_;
    QOpenGLVertexArrayObject vao_;
};

#endif // GLWIDGET_H

<glwidget.cpp>

#include "glwidget.h"
#include <QOpenGLShaderProgram>

GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{

}

GLWidget::~GLWidget()
{
    if(vbo_.isCreated())
    {
        vbo_.destroy();
    }

    if(vao_.isCreated())
    {
        vao_.destroy();
    }
}

void GLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    // シェーダー生成
    program_.addShaderFromSourceFile(QOpenGLShader::Vertex, "../GLWidget/shader/vshader.glsl");
    program_.addShaderFromSourceFile(QOpenGLShader::Fragment, "../GLWidget/shader/fshader.glsl");
    program_.link();
    program_.bind();

    // Vertex Buffer Object(VBO)生成
    QVector<GLfloat> vertices;
    vertices.append(-0.5f); vertices.append(-0.5f); vertices.append(0.0f);
    vertices.append(0.0f); vertices.append(0.5f); vertices.append(0.0f);
    vertices.append(0.5f); vertices.append(-0.5f); vertices.append(0.0f);
    vbo_.create();
    vbo_.bind();
    vbo_.setUsagePattern(QOpenGLBuffer::StaticDraw);
    vbo_.allocate(vertices.constData(), vertices.count() * sizeof(GLfloat));

    // Vertex Array Object(VAO)生成
    vao_.create();
    vao_.bind();

    // 頂点情報の書式を設定
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);

    vao_.release();
    vbo_.release();
}

void GLWidget::paintGL()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    program_.bind();
    vao_.bind();
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

void GLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);
    Q_UNUSED(h);
}

<頂点シェーダー: vshader.glsl>

#version 400 core
layout(location = 0) in vec3 position;

void main()
{
    gl_Position = vec4(position, 1.0);
}

<フラグメントシェーダー: fshader.glsl>

#version 400 core
out vec4 color;

void main()
{
    color = vec4(0.0, 0.0, 1.0, 1.0);
}

解説

1. シェーダーのセットアップ

QtでOpenGLを使うにはGLSL (OpenGL Shading Language)を使ってGPUに対する 描画命令を記述します。今回は頂点シェーダーとフラグメントシェーダーを使って 三角形を描画します。
はじめにシェーダーファイルを作成します。頂点シェーダーでは座標を決めるだけで、フラグメントシェーダーでは青色で塗りつぶしているだけのシンプルなシェーダーです。
ソースコードを見ていきます。QtにはOpenGLでシェーダーを使うためのクラスとしてQOpenGLShaderProgramクラスが用意されていて、シェーダーファイルの読み込み、コンパイル、リンクをQOpenGLShaderProgramクラスが行ってくれます。
addShaderFromSourceFile関数ではシェーダーの種類とファイルパスを指定すると、シェーダーファイルを読み込んでコンパイルを行います。addShaderFromSourceFile関数はbool型の関数で、コンパイルに失敗するとfalseを返します。このエラーチェックもとても便利です。
シェーダーをコンパイルした後はこれらをリンクし、バインドして準備完了です。

2. Vertex Buffer Object(VBO)のセットアップ

VBOとは頂点座標や色などの頂点の情報を格納するバッファのことです。詳しくはこちら
QtにはVBOを扱うためのクラスとしてQOpenGLBufferクラスが用意されています。 create関数で生成・バインドをした後に、usagePatternの設定と頂点データのセットを行います。
今回はvshader.glslで頂点座標しか設定していないので頂点座標用のVBOだけ生成します。 頂点カラーや法線なども設定する場合はそれぞれにVBOを作成します(これらをすべて一つのVBOにまとめることもできます)。
また、今回は頂点座標は動的に変わらないので、usagePatternはStaticDrawを指定しています。allocate関数でデータそのものと、データのバイト数を指定します。

3. Vertex Array Object(VAO)のセットアップ

VAOはVBOをまとめるためのものです。たくさんのVBOを使う場合に便利です。
QtでVAOを扱うクラスとしてQOpenGLVertexArrayObjectクラスが用意されていますので生成・バインドをします。

4. 頂点情報の書式を設定

VBOを生成し、VAOに関連付けた後に、VBOのデータの書式を設定します。vshader.glslではlocation=0として3次元ベクトルデータを定義していますのでその紐づけを行います。
glEnableVertexAttribArrayでロケーションIDを指定して有効にします。その後、glVertexAttribPointerで書式を設定します。

void QOpenGLFunctions::glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr)

  • index: ロケーションID。vshader.glslで頂点座標のロケーションIDは0を指定しているので、ここでは0を指定。
  • size: 1頂点あたりのデータ数。vshader.glslにvec3と定義しているのでデータ数は3つ。
  • type: データの型。今回はGLfloat型なのでGL_FLOATを指定。
  • normalized: データを正規化するか。今回はそのままの値を使うのでGL_FALSE。
  • stride: データ間のバイトオフセットを指定。0を指定するとデータが隙間なく入っていることを意味するので今回は0。
  • ptr: バインドされているデータの初期位置。

書式の設定が終わったらVAOとVBOをリリースします。
複数のVAOやVBOを使う場合、適切にリリースしないと意図しないところにデータが関連付けられるので不要になったらリリースします。

5. 描画

GLWidgetクラスのpaintGL関数で描画を行います。
描画時はシェーダープログラムとVAOをバインドして描画コール関数のglDrawArrays関数を呼ぶだけです。

以上です。

今回はQtに用意されているOpenGL用のクラスを使用しましたが、これらを使わずに通常のOpenGLの関数だけでも描画可能です。
Qtのクラスは便利なのですが、情報が少なすぎるのでGLSLに慣れるまでは大変苦労しました。ある程度GLSLの使い方に慣れるまでは、Qtの方のクラスは使わない方がいいのかなと思います。
GLSLに関してはここのサイトがとても詳しく解説されていたので参考にしました。
WebGLの解説サイトなので細かい部分は違いますが、OpenGLの概念を理解するのにはとても参考になりました。

Qt5でOpenGLを使用する

概要

Qt5でOpenGLを使うにはQOpenGLWidgetクラスとQOpenGLFunctionsクラスを使用します。
今回はプロジェクト作成から画面のクリアまで行います。

f:id:hana1187:20210307190542p:plain
OpenGLで画面を青色で塗りつぶした状態

実行環境

手順

  1. Qt Widget Applicationのプロジェクトの作成
  2. QOpenGLWidgetクラスを継承したGLWidgetクラスの作成
  3. QOpenGLWidgetクラスの関数のオーバーライド
  4. OpenGLのバージョンの指定
  5. 描画部分の作成
  6. GLWidgetをMainWindowにセット

ソースコード

<glwidget.h>

#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>

class GLWidget : public QOpenGLWidget, public QOpenGLFunctions
{
    Q_OBJECT
public:
    GLWidget(QWidget *parent = nullptr);
    virtual ~GLWidget();

    void initializeGL();
    void paintGL();
    void resizeGL(int w, int h);
};

#endif // GLWIDGET_H

<glwidget.cpp>

#include "glwidget.h"

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

GLWidget::~GLWidget()
{
}

void GLWidget::initializeGL()
{
    initializeOpenGLFunctions();
}

void GLWidget::paintGL()
{
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}

void GLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);
    Q_UNUSED(h);
}

<main.cpp>

#include "mainwindow.h"

#include <QApplication>
#include <QSurfaceFormat>

int main(int argc, char *argv[])
{
    QSurfaceFormat fmt;
    fmt.setVersion(4,0);
    QSurfaceFormat::setDefaultFormat(fmt);

    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

<mainwindow.h>

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

<mainwindow.cpp>

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "glwidget.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    GLWidget* glw = new GLWidget(this);
    this->setCentralWidget(glw);
}

MainWindow::~MainWindow()
{
    delete ui;
}

詳細

1. Qt Widget Applicationのプロジェクトの作成

Qt Creatorの「ファイル」→「ファイル/プロジェクトの新規作成」から「Qt Widgets Application」を選択してプロジェクトを作成します。

f:id:hana1187:20210307191609p:plain
Qt Widgets Applicationを選択

2. QOpenGLWidgetクラスを継承したGLWidgetクラスの作成

Qt CreatorでSourcesのところを右クリックして「Add New...」でC++クラスを追加します。

f:id:hana1187:20210307191951p:plain
C++クラスの作成

基底クラスはQWidgetにします。

f:id:hana1187:20210307200700p:plain
基底クラスはQWidget

生成されたGLWidgetクラスはQWidgetクラスを継承しているので、ここをQOpenGLWidgetクラスに書き換えます。また、QOpenGLFunctionsクラスも継承させます。
QOpenGLFunctionsに関してはGLWidgetが所有するようにしてもいいですが、継承した方が便利だったので継承させました。

3. QOpenGLWidgetクラスの関数のオーバーライド

QOpenGLWidgetクラスを使うときは以下の3つの関数をオーバーライドして使います。

  • void initializeGL();
    OpenGLの初期化時に呼ばれる関数。QtでOpenGLの関数を使用するにはQOpenGLFunctionsクラスのinitializeOpenGLFunctions関数を呼ぶ必要がありますので、この初期化のタイミングで呼びます。
  • void paintGL();
    OpenGLの描画時に呼ばれる関数。今回は画面をクリアする関数をここで呼びます。
  • void resizeGL(int w, int h);
    Widgetのサイズが変わったときに呼ばれる関数。引数のwとhにはWidgetの幅と高さが入ります。

4. OpenGLのバージョンの指定

使用するOpenGLのバージョンの指定はQSurfaceFormatクラスのsetDefaultFormat関数で行います。
QtのドキュメントによるとsetDefaultFormat関数はQApplicationを生成する前に呼ぶことが推奨されているので main関数の最初のところで呼びます。

5. 描画

QOpenGLWidgetクラスではOpenGLの描画が必要な時にpaintGL関数が呼ばれます。
ここでglDrawArrays関数などを呼んでポリゴンを描画しますが、今回は画面のクリアだけなので使用しません。

6. GLWidgetをMainWindowにセット

GLWidgetクラスをMainWindowクラスの中で生成し、setCentralWidget関数でセントラルウィジェットに設定します。
setCentralWidgetでウィジェットをセットするとdeleteはMainWindowクラスが行うので、自分でdeleteする必要はありません。


以上です。

QtでOpenGLを使う際にたくさん躓いたので備忘録として書きました。
いろいろと初心者ですので、間違い等ご指摘いただけますと幸いです。