コントローラからの情報のオンライン表示

コントローラーを実行させながらグラフを表示したい場合があると思います(スタビライザーのゲインを調整する場合など)。それを実現する方法の一つが、現在記録中のログを開き、見たいデータをプロットすることです。しかし、実験が長時間にわたる場合、この方法ではログのサイズが大きくなり、ログを開いて処理するのに時間がかかるため、作業が煩わしくなります。

よりよい方法は、このチュートリアルで説明するように、現在記録中のログをコントローラーに表示させることです。

注記

このページの例では、説明を簡単にするために、以下のコードスニペットが用意されているものとします

// 必要なすべての型宣言をインクルードする
#include <mc_rtc/gui/plot.h>

// 頻繁に使用する型の略称を定義する
using Color = mc_rtc::gui::Color;
using PolygonDescription = mc_rtc::gui::plot::PolygonDescription;
using Range = mc_rtc::gui::plot::Range;
using Style = mc_rtc::gui::plot::Style;
using Side = mc_rtc::gui::plot::Side;

入門用の2つの簡単な例

以下の2つの例では、横軸を共有するグラフと横軸を共有しないグラフを作成する方法について説明します。

最初の例では、mc_rtc::gui::plot::Xによって生成された横軸と、mc_rtc::gui::plot::Ymc_rtc::gui::plot::XYmc_rtc::gui::plot::Polygon(s)のいずれかによって生成されたプロットデータを指定します。このページでは、以下、この種類のプロットを「標準プロット」と呼ぶことにします。

gui()->addPlot(
  "sin(t)",
  mc_rtc::gui::plot::X("t", [this]() { return t; }),
  mc_rtc::gui::plot::Y("sin(t)", [this]() { return sin(t); }, Color::Red)
);

2番目の例では、mc_rtc::gui::plot::XYまたはmc_rtc::gui::plot::Polygon(s)によって生成されたデータのみを指定します。このページでは、以下、この種類のプロットを「XYプロット」と呼ぶことにします。

gui()->addXYPlot(
  "Circle in a square",
  mc_rtc::gui::plot::XY("Circle",
                        [this]() { return cos(t); }, [this]() { return sin(t); },
                        Color::Red),
  mc_rtc::gui::plot::Polygon("Square",
                             []() { return PolygonDescription({{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}, Color::Blue); })
);

どちらの種類のプロットも同じ方法で削除できます。

gui()->removePlot("sin(t)");
gui()->removePlot("Circle in a square");

概要は以上です。次のセクションでは、例で使用されている各要素について詳しく見ていきます。

mc_rtc::gui::plot::AxisConfiguration

このクラスを使用すると、指定した軸の名前と範囲を変更できます。デフォルトでは、範囲は[-inf, +inf]に設定されています。この場合、GUIクライアントは範囲を自動的に推測します。

横軸の設定(X軸)

標準プロットの場合

標準プロットの場合、横軸の設定はmc_rtc::gui::plot::Xで指定します。最初の例では第1引数で名前を指定しただけでしたが、この関数ではすべての設定を指定できます。例:

double t0 = t;
gui()->addPlot(
  "sin(t)",
  // 軸の最小値をt0に設定する
  // 軸の最大値をt0 + 10に設定する
  mc_rtc::gui::plot::X({"t", {t, t + 10}}, [this]() { return t; }),
  mc_rtc::gui::plot::Y("sin(t)", [this]() { return sin(t); }, Color::Red)
);

XYプロットの場合

XYプロットの場合、横軸の設定は省略できます。横軸を設定する場合は、addXYPlotの第1引数にmc_rtc::gui::plot::AxisConfigurationを指定する必要があります。例:

gui()->addXYPlot(
  "Circle",
  // 範囲を設定するもう一つの方法
  AxisConfiguration("X").min(-1).max(1),
  mc_rtc::gui::plot::XY("Circle",
                        [this]() { return cos(t); }, [this]() { return sin(t); },
                        Color::Red)
);

縦軸の設定(左右のY軸)

AxisConfigurationオブジェクトを指定することで、左右の縦軸を設定できます。

左側のY軸

  • 標準プロットの場合、横軸の設定を指定した後にY軸の設定を指定します。
  • XYプロットの場合、X軸の設定を指定した後に左側のY軸の設定を指定します。

右側のY軸

どちらのプロットの場合も、左側のY軸の設定を指定した後に右側のY軸の設定を指定します。

// 左のY軸のみ設定
double t0 = t;
gui()->addPlot(
  "sin(t)",
  mc_rtc::gui::plot::X({"t", {t, t + 10}}, [this]() { return t; }),
  AxisConfiguration("Y", {-1, 1}),
  mc_rtc::gui::plot::Y("sin(t)", [this]() { return sin(t); }, Color::Red)
);

// 両方のY軸を設定
gui()->addXYPlot(
  "Circle",
  // 上下限を設定する別の方法
  AxisConfiguration("X").min(-1).max(1),
  AxisConfiguration("Left Y"),
  AxisConfiguration("Right Y").max(10),
  mc_rtc::gui::plot::XY("Circle",
                        [this]() { return cos(t); }, [this]() { return sin(t); },
                        Color::Red)
);

mc_rtc::gui::plot::X

指定すべき項目はそれほどありません。以下の2つの要素を指定します。

  1. 横軸の名前またはAxisConfiguration
  2. double型の値を返すコールバック関数

mc_rtc::gui::plot::Y

必須の引数として3つの引数を指定し、オプションの引数として2つの引数を指定します。

  1. プロットの凡例に使用する名前(必須
  2. double型の値を返すコールバック関数(必須
  3. Color(色)、またはColor(色)を返すコールバック関数(必須
  4. ラインのStyle(スタイル)。デフォルトはStyle::Solid(実線)
  5. データを左側と右側のどちらのY軸に表示するかを指定するSide(左右の位置)。デフォルトはSide::Left(左側)

オブジェクトを生成した後にStyle(スタイル)とSide(左右の位置)を簡単に指定できる便利な関数が用意されています。

mc_rtc::gui::plot::Styleについて

4つのラインスタイルを指定できます。

  1. Style::Solid: 実線
  2. Style::Dashed: 破線
  3. Style::Dotted: 点線
  4. Style::Point: 線を1つの点に置き換える

gui()->addPlot(
  "cos(t)/sin(t)",
  mc_rtc::gui::plot::X("t", [this]() { return t; }),
  mc_rtc::gui::plot::Y("cos(t)", [this]() { return cos(t); }, Color::Red, Style::Dotted),
  // デフォルトのスタイルを変更せずに、左右のどちらのY軸を使用するかを指定する
  mc_rtc::gui::plot::Y("sin(t)", [this]() { return sin(t); }, Color::Blue).side(Side::Right)
);

mc_rtc::gui::plot::XY

必須の引数として4つの引数を指定し、オプションの引数として2つの引数を指定します。

  1. プロットの凡例に使用する名前(必須
  2. double型の横軸の値(x)を返すコールバック関数(必須
  3. double型の縦軸の値(y)を返すコールバック関数(必須
  4. Color(色)、またはColor(色)を返すコールバック関数(必須
  5. ラインのStyle(スタイル)。デフォルトはStyle::Solid(実線)
  6. データを左側と右側のどちらのY軸に表示するかを指定するSide(左右の位置)。デフォルトはSide::Left(左側)

オブジェクトを生成した後にStyle(スタイル)とSide(左右の位置)を簡単に指定できる便利な関数が用意されています。

mc_rtc::gui::plot::Polygon

必須の引数として2つの引数を指定し、オプションの引数として1つの引数を指定します。

  1. プロットの凡例に使用する名前(必須
  2. 次に説明するPolygonDescriptionを返すコールバック関数(必須
  3. データを左側と右側のどちらのY軸に表示するかを指定するSide(左右の位置)。デフォルトはSide::Left(左側)

オブジェクトを生成した後にSide(左右の位置)を簡単に指定できる便利な関数が用意されています。

mc_rtc::gui::plot::PolygonDescription

このオブジェクトは以下の情報を保持します。

  • points(): 点のリスト(std::vector<std::array<double, 2>>
  • outline(): 多角形の輪郭のColor(色)
  • style(): 多角形の輪郭のStyle(スタイル)(デフォルトはStyle::Solid(実線))
  • fill(): 閉じた多角形を塗りつぶすColor(色)(デフォルトは透明(塗りつぶしなし))
  • closed(): 多角形が閉じているかどうか(デフォルトはtrue)

注: 多角形が閉じている場合、points()の最後に始点を改めて指定する必要はありません(これはクライアントによって処理されます)。

auto redSquareBlueFill =
  PolygonDescription({{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}, Color::Red).fill(Color::Blue);
auto purpleTriangleYellowFill =
  PolygonDescription({{1, 0}, {1.5, 2}, {2, -2}}, Color::Magenta).fill(Color::Yellow);
auto cyanRectangle =
  PolygonDescription({{-2, -2}, {2, -2}, {2, -3}, {-2, -3}}, Color::Cyan);

注: コールバック関数mc_rtc::gui::plot::PolygonによってPolygonDescriptionが返されるため、アクティブなプロットの多角形の表示スタイルを自由に変更できます。

mc_rtc::gui::plot::Polygons

これはmc_rtc::gui::plot::Polygonと似ていますが、このコールバック関数はstd::vector<PolygonDescription>を返します。そのため、アクティブなプロットの多角形を時系列的に変化させて、シンプルなアニメーションを作成できます。

実行中にグラフを作成する

実行中にデータを選択してグラフを作成したい場合、イントロダクションで紹介したAPIではうまくいかない場合があります。その場合、以下に示すようにmc_rtc::gui::StateBuilder::addPlotDataメソッドを使用します。

// この例ではコールバック関数のマップを使用する。マップのキーによって、ラベルとデータが指定される
// コールバック関数ごとにスタイルを変えることで、処理をカスタマイズできる
std::map<std::string, std::function<double()>> callbacks;

// Y軸のデータなしでプロットを作成する
gui()->addPlot(
  "MyPlot",
  mc_rtc::gui::plot::X("t", [this]() { return t; }));

// N実行時の入力に基づきデータを追加する
for(const auto & it : callbacks)
{
  const auto & label = it.first;
  const auto & callback = it.second;
  gui()->addPlotData("MyPlot", mc_rtc::gui::plot::Y(label, callback));
}

注: この処理では実行時に以下のチェックが行われます。

  1. 指定されたプロットが存在しない場合、この呼び出しは何の効果もありません。
  2. データとして1次元データが指定され、グラフとしてXYプロットが指定された場合、この呼び出しは何の効果もありません。

Chunky XY プロット

これまで提供してきた例は常に一度に1つのデータポイントを提供します。しかし、XY プロットは、任意の数のポイントを一度に提供することができる追加のシグネチャをサポートしています。これは非同期でデータが送られてくる場合や、一度に単一のプロットを描画する場合に役立ちます。

gui()->addXYPlot(
  "Chunky plot",
  mc_rtc::gui::plot::XYChunk("Chunky",
                             [this](std::vector<std::array<double, 2>> & points) {
                               // 外部の条件に基づいてポイントを追加
                               // 何も追加しないこともあります
                             },
                             Color::Red)
);

注: これは XY プロットでのみサポートされており、そうでない場合は Y プロットを正しく同期させることができません。