[C++] 301 FLTK : ChatGPTアプリの製作 その30 CSVデータのグラフ化改良2

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

ChatGPT APIの回答時間推移グラフにY軸の補助線とX2軸、Y2軸を追加しました。

過去1週間のデータをグラフ化しています。Y軸補助線は24時間刻みです。

これでグラフの形式は完成といったところでしょうか。

2Dグラフィックスライブラリでグラフを描くのが徐々に楽しくなってきました。

FLTKにもFl_Chartというグラフ描画クラスがあるのですが、xyプロットの出来ない簡易版でした。

cairo以外の2Dライブラリを聞いてみたが、QtとSDLは既知だった。
// データを描画する関数
void draw_data(cairo_t* cr, vector<vector<double>> data, double x_min, double x_max, double y_min, double y_max, double width, double height) {
    // 背景色を白に設定
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_paint(cr);

    // 折れ線の幅を設定
    cairo_set_line_width(cr, 2);

    // グラフの周囲に20pixelの余白を設ける
    double x_margin = 20;
    double y_margin = 20;

    // 7*24時間より前のデータを削除
    time_t current_time = time(NULL);
    time_t threshold_time = current_time - 7 * 24 * 3600;
    vector<vector<double>> filtered_data;
    for (int i = 0; i < data.size(); i++) {
        if (data[i][0] >= threshold_time) {
            filtered_data.push_back(data[i]);
        }
    }
    data = filtered_data;

    // x_minをx_maxの7*24時間前とする
    x_min = x_max - 7 * 24 * 3600;

    // x軸とy軸のスケールを計算
    double x_scale = (width - 2 * x_margin) / (x_max - x_min);
    double y_scale = (height - 2 * y_margin) / 30;

    // x軸の描画と目盛り
    cairo_set_source_rgb(cr, 0, 0, 0); // 黒色に設定
    cairo_move_to(cr, x_margin, height - y_margin);
    cairo_line_to(cr, width - x_margin, height - y_margin);
    cairo_stroke(cr);

    cairo_select_font_face(cr, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, 10);

    int num_ticks = 7; // 1目盛り24時間
    double x_tick_interval = (x_max - x_min) / num_ticks;
    for (int i = 0; i <= num_ticks; i++) {
        double x = x_margin + i * x_tick_interval * x_scale;
        cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); // 灰色に設定
        cairo_move_to(cr, x, height - y_margin + 5);
        cairo_line_to(cr, x, y_margin);
        cairo_set_line_width(cr, 0.5);
        cairo_stroke(cr);

        time_t t = x_min + i * x_tick_interval;
        struct tm* timeinfo = localtime(&t);
        char buffer[80];
        strftime(buffer, 80, "%H:%M", timeinfo); // hh:mm形式に変更
        cairo_text_extents_t extents;
        cairo_text_extents(cr, buffer, &extents);
        cairo_move_to(cr, x - extents.width / 2, height - y_margin + 15);
        cairo_set_source_rgb(cr, 0, 0, 0); // 黒色に設定
        cairo_show_text(cr, buffer);
    }

    // y軸の描画と目盛り
    int num_ticksY = 6;
    cairo_set_source_rgb(cr, 0, 0, 0); // 描画色を黒色に設定します
    cairo_move_to(cr, x_margin, height - y_margin); // 描画開始位置を指定します
    cairo_line_to(cr, x_margin, y_margin); // 描画終了位置を指定します
    cairo_set_line_width(cr, 1);
    cairo_stroke(cr); // 描画を行います

    // y軸の目盛りに関する設定を行います
    double y_tick_interval = 5; // 目盛りの間隔を指定します
    for (int i = 0; i <= num_ticksY; i++) { // 目盛りの数だけループします
        double y = height - y_margin - i * y_tick_interval * y_scale; // 目盛りの位置を計算します
        cairo_move_to(cr, x_margin, y); // 描画開始位置を指定します
        cairo_line_to(cr, x_margin - 5, y); // 描画終了位置を指定します
        cairo_set_line_width(cr, 0.5);
        cairo_stroke(cr); // 描画を行います

        char buffer[80]; // 文字列を格納するためのバッファを用意します
        sprintf(buffer, "%d", i * 5); // 目盛りの値を文字列に変換します
        cairo_text_extents_t extents; // テキストの描画範囲を格納するための変数を用意します
        cairo_text_extents(cr, buffer, &extents); // テキストの描画範囲を計算します
        cairo_move_to(cr, x_margin - extents.width - 10, y + extents.height / 2); // テキストの描画位置を指定します
        cairo_show_text(cr, buffer); // テキストを描画します
    }

    // X2軸の描画
    cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); // 灰色に設定
    cairo_set_line_width(cr, 0.5);
    cairo_move_to(cr, x_margin, y_margin);
    cairo_line_to(cr, width - x_margin, y_margin);
    cairo_stroke(cr);

    // Y2軸の描画
    cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); // 灰色に設定
    cairo_set_line_width(cr, 0.5);
    cairo_move_to(cr, width - x_margin, height - y_margin);
    cairo_line_to(cr, width - x_margin, y_margin);
    cairo_stroke(cr);

    // 折れ線を描画
    cairo_set_source_rgb(cr, 1, 0, 1); // マゼンタに設定
    cairo_set_line_width(cr, 1);
    cairo_move_to(cr, x_margin, height - y_margin - (data[0][1]) * y_scale);
    for (int i = 1; i < data.size(); i++) {
        double x = x_margin + (data[i][0] - x_min) * x_scale;
        double y = height - y_margin - (data[i][1]) * y_scale;
        cairo_line_to(cr, x, y);
    }
    cairo_stroke(cr);

    // yの最大値と最小値の表示
    cairo_select_font_face(cr, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, 12);
    char buffer[80];
    sprintf(buffer, "y_min: %.2f", y_min);
    cairo_text_extents_t extents;
    cairo_text_extents(cr, buffer, &extents);
    cairo_move_to(cr, x_margin + 5, height - y_margin - y_min * y_scale);
    cairo_set_source_rgb(cr, 0, 0.5, 0); // 濃緑に設定
    cairo_show_text(cr, buffer);

    sprintf(buffer, "y_max: %.2f", y_max);
    cairo_text_extents(cr, buffer, &extents);
    cairo_move_to(cr, x_margin + 5, height - y_margin - y_max * y_scale);
    cairo_set_source_rgb(cr, 0, 0.5, 0); // 濃緑に設定
    cairo_show_text(cr, buffer);
}