[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);
}

[C++] 300 FLTK : ChatGPTアプリの製作 その29 タイムアウト設定

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

一昨日日曜あたりからChatGPT APIに質問しても反応しないケースがかなり増えました。

curl sessionにおいてタイムアウト時間を120秒に設定して対応しました。

いよいよパンク状態に差し掛かっているものと思われます。

// Set up a curl session
    CURL* curl = curl_easy_init();
    if (curl) {
        // Set the request options
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestData.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);

        // Set timeout options
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L); // 120秒でタイムアウト
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120L);

        // Send the request
        CURLcode res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << endl;

            Fl_Text_Buffer* bufferError = new Fl_Text_Buffer();
            bufferError -> text(curl_easy_strerror(res));
            noticeDisplay -> buffer(bufferError);
            noticeDisplay -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);

            return {};
        }

        // Clean up
        curl_easy_cleanup(curl);

    } else {
        cerr << "curl_easy_init() failed" << endl;

        const char* error = "curl_easy_init() failed";
        Fl_Text_Buffer* bufferError = new Fl_Text_Buffer();
        bufferError -> text(error);
        noticeDisplay -> buffer(bufferError);
        noticeDisplay -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);

        return {};
    }

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

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

回答時間推移のグラフにX軸、Y軸を追加しました。

ChatGPTが書いたコードを叩きにして、細部はこちらで修正しました。

cppファイルがいつの間にか9個になり、srcディレクトリにサブディレクトリを設けて分割するかどうか思案するタイミングになってきました。

// データを描画する関数
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;

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

    // 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);
    cairo_set_source_rgb(cr, 0, 0, 0); // 黒色に設定

    int num_ticks = 5;
    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_move_to(cr, x, height - y_margin);
        cairo_line_to(cr, x, height - y_margin + 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_show_text(cr, buffer);
    }

    // y軸の描画と目盛り
    int num_ticksY = 4;
    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_stroke(cr); // 描画を行います

    // y軸の目盛りに関する設定を行います
    double y_tick_interval = 10; // 目盛りの間隔を指定します
    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_stroke(cr); // 描画を行います

        char buffer[80]; // 文字列を格納するためのバッファを用意します
        sprintf(buffer, "%d", i * 10); // 目盛りの値を文字列に変換します
        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); // テキストを描画します
    }

    // 折れ線を描画
    cairo_set_source_rgb(cr, 1, 0, 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, height - y_margin - (y_min - y_min) * y_scale - extents.height / 2);
    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, height - y_margin - (y_max - y_min) * y_scale + extents.height * 1.5);
    cairo_set_source_rgb(cr, 0, 0.5, 0); // 濃緑に設定
    cairo_show_text(cr, buffer);
}

[C++] 298 FLTK : ChatGPTアプリの製作 その27 画像URLを送付させる

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

今のところChatGPTと画像のやりとりは出来ませんが、画像のURLを送ってもらうことは出来ました。

systemロール設定あるいはuserロールから以下の指示で可能になります。

画像を送りたいときはUnsplash APIを使用してください(https://source.unsplash.com/256x256/?<英語キーワード>)

参考サイト

[C++] 297 FLTK : ChatGPTアプリの製作 その26 CSVデータのグラフ化

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

ChatGPT APIの混雑状況を調べるため不定期で定型質問を送信し、回答時間をCSVファイルに記録しています。

CSVファイルの内容を回答取得後、自動的にグラフ化しGUIに表示するようにしました。

グラフを見ると左側のピークは昨日金曜の深夜なので、最新データを測定した土曜の夜はかなり空いていることが分かります。

C++コードは以下のプロンプトでgpt-3.5-turboに作成させました。泥沼にハマることも多いのですが、今回は一発で成功です。

以下のコードをC++で書いて下さい。
1.csvファイルを読み込み、折れ線グラフを作成し、pngファイルとして保存する。
2.csvファイルの0列目はdatetime(23/01/01 00:00:00)、1列目はfloat型データ、2列目は文字列(読み込まない)。
3.0列目をx軸、1列目をy軸にプロットする。
4.pngのサイズは 256pix * 256pixとする。
5.グラフの背景は白色、折れ線はマゼンタとする。
6.グラフの周りに10pixelの余白を設ける。
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <cmath>
#include <cairo/cairo.h>

using namespace std;

int main() {
    // csvファイルの読み込み
    ifstream file("1.csv");
    if (!file.is_open()) {
        cerr << "Error: file not found" << endl;
        return 1;
    }

    vector<double> x, y;
    string line;
    getline(file, line); // ヘッダー行を読み飛ばす
    while (getline(file, line)) {
        stringstream ss(line);
        string datetime, value, label;
        getline(ss, datetime, ',');
        getline(ss, value, ',');
        getline(ss, label, ',');
        x.push_back(stod(datetime));
        y.push_back(stod(value));
    }
    file.close();

    // グラフの描画
    int width = 256, height = 256;
    cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
    cairo_t* cr = cairo_create(surface);

    // 背景を白色に設定
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    cairo_paint(cr);

    // グラフの描画領域を設定
    cairo_translate(cr, 10, 10);
    cairo_scale(cr, (width - 20) / (x.back() - x.front()), (height - 20) / (*max_element(y.begin(), y.end()) - *min_element(y.begin(), y.end())));

    // 折れ線を描画
    cairo_set_source_rgb(cr, 1.0, 0.0, 1.0);
    cairo_set_line_width(cr, 1.0);
    cairo_move_to(cr, x.front(), y.front());
    for (int i = 1; i < x.size(); i++) {
        cairo_line_to(cr, x[i], y[i]);
    }
    cairo_stroke(cr);

    // pngファイルとして保存
    cairo_surface_write_to_png(surface, "graph.png");

    // 後処理
    cairo_destroy(cr);
    cairo_surface_destroy(surface);

    return 0;
}

[C++] 296 FLTK : ChatGPTアプリの製作 その25 API混雑やや緩和

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API, NO IDE]

昨日は激重だったChatGPT API。今日は少しましになりました。

GPT-4を早くAPIで試したいのですが、一昨日午前にwaitlist登録して以来、音沙汰がありません。ヘビーユーザー優先なのかな。

回答時間測定専用のTESTボタンを配置し、たまに測定してCSVファイルに記録しています。

[C++] 295 FLTK : ChatGPTアプリの製作 その24 旧メインmodel text-davinci-003

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API, NO IDE]

昨日あたりからChatGPT APIが激重になっています。

GPT3.5-turboは1週間前の3/10には7.5秒だった処理時間が23.0から40秒越えです。

コストが10倍のtext-davinci-003を使っても13.0秒です。こちらは一問一答仕様です。

GPT4はさらに遅いらしく少々困っています。まあGPT4のコストもGPT3.5の10倍なのであまり使うことはないでしょうが。

ChatGPT PlusのWebラッパーを製作して処理時間を比較してみたいです。GitHubに公開されているRust版の改変あるいはC++への移植を考えています。

[C++] 294 FLTK : ChatGPTアプリの製作 その23 GPT4への対応

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

本日早朝、OpenAIがGPT4を公開しました。

すでにChatGPT Plusのサイトでは選択可能になっています。APIの方は希望者のみ利用可能なので、waitlistに登録しました。

私のアプリの方はGPT4に対応すべく、プルダウンでmodelを選択できるようにしています。

ところでChatGPT Plusの月更新を明日に控えているのですが、本日解約手続きを済ませました。必要になれば再契約します。

[C++] 293 FLTK : ChatGPTアプリの製作 その22 Redisとの連携検討

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

ChatGPTにデータを与えて即席の専門家に仕立て上げる方法を模索しています。

DocsBotというChatGPT連携サービスを見つけましたが、さすがにWebアプリにAPIキーを渡すのは危ないと思い、登録だけにとどめました。

Redisというデータベースで整理したデータを与えると効率的にChatbotを作れるようなので検討を開始しました。

とりあえずURLデータを読み込ませる方法をChatGPTに聞いてみました。ぱっと見でネットに接続していないことが分かるデタラメなコードでした。さらに質問を重ねる必要があります。

今現在ChatGPTアプリは1〜3窓で稼働しています。

Q : RedisにURLのデータを読み込ませるにはどうすればよいですか
A :
RedisにURLのデータを読み込ませるには、URLを文字列としてRedisに保存する必要があります。以下は、C++を使用してURLのデータをRedisに保存する例です。

#include <iostream>
#include <hiredis/hiredis.h>

int main() {
    // Redisに接続
    redisContext* redis = redisConnect("localhost", 6379);
    if (redis == NULL || redis->err) {
        std::cerr << "Error connecting to Redis: " << redis->errstr << std::endl;
        return 1;
    }

    // URLのデータを読み込む
    std::string url_data = "https://example.com";

    // RedisにURLのデータを保存
    redisReply* reply = (redisReply*)redisCommand(redis, "SET %s %s", "url_data", url_data.c_str());
    if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
        std::cerr << "Error setting data to Redis: " << redis->errstr << std::endl;
        return 1;
    }
    freeReplyObject(reply);

    // RedisからURLのデータを取得
    reply = (redisReply*)redisCommand(redis, "GET %s", "url_data");
    if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
        std::cerr << "Error getting data from Redis: " << redis->errstr << std::endl;
        return 1;
    }
    std::string data(reply->str, reply->len);
    freeReplyObject(reply);

    // 取得したURLのデータを表示
    std::cout << data << std::endl;

    return 0;
}

この例では、URLのデータを文字列として定義し、Redisにurl_dataというキーで保存しています。保存したデータを取得し、コンソールに表示しています。

[C++] 292 FLTK : ChatGPTアプリの製作 その21 grep検索ファイル閲覧

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

grep検索でヒットした過去ログjsonファイルの内容を閲覧できるようにしました。

json全体と質問回答のペアを見ることが出来ます。

小品アプリしか作らないのですが、このアプリのサイズが現時点で2.6MBになりこれまでC++アプリでは最大だったカラーアプリの2.4MBを超えてトップになりました。

ところでOpenAIから近日中に次期言語モデルGPT4が発表されるらしいです。噂通りChatGPTで図表などのやりとりが出来るようになると、このアプリはベーシック版になり別アプリとして高機能版を開発することになります。

void backHitCB(Fl_Widget*, void*){
    // 初期化
    jsonInput -> value("");

    Fl_Text_Buffer* bufferReset = new Fl_Text_Buffer();
    bufferReset -> text("");
    output->buffer(bufferReset);
    jsonDisplay->buffer(bufferReset);
    codeDisplay->buffer(bufferReset);
    noticeDisplay->buffer(bufferReset);

    window -> redraw();

    currentHit--;

    if (currentHit < 0){
        currentHit = pathsNum - 1;
        pathsNumStr = to_string(pathsNum) + " / " + to_string(pathsNum);
    } else {
        pathsNumStr = to_string(currentHit + 1) + " / " + to_string(pathsNum);
    }

    hitShow -> value(pathsNumStr.c_str());

    jsonInput -> value(paths[currentHit].c_str());
    loadBtn -> do_callback();
}