📊 Node-RED Dashboard 2.0 データ可視化ウィジェット ガイド

このガイドでは、Dashboard 2.0のデータ可視化系ウィジェットについて詳しく解説します。センサーデータのグラフ表示、ゲージによるリアルタイム監視、テーブルでのデータ一覧表示など、IoTダッシュボードに欠かせない機能を学びましょう。

📑 目次

  1. データ可視化ウィジェット概要
  2. サンプルフロー
  3. chart(チャート)
  4. gauge(ゲージ)
  5. table(テーブル)
  6. progress(プログレスバー)
  7. 演習問題
  8. まとめ
  9. トラブルシューティング
  10. 実務活用例
  11. 追加リソース

📈 1. データ可視化ウィジェット概要

🎨 日常のアナロジー:車のダッシュボード

車を運転するとき、ダッシュボードを見ると様々な情報が視覚的に表示されています:

Dashboard 2.0のデータ可視化ウィジェットも同様に、IoTセンサーやシステムの状態を直感的に把握できる形で表示します。

📋 データ可視化ウィジェット一覧

📈

chart

折れ線・棒・円グラフなど多彩なチャート

🎯

gauge

メーター形式の値表示

📋

table

データの一覧表示

📊

progress

進捗状況の表示

🔧 2. サンプルフロー

以下のサンプルフローをインポートして、各ウィジェットの動作を確認できます。

Inject データ生成 chart
slider gauge
📥 サンプルフローJSON(クリックで展開)
[ { "id": "viz_tab", "type": "tab", "label": "データ可視化サンプル", "disabled": false, "info": "" }, { "id": "viz_ui_base", "type": "ui-base", "name": "My Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 5, "showDisconnectNotification": true, "allowInstall": true }, { "id": "viz_ui_page", "type": "ui-page", "name": "データ可視化", "ui": "viz_ui_base", "path": "/visualization", "icon": "mdi-chart-line", "layout": "grid", "theme": "viz_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Small Desktop", "px": 768, "cols": 9}, {"name": "Desktop", "px": 1024, "cols": 12} ], "order": 1, "visible": "true", "disabled": "false", "className": "" }, { "id": "viz_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#b92d5d", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" }, "sizes": { "pagePadding": "12px", "groupGap": "12px", "groupBorderRadius": "4px", "widgetGap": "12px", "density": "default" } }, { "id": "viz_group_chart", "type": "ui-group", "name": "📈 チャート", "page": "viz_ui_page", "width": "6", "height": "10", "order": 1, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "viz_group_gauge", "type": "ui-group", "name": "🎯 ゲージ", "page": "viz_ui_page", "width": "6", "height": "10", "order": 2, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "viz_group_table", "type": "ui-group", "name": "📋 テーブル", "page": "viz_ui_page", "width": "6", "height": "6", "order": 3, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "viz_group_progress", "type": "ui-group", "name": "📊 プログレス", "page": "viz_ui_page", "width": "6", "height": "4", "order": 4, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "viz_inject_chart", "type": "inject", "z": "viz_tab", "name": "Chart: 5秒毎", "props": [ {"p": "payload"}, {"p": "topic", "vt": "str"} ], "repeat": "5", "crontab": "", "once": true, "onceDelay": "0.1", "topic": "センサーA", "payload": "", "payloadType": "date", "x": 140, "y": 60, "wires": [["viz_func_chart"]] }, { "id": "viz_func_chart", "type": "function", "z": "viz_tab", "name": "パターン1: シンプル数値", "func": "// パターン1: シンプルな数値(時系列自動)\nmsg.payload = Math.floor(Math.random() * 100);\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 380, "y": 60, "wires": [["viz_chart_line"]] }, { "id": "viz_inject_chart_obj", "type": "inject", "z": "viz_tab", "name": "Chart: オブジェクト形式", "props": [ {"p": "payload"}, {"p": "topic", "vt": "str"} ], "repeat": "5", "crontab": "", "once": true, "onceDelay": "0.5", "topic": "センサーB", "payload": "", "payloadType": "date", "x": 170, "y": 120, "wires": [["viz_func_chart_obj"]] }, { "id": "viz_func_chart_obj", "type": "function", "z": "viz_tab", "name": "パターン2: オブジェクト形式", "func": "// パターン2: オブジェクト形式\n// X軸の値を明示的に指定する場合\nmsg.payload = {\n time: Date.now(),\n value: Math.floor(Math.random() * 100)\n};\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 420, "y": 120, "wires": [["viz_chart_obj"]] }, { "id": "viz_chart_obj", "type": "ui-chart", "z": "viz_tab", "group": "viz_group_chart", "name": "オブジェクト形式チャート", "label": "オブジェクト形式", "order": 2, "chartType": "line", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisProperty": "time", "xAxisPropertyType": "property", "xAxisType": "time", "xAxisFormat": "", "xAxisFormatType": "auto", "xmin": "", "xmax": "", "yAxisLabel": "", "yAxisProperty": "value", "yAxisPropertyType": "property", "ymin": "", "ymax": "", "bins": "", "action": "append", "stackSeries": false, "pointShape": "circle", "pointRadius": 4, "showLegend": true, "removeOlder": 1, "removeOlderUnit": "3600", "removeOlderPoints": "", "colors": ["#2196f3", "#b92d5d", "#4caf50", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "6", "height": "4", "className": "", "interpolation": "linear", "x": 640, "y": 120, "wires": [[]] }, { "id": "viz_inject_chart_arr", "type": "inject", "z": "viz_tab", "name": "Chart: 配列形式", "props": [ {"p": "payload"} ], "repeat": "", "crontab": "", "once": true, "onceDelay": "1", "topic": "", "payload": "", "payloadType": "date", "x": 150, "y": 180, "wires": [["viz_func_chart_arr"]] }, { "id": "viz_func_chart_arr", "type": "function", "z": "viz_tab", "name": "パターン3: 配列形式", "func": "// パターン3: 配列形式(一括データ)\n// カテゴリ別データを配列で送信\nmsg.payload = [\n { category: '1月', sales: Math.floor(Math.random() * 100 + 50) },\n { category: '2月', sales: Math.floor(Math.random() * 100 + 50) },\n { category: '3月', sales: Math.floor(Math.random() * 100 + 50) },\n { category: '4月', sales: Math.floor(Math.random() * 100 + 50) }\n];\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 390, "y": 180, "wires": [["viz_chart_bar"]] }, { "id": "viz_chart_bar", "type": "ui-chart", "z": "viz_tab", "group": "viz_group_chart", "name": "配列形式棒グラフ", "label": "月別売上", "order": 3, "chartType": "bar", "category": "category", "categoryType": "property", "xAxisLabel": "", "xAxisProperty": "category", "xAxisPropertyType": "property", "xAxisType": "category", "xAxisFormat": "", "xAxisFormatType": "auto", "xmin": "", "xmax": "", "yAxisLabel": "", "yAxisProperty": "sales", "yAxisPropertyType": "property", "ymin": "", "ymax": "", "bins": "", "action": "replace", "stackSeries": false, "pointShape": "circle", "pointRadius": 4, "showLegend": false, "removeOlder": 1, "removeOlderUnit": "3600", "removeOlderPoints": "", "colors": ["#4caf50", "#b92d5d", "#2196f3", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "6", "height": "4", "className": "", "interpolation": "linear", "x": 600, "y": 180, "wires": [[]] }, { "id": "viz_chart_line", "type": "ui-chart", "z": "viz_tab", "group": "viz_group_chart", "name": "温度チャート", "label": "パターン1: シンプル数値", "order": 1, "chartType": "line", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisProperty": "", "xAxisPropertyType": "property", "xAxisType": "time", "xAxisFormat": "", "xAxisFormatType": "auto", "xmin": "", "xmax": "", "yAxisLabel": "", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "ymin": "", "ymax": "", "bins": "", "action": "append", "stackSeries": false, "pointShape": "circle", "pointRadius": 4, "showLegend": true, "removeOlder": 1, "removeOlderUnit": "3600", "removeOlderPoints": "", "colors": ["#b92d5d", "#2196f3", "#4caf50", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "6", "height": "4", "className": "", "interpolation": "linear", "x": 560, "y": 60, "wires": [[]] }, { "id": "viz_inject_gauge", "type": "inject", "z": "viz_tab", "name": "Gauge: 3秒毎", "props": [ {"p": "payload"}, {"p": "topic", "vt": "str"} ], "repeat": "3", "crontab": "", "once": true, "onceDelay": "0.1", "topic": "温度", "payload": "", "payloadType": "date", "x": 140, "y": 280, "wires": [["viz_func_gauge"]] }, { "id": "viz_func_gauge", "type": "function", "z": "viz_tab", "name": "ランダム温度生成", "func": "// Gauge用: ランダムな温度値を生成\nmsg.payload = Math.floor(Math.random() * 100);\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 280, "wires": [["viz_gauge_34"]] }, { "id": "viz_gauge_34", "type": "ui-gauge", "z": "viz_tab", "name": "温度ゲージ", "group": "viz_group_gauge", "order": 1, "value": "payload", "valueType": "msg", "width": "6", "height": "4", "gtype": "gauge-half", "gstyle": "rounded", "title": "現在温度", "alwaysShowTitle": false, "units": "°C", "icon": "mdi-thermometer", "prefix": "", "suffix": "", "segments": [ {"from": "0", "color": "#2196f3", "text": "", "textType": "label"}, {"from": "30", "color": "#4caf50", "text": "", "textType": "label"}, {"from": "60", "color": "#ff9800", "text": "", "textType": "label"}, {"from": "80", "color": "#f44336", "text": "", "textType": "label"} ], "min": 0, "max": 100, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "className": "", "x": 560, "y": 280, "wires": [[]] }, { "id": "viz_inject_table", "type": "inject", "z": "viz_tab", "name": "テーブルデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": "1", "topic": "", "payload": "[{\"id\":1,\"name\":\"センサーA\",\"value\":25.5,\"status\":\"正常\"},{\"id\":2,\"name\":\"センサーB\",\"value\":30.2,\"status\":\"正常\"},{\"id\":3,\"name\":\"センサーC\",\"value\":45.8,\"status\":\"警告\"},{\"id\":4,\"name\":\"センサーD\",\"value\":18.3,\"status\":\"正常\"}]", "payloadType": "json", "x": 140, "y": 340, "wires": [["viz_table"]] }, { "id": "viz_table", "type": "ui-table", "z": "viz_tab", "group": "viz_group_table", "name": "センサー一覧", "label": "センサーデータ", "order": 1, "width": "0", "height": "0", "maxrows": 10, "autocols": true, "showSearch": true, "deselect": true, "selectionType": "none", "columns": [], "mobileBreakpoint": "sm", "mobileBreakpointType": "defaults", "action": "replace", "className": "", "x": 350, "y": 340, "wires": [[]] }, { "id": "viz_slider_progress", "type": "ui-slider", "z": "viz_tab", "group": "viz_group_progress", "name": "進捗スライダー", "label": "進捗率", "order": 1, "width": "0", "height": "0", "outs": "all", "topic": "", "topicType": "str", "min": 0, "max": 100, "step": 1, "className": "", "x": 140, "y": 400, "wires": [["viz_progress"]] }, { "id": "viz_progress", "type": "ui-progress", "z": "viz_tab", "group": "viz_group_progress", "name": "処理進捗", "order": 2, "width": "0", "height": "0", "color": "#b92d5d", "className": "", "x": 340, "y": 400, "wires": [] } ]

📈 3. chart(チャート)

表示 chart ウィジェット

時系列データや比較データを視覚的に表示するチャートウィジェットです。eChartsライブラリをベースに、折れ線グラフ、棒グラフ、散布図、円グラフ、ヒストグラムなど多彩な表現が可能です。

🔹 対応チャートタイプ

📈

Line(折れ線)

時系列データの推移表示に最適

📊

Bar(棒グラフ)

カテゴリ別の比較に最適

Scatter(散布図)

2変数の相関分析に最適

🥧

Pie/Doughnut(円)

構成比の表示に最適

📶

Histogram

度数分布の表示に最適

🔹 主要プロパティ

プロパティ 動的 説明
Chart Type (chartType) - Line (line) / Bar (bar) / Scatter (scatter) / Pie (pie) / Doughnut (doughnut) / Histogram (histogram)
X-Axis Type (xAxisType) - Timescale (time) / Linear (linear) / Categorical (category)
Series - データのグループ化方法(msg.topic、key指定など)
X / Y - プロットするデータのプロパティ指定
Action append(追加)/ replace(置換)
X-Axis Limit - 表示するデータ期間の制限
Show Legend - 凡例の表示/非表示
Category Type - Seriesのデータ取得元タイプ(msg等)
X-Axis Label - X軸のラベルテキスト
X-Axis Property - X軸にプロットするプロパティ
X-Axis Property Type - X軸プロパティのタイプ(timestamp/msg等)
X-Axis Format - X軸の表示フォーマット
X-Axis Format Type - X軸フォーマットタイプ(auto等)
X Min / X Max - X軸の表示範囲制限
Y-Axis Label - Y軸のラベルテキスト
Y-Axis Property - Y軸にプロットするプロパティ(デフォルト: payload)
Y-Axis Property Type - Y軸プロパティのタイプ
Y Min / Y Max - Y軸の表示範囲制限
Bins - ヒストグラムのビン数(デフォルト: 10)
Stack Series - 系列の積み上げ表示
Point Shape - データポイントの形状(circle等)
Point Radius - データポイントのサイズ(デフォルト: 4)
Remove Older Unit - X-Axis Limitの時間単位(秒)
Remove Older Points - ポイント数による制限
Colors - 系列の色配列
Text Color - 軸ラベル等のテキスト色
Text Color Default - デフォルトテキスト色を使用
Grid Color - グリッド線の色
Grid Color Default - デフォルトグリッド色を使用
Interpolation - データ間の補間方法(linear/step等)
Size (Width / Height) - ウィジェットの幅と高さ(デフォルト: 6 x 8)
Class - CSSクラス名

🔹 入力データ形式

パターン1: シンプルな数値(時系列自動)

📤 送信データ
msg.payload = 25.5; msg.topic = "温度";

X軸のタイプが「Timescale」で、X値を指定しない場合、現在時刻が自動的に使用されます。

パターン2: オブジェクト形式

📤 送信データ
msg.payload = { "time": 1703500800000, // ミリ秒タイムスタンプ "value": 25.5 }; msg.topic = "センサーA";

パターン3: 配列形式(一括データ)

📤 送信データ
msg.payload = [ { "category": "1月", "sales": 100 }, { "category": "2月", "sales": 150 }, { "category": "3月", "sales": 120 } ];

🔹 補間方法(Interpolation)

折れ線グラフでポイント間をどう描画するかを選択できます:

🔹 タイムスタンプのフォーマット

フォーマット 表示例
{HH}:{mm}:{ss} 12:30:45
{HH}:{mm} 12:30
{yyyy}-{M}-{d} 2024-12-25
{d}/{M} 25/12
{ee} {HH}:{mm} Wed 12:30

🔹 チャートのクリア

💡 ヒント: チャートの全データをクリアするには、msg.payload = [](空配列)を送信します。
button chart

🎯 4. gauge(ゲージ)

表示 gauge ウィジェット

数値をメーター形式で表示するウィジェットです。温度、速度、進捗率など、範囲が明確な値の表示に最適です。複数のスタイル(Half、3/4、Tile、Battery、Water Tank)から選択できます。

🔹 ゲージタイプ

🎯

3/4 Gauge

270度の円弧

⏱️

Half Gauge

180度の半円

🔲

Tile

タイル形式の数値表示

🔋

Battery

バッテリー形式

💧

Water Tank

水位タンク形式

🔹 主要プロパティ

プロパティ 動的 説明
Type (gtype) gauge-34 / gauge-half / gauge-tile / gauge-battery / gauge-tank
Style (gstyle) rounded(丸)/ needle(針)
Range (min/max) 表示範囲の最小値・最大値
Segments 色分けするセグメントの定義
Label (title) ゲージの上部に表示するラベル
Units 単位(°C、%など)
Icon Material Design Iconの指定
Value - 表示する値のプロパティ(デフォルト: payload)
Value Type - 値の取得元タイプ(msg等)
Always Show Title - タイトルを常に表示
Floating Title Position - フローティングタイトルの位置(top-left等)
Prefix 値の前に表示するテキスト
Suffix 値の後に表示するテキスト
Size Thickness - ゲージバーの太さ(デフォルト: 16)
Size Gap - セグメント間のギャップ(デフォルト: 4)
Size Key Thickness - 凡例キーの太さ(デフォルト: 8)
Size (Width / Height) - ウィジェットの幅と高さ
Class - CSSクラス名

🔹 セグメント(色分け)の設定

// セグメント設定例 [ { "from": 0, "color": "#2196f3" }, // 青: 0-30 { "from": 30, "color": "#4caf50" }, // 緑: 30-60 { "from": 60, "color": "#ff9800" }, // オレンジ: 60-80 { "from": 80, "color": "#f44336" } // 赤: 80-100 ]

🔹 動的プロパティの更新

📤 msg.ui_updateで動的変更
msg.payload = 75; // 値 msg.ui_update = { "label": "CPU温度", "units": "°C", "min": 0, "max": 100, "segments": [ { "from": 0, "color": "green" }, { "from": 70, "color": "orange" }, { "from": 90, "color": "red" } ] };

🔹 値のフォーマット(JSONata)

Valueプロパティで「JSONata」タイプを選択すると、値を加工して表示できます:

$round(payload, 1) // 小数点1桁に丸める $round(payload, 0) // 整数に丸める payload * 100 // パーセント変換

📋 5. table(テーブル)

入出力 table ウィジェット

配列データを表形式で表示するウィジェットです。センサー一覧、ログデータ、設定値リストなどの表示に最適です。行選択による入力機能もあります。

🔹 基本的な使い方

データ table 選択行

🔹 入力データ形式

📤 配列形式のデータ
msg.payload = [ { "id": 1, "name": "センサーA", "value": 25.5, "status": "正常" }, { "id": 2, "name": "センサーB", "value": 30.2, "status": "正常" }, { "id": 3, "name": "センサーC", "value": 45.8, "status": "警告" }, { "id": 4, "name": "センサーD", "value": 18.3, "status": "正常" } ];

🔹 主要プロパティ

プロパティ 説明
Auto Columns オブジェクトのキーから自動的に列を生成
Columns 手動での列定義(表示順序、ラベル、幅などを指定)
Max Rows 表示する最大行数
Selection 行選択時にメッセージを出力
Label テーブルのラベル
Show Search 検索フィールドの表示
Deselect 選択解除を許可
Action 行クリック時のアクション
Mobile Breakpoint モバイル表示に切り替えるブレークポイント(デフォルト: sm)
Mobile Breakpoint Type ブレークポイントのタイプ
Size (Width / Height) ウィジェットの幅と高さ
Class CSSクラス名

🔹 列のカスタム定義

// Columnsプロパティの設定例 [ { "key": "id", "label": "ID", "width": "60px" }, { "key": "name", "label": "センサー名", "width": "150px" }, { "key": "value", "label": "現在値", "width": "100px", "align": "right" }, { "key": "status", "label": "状態", "width": "80px" } ]

🔹 行選択時の出力

📥 選択行のデータが出力される
msg.payload = { "id": 2, "name": "センサーB", "value": 30.2, "status": "正常" }

📊 6. progress(プログレスバー)

表示 progress ウィジェット

進捗状況を棒形式で表示するシンプルなウィジェットです。ダウンロード進捗、処理状況、容量使用率などの表示に最適です。

🔹 基本的な使い方

進捗値 progress

🔹 主要プロパティ

プロパティ 説明
Color バーの色
Label プログレスバーのラベル(デフォルト: "Progress")
Size (Width / Height) ウィジェットの幅と高さ
Class CSSクラス名

🔹 入力データ

msg.payload = 75; // 0-100の値

📝 7. 演習問題

各ウィジェットごとに実践的な演習問題を用意しました。段階的に取り組んで、データ可視化の技術を身につけましょう。

💡 演習の進め方:

📈 chart ウィジェット演習

演習 C-1: シンプルな温度グラフ初級

📋 課題:

温度センサーの値をリアルタイムで折れ線グラフに表示するダッシュボードを作成してください。

🎯 要求仕様:

💡 ヒント

Functionノードの設定:

// ランダム温度生成(20〜40度) msg.payload = Math.floor(Math.random() * 21) + 20; msg.topic = "室温"; return msg;

ui-chartの設定:

  • Chart Type: Line
  • X-Axis Type: Timescale
  • X-Axis Limit: 1分
  • Series: msg.topic
✅ 解答例フロー
[ { "id": "ex1_tab", "type": "tab", "label": "演習1: 温度モニター", "disabled": false, "info": "" }, { "id": "ex1_ui_base", "type": "ui-base", "name": "My Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 5, "showDisconnectNotification": true, "allowInstall": true }, { "id": "ex1_ui_page", "type": "ui-page", "name": "温度モニター", "ui": "ex1_ui_base", "path": "/exercise1", "icon": "mdi-thermometer", "layout": "grid", "theme": "ex1_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Small Desktop", "px": 768, "cols": 9}, {"name": "Desktop", "px": 1024, "cols": 12} ], "order": 1, "visible": "true", "disabled": "false", "className": "" }, { "id": "ex1_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#b92d5d", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" }, "sizes": { "pagePadding": "12px", "groupGap": "12px", "groupBorderRadius": "4px", "widgetGap": "12px", "density": "default" } }, { "id": "ex1_ui_group", "type": "ui-group", "name": "🌡️ 温度モニター", "page": "ex1_ui_page", "width": "6", "height": "8", "order": 1, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex1_inject", "type": "inject", "z": "ex1_tab", "name": "2秒毎", "props": [{"p": "payload"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 100, "wires": [["ex1_function"]] }, { "id": "ex1_function", "type": "function", "z": "ex1_tab", "name": "温度生成", "func": "msg.payload = Math.floor(Math.random() * 21) + 20;\nmsg.topic = \"室温\";\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 280, "y": 100, "wires": [["ex1_chart"]] }, { "id": "ex1_chart", "type": "ui-chart", "z": "ex1_tab", "group": "ex1_ui_group", "name": "温度推移", "label": "温度推移", "order": 1, "chartType": "line", "category": "topic", "categoryType": "msg", "xAxisProperty": "", "xAxisPropertyType": "msg", "xAxisType": "time", "xAxisFormat": "", "xAxisFormatType": "auto", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "action": "append", "pointShape": "circle", "pointRadius": 4, "showLegend": true, "removeOlder": 1, "removeOlderUnit": "3600", "removeOlderPoints": "", "colors": ["#b92d5d"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "0", "height": "0", "className": "", "x": 450, "y": 80, "wires": [[]] } ]

演習 C-2: 複数センサー比較チャート中級

📋 課題:

3つのセンサーからのデータを1つのチャートで比較表示するダッシュボードを作成してください。

🎯 要求仕様:

💡 ヒント

各Injectノードの設定:

  • センサーA: topic = "センサーA", 10-30の値
  • センサーB: topic = "センサーB", 20-40の値
  • センサーC: topic = "センサーC", 15-35の値

クリアボタン(ui-buttonまたはInject):

  • Payload: [] (空配列)
  • Payload Type: JSON
✅ 解答例フロー
[ { "id": "ex2_tab", "type": "tab", "label": "演習2: 複数センサー比較", "disabled": false, "info": "" }, { "id": "ex2_ui_base", "type": "ui-base", "name": "My Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 5, "showDisconnectNotification": true, "allowInstall": true }, { "id": "ex2_ui_page", "type": "ui-page", "name": "センサー比較", "ui": "ex2_ui_base", "path": "/exercise2", "icon": "mdi-chart-multiline", "layout": "grid", "theme": "ex2_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Small Desktop", "px": 768, "cols": 9}, {"name": "Desktop", "px": 1024, "cols": 12} ], "order": 1, "visible": "true", "disabled": "false", "className": "" }, { "id": "ex2_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#b92d5d", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" }, "sizes": { "pagePadding": "12px", "groupGap": "12px", "groupBorderRadius": "4px", "widgetGap": "12px", "density": "default" } }, { "id": "ex2_ui_group", "type": "ui-group", "name": "📈 センサー比較", "page": "ex2_ui_page", "width": "6", "height": "6", "order": 1, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex2_inject_a", "type": "inject", "z": "ex2_tab", "name": "センサーA", "props": [{"p": "payload"},{"p": "topic", "vt": "str"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "センサーA", "payload": "", "payloadType": "date", "x": 130, "y": 80, "wires": [["ex2_func_a"]] }, { "id": "ex2_inject_b", "type": "inject", "z": "ex2_tab", "name": "センサーB", "props": [{"p": "payload"},{"p": "topic", "vt": "str"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.5, "topic": "センサーB", "payload": "", "payloadType": "date", "x": 130, "y": 140, "wires": [["ex2_func_b"]] }, { "id": "ex2_inject_c", "type": "inject", "z": "ex2_tab", "name": "センサーC", "props": [{"p": "payload"},{"p": "topic", "vt": "str"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 1, "topic": "センサーC", "payload": "", "payloadType": "date", "x": 130, "y": 200, "wires": [["ex2_func_c"]] }, { "id": "ex2_func_a", "type": "function", "z": "ex2_tab", "name": "10-30", "func": "msg.payload = Math.floor(Math.random() * 21) + 10;\nreturn msg;", "outputs": 1, "x": 290, "y": 80, "wires": [["ex2_chart"]] }, { "id": "ex2_func_b", "type": "function", "z": "ex2_tab", "name": "20-40", "func": "msg.payload = Math.floor(Math.random() * 21) + 20;\nreturn msg;", "outputs": 1, "x": 290, "y": 140, "wires": [["ex2_chart"]] }, { "id": "ex2_func_c", "type": "function", "z": "ex2_tab", "name": "15-35", "func": "msg.payload = Math.floor(Math.random() * 21) + 15;\nreturn msg;", "outputs": 1, "x": 290, "y": 200, "wires": [["ex2_chart"]] }, { "id": "ex2_clear", "type": "inject", "z": "ex2_tab", "name": "クリア", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "topic": "", "payload": "[]", "payloadType": "json", "x": 130, "y": 260, "wires": [["ex2_chart"]] }, { "id": "ex2_chart", "type": "ui-chart", "z": "ex2_tab", "group": "ex2_ui_group", "name": "センサー比較", "label": "センサー比較チャート", "order": 1, "chartType": "line", "category": "topic", "categoryType": "msg", "xAxisProperty": "", "xAxisPropertyType": "msg", "xAxisType": "time", "xAxisFormat": "", "xAxisFormatType": "auto", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "action": "append", "pointShape": "circle", "pointRadius": 4, "showLegend": true, "removeOlder": 1, "removeOlderUnit": "3600", "removeOlderPoints": "", "colors": ["#b92d5d", "#2196f3", "#4caf50"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "0", "height": "0", "className": "", "x": 470, "y": 140, "wires": [[]] } ]

演習 C-3: カテゴリ別棒グラフとデータクリア上級

📋 課題:

月別の売上データを棒グラフで表示し、データの更新・クリア・アニメーションの切替えができるダッシュボードを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • 起動時に1回だけ実行(once: true)
  • PayloadはFunctionで生成するため空でOK

Functionノード(データ生成):

// 月別売上データを配列で生成 msg.payload = [ { "x": "1月", "y": Math.floor(Math.random() * 50 + 50) }, { "x": "2月", "y": Math.floor(Math.random() * 50 + 50) }, { "x": "3月", "y": Math.floor(Math.random() * 50 + 50) }, { "x": "4月", "y": Math.floor(Math.random() * 50 + 50) }, { "x": "5月", "y": Math.floor(Math.random() * 50 + 50) }, { "x": "6月", "y": Math.floor(Math.random() * 50 + 50) } ]; return msg;

ui-button(クリア):

  • Payload: [](空配列)
  • Payload Type: JSON

ui-chartの設定:

  • Chart Type: Bar
  • X-Axis Type: Categorical
  • X: property "x", Y: property "y"
  • Action: replace(データ更新時に置換)
✅ 解答例フロー
[ { "id": "c3_tab", "type": "tab", "label": "演習 C-3: カテゴリ別棒グラフ", "disabled": false, "info": "" }, { "id": "c3_inject", "type": "inject", "z": "c3_tab", "name": "初期データ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 80, "wires": [["c3_func_data"]] }, { "id": "c3_func_data", "type": "function", "z": "c3_tab", "name": "売上データ生成", "func": "msg.payload = [\n { \"x\": \"1月\", \"y\": Math.floor(Math.random() * 50 + 50) },\n { \"x\": \"2月\", \"y\": Math.floor(Math.random() * 50 + 50) },\n { \"x\": \"3月\", \"y\": Math.floor(Math.random() * 50 + 50) },\n { \"x\": \"4月\", \"y\": Math.floor(Math.random() * 50 + 50) },\n { \"x\": \"5月\", \"y\": Math.floor(Math.random() * 50 + 50) },\n { \"x\": \"6月\", \"y\": Math.floor(Math.random() * 50 + 50) }\n];\nreturn msg;", "outputs": 1, "x": 320, "y": 80, "wires": [["c3_chart"]] }, { "id": "c3_btn_update", "type": "ui-button", "z": "c3_tab", "group": "", "name": "データ更新", "label": "🔄 データ更新", "order": 1, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "", "className": "", "icon": "", "iconPosition": "left", "payload": "", "payloadType": "date", "topic": "update", "topicType": "str", "buttonColor": "", "x": 130, "y": 140, "wires": [["c3_func_data"]] }, { "id": "c3_btn_clear", "type": "ui-button", "z": "c3_tab", "group": "", "name": "クリア", "label": "🗑️ クリア", "order": 2, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "#f44336", "className": "", "icon": "", "iconPosition": "left", "payload": "[]", "payloadType": "json", "topic": "clear", "topicType": "str", "x": 130, "y": 200, "wires": [["c3_chart"]] }, { "id": "c3_chart", "type": "ui-chart", "z": "c3_tab", "group": "", "name": "月別売上", "label": "月別売上(万円)", "order": 3, "chartType": "bar", "category": "", "categoryType": "none", "xAxisProperty": "x", "xAxisPropertyType": "property", "xAxisType": "category", "yAxisProperty": "y", "yAxisPropertyType": "property", "action": "replace", "pointShape": "rect", "pointRadius": 4, "showLegend": false, "removeOlder": 1, "removeOlderUnit": "3600", "colors": ["#b92d5d", "#2196f3", "#4caf50"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "6", "height": "4", "className": "", "x": 520, "y": 120, "wires": [[]] } ]

🎯 gauge ウィジェット演習

演習 G-1: シンプルな温度ゲージ初級

📋 課題:

2秒ごとにランダムな温度値を生成し、3/4ゲージで表示するフローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノード:

  • 繰り返し: 2秒
  • 起動時に1回実行: 有効

Functionノード:

msg.payload = Math.floor(Math.random() * 21) + 15; return msg;

ui-gaugeの設定:

  • Type: 3/4 Gauge
  • Min: 0, Max: 50
  • Units: °C
  • Segments: 0→青, 20→緑, 30→オレンジ, 35→赤
✅ 解答例フロー
[ { "id": "g1_tab", "type": "tab", "label": "演習 G-1: シンプルゲージ", "disabled": false, "info": "" }, { "id": "g1_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false }, { "id": "g1_ui_page", "type": "ui-page", "name": "演習G-1", "ui": "g1_ui_base", "path": "/g1", "icon": "home", "layout": "grid", "theme": "g1_ui_theme", "order": 1, "visible": true, "disabled": false }, { "id": "g1_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#0094ce", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" } }, { "id": "g1_ui_group", "type": "ui-group", "name": "温度ゲージ", "page": "g1_ui_page", "width": "6", "height": "1", "order": 1, "showTitle": true }, { "id": "g1_inject", "type": "inject", "z": "g1_tab", "name": "2秒毎", "props": [{"p": "payload"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 100, "wires": [["g1_function"]] }, { "id": "g1_function", "type": "function", "z": "g1_tab", "name": "温度生成(15-35)", "func": "msg.payload = Math.floor(Math.random() * 21) + 15;\nreturn msg;", "outputs": 1, "x": 330, "y": 100, "wires": [["g1_gauge"]] }, { "id": "g1_gauge", "type": "ui-gauge", "z": "g1_tab", "name": "温度ゲージ", "group": "g1_ui_group", "order": 1, "width": "6", "height": "4", "gtype": "gauge-34", "gstyle": "rounded", "title": "室温", "units": "°C", "icon": "mdi-thermometer", "prefix": "", "suffix": "", "segments": [ {"from": 0, "color": "#2196f3"}, {"from": 20, "color": "#4caf50"}, {"from": 30, "color": "#ff9800"}, {"from": 35, "color": "#f44336"} ], "min": 0, "max": 50, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "className": "", "x": 550, "y": 100, "wires": [] } ]

演習 G-2: 複数タイプのゲージ比較中級

📋 課題:

同じ値を異なるタイプのゲージ(Half, Tile, Battery)で同時に表示し、各タイプの特徴を比較できるダッシュボードを作成してください。

🎯 要求仕様:

💡 ヒント

Inject/Functionノード:

  • C-1と同様の設定でOK
  • Functionの出力を3つのゲージに接続

ui-gaugeの設定:

  • Half: gtype = "gauge-half"
  • Tile: gtype = "gauge-tile"
  • Battery: gtype = "gauge-battery"
✅ 解答例フロー
[ { "id": "g2_tab", "type": "tab", "label": "演習 G-2: ゲージタイプ比較", "disabled": false, "info": "" }, { "id": "g2_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false }, { "id": "g2_ui_page", "type": "ui-page", "name": "演習G-2", "ui": "g2_ui_base", "path": "/g2", "icon": "home", "layout": "grid", "theme": "g2_ui_theme", "order": 1, "visible": true, "disabled": false }, { "id": "g2_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#0094ce", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" } }, { "id": "g2_ui_group", "type": "ui-group", "name": "ゲージ比較", "page": "g2_ui_page", "width": "6", "height": "1", "order": 1, "showTitle": true }, { "id": "g2_inject", "type": "inject", "z": "g2_tab", "name": "2秒毎", "props": [{"p": "payload"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 140, "wires": [["g2_function"]] }, { "id": "g2_function", "type": "function", "z": "g2_tab", "name": "値生成(0-100)", "func": "msg.payload = Math.floor(Math.random() * 101);\nreturn msg;", "outputs": 1, "x": 330, "y": 140, "wires": [["g2_half", "g2_tile", "g2_battery"]] }, { "id": "g2_half", "type": "ui-gauge", "z": "g2_tab", "name": "Half Gauge", "group": "g2_ui_group", "order": 1, "width": "4", "height": "3", "gtype": "gauge-half", "gstyle": "rounded", "title": "Half Gauge", "units": "%", "icon": "", "segments": [ {"from": 0, "color": "#2196f3"}, {"from": 50, "color": "#4caf50"}, {"from": 80, "color": "#f44336"} ], "min": 0, "max": 100, "className": "", "x": 550, "y": 80, "wires": [] }, { "id": "g2_tile", "type": "ui-gauge", "z": "g2_tab", "name": "Tile Gauge", "group": "g2_ui_group", "order": 2, "width": "4", "height": "3", "gtype": "gauge-tile", "gstyle": "rounded", "title": "Tile Gauge", "units": "%", "icon": "mdi-percent", "segments": [ {"from": 0, "color": "#2196f3"}, {"from": 50, "color": "#4caf50"}, {"from": 80, "color": "#f44336"} ], "min": 0, "max": 100, "className": "", "x": 550, "y": 140, "wires": [] }, { "id": "g2_battery", "type": "ui-gauge", "z": "g2_tab", "name": "Battery Gauge", "group": "g2_ui_group", "order": 3, "width": "4", "height": "3", "gtype": "gauge-battery", "gstyle": "rounded", "title": "Battery Gauge", "units": "%", "icon": "", "segments": [ {"from": 0, "color": "#f44336"}, {"from": 20, "color": "#ff9800"}, {"from": 50, "color": "#4caf50"} ], "min": 0, "max": 100, "className": "", "x": 550, "y": 200, "wires": [] } ]

演習 G-3: 動的プロパティ更新ゲージ上級

📋 課題:

msg.ui_updateを使って、値に応じてゲージのラベルやセグメント色を動的に変更するフローを作成してください。

🎯 要求仕様:

💡 ヒント

Inject/Functionノード:

  • 前の演習と同様

Functionノード(動的更新):

let value = Math.floor(Math.random() * 81) + 10; // 10-90 msg.payload = value; if (value < 30) { msg.ui_update = { "label": "低温", "segments": [ { "from": 0, "color": "#90caf9" }, { "from": 20, "color": "#2196f3" } ] }; } else if (value < 60) { msg.ui_update = { "label": "正常", "segments": [ { "from": 0, "color": "#a5d6a7" }, { "from": 40, "color": "#4caf50" } ] }; } else { msg.ui_update = { "label": "高温警告", "segments": [ { "from": 0, "color": "#ffcc80" }, { "from": 60, "color": "#f44336" } ] }; } return msg;
✅ 解答例フロー
[ { "id": "g3_tab", "type": "tab", "label": "演習 G-3: 動的ゲージ", "disabled": false, "info": "" }, { "id": "g3_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false }, { "id": "g3_ui_page", "type": "ui-page", "name": "演習G-3", "ui": "g3_ui_base", "path": "/g3", "icon": "home", "layout": "grid", "theme": "g3_ui_theme", "order": 1, "visible": true, "disabled": false }, { "id": "g3_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#0094ce", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" } }, { "id": "g3_ui_group", "type": "ui-group", "name": "動的ゲージ", "page": "g3_ui_page", "width": "6", "height": "1", "order": 1, "showTitle": true }, { "id": "g3_inject", "type": "inject", "z": "g3_tab", "name": "2秒毎", "props": [{"p": "payload"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 100, "wires": [["g3_function"]] }, { "id": "g3_function", "type": "function", "z": "g3_tab", "name": "動的プロパティ設定", "func": "let value = Math.floor(Math.random() * 81) + 10;\nmsg.payload = value;\n\nif (value < 30) {\n msg.ui_update = {\n \"label\": \"低温\",\n \"segments\": [\n { \"from\": 0, \"color\": \"#90caf9\" },\n { \"from\": 20, \"color\": \"#2196f3\" }\n ]\n };\n} else if (value < 60) {\n msg.ui_update = {\n \"label\": \"正常\",\n \"segments\": [\n { \"from\": 0, \"color\": \"#a5d6a7\" },\n { \"from\": 40, \"color\": \"#4caf50\" }\n ]\n };\n} else {\n msg.ui_update = {\n \"label\": \"高温警告\",\n \"segments\": [\n { \"from\": 0, \"color\": \"#ffcc80\" },\n { \"from\": 60, \"color\": \"#f44336\" }\n ]\n };\n}\nreturn msg;", "outputs": 1, "x": 350, "y": 100, "wires": [["g3_gauge"]] }, { "id": "g3_gauge", "type": "ui-gauge", "z": "g3_tab", "name": "動的ゲージ", "group": "g3_ui_group", "order": 1, "width": "6", "height": "4", "gtype": "gauge-34", "gstyle": "rounded", "title": "状態", "units": "°C", "icon": "mdi-thermometer", "segments": [ {"from": 0, "color": "#4caf50"} ], "min": 0, "max": 100, "className": "", "x": 570, "y": 100, "wires": [] } ]

📋 table ウィジェット演習

演習 TB-1: シンプルなデータ一覧初級

📋 課題:

センサー情報をテーブルで表示するシンプルなフローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノード:

  • Payload Type: JSON
  • 起動時に1回実行
[ {"id": 1, "name": "温度センサー", "value": 25.5, "status": "正常"}, {"id": 2, "name": "湿度センサー", "value": 60, "status": "正常"}, {"id": 3, "name": "圧力センサー", "value": 1013, "status": "警告"}, {"id": 4, "name": "照度センサー", "value": 500, "status": "正常"} ]

ui-tableの設定:

  • Auto Columns: 有効
  • Max Rows: 10
  • Action: replace
✅ 解答例フロー
[ { "id": "tb1_tab", "type": "tab", "label": "演習 TB-1: シンプルテーブル", "disabled": false, "info": "" }, { "id": "tb1_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false }, { "id": "tb1_ui_page", "type": "ui-page", "name": "演習TB-1", "ui": "tb1_ui_base", "path": "/tb1", "icon": "home", "layout": "grid", "theme": "tb1_ui_theme", "order": 1, "visible": true, "disabled": false }, { "id": "tb1_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#0094ce", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" } }, { "id": "tb1_ui_group", "type": "ui-group", "name": "センサー一覧", "page": "tb1_ui_page", "width": "6", "height": "1", "order": 1, "showTitle": true }, { "id": "tb1_inject", "type": "inject", "z": "tb1_tab", "name": "センサーデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "[{\"id\":1,\"name\":\"温度センサー\",\"value\":25.5,\"status\":\"正常\"},{\"id\":2,\"name\":\"湿度センサー\",\"value\":60,\"status\":\"正常\"},{\"id\":3,\"name\":\"圧力センサー\",\"value\":1013,\"status\":\"警告\"},{\"id\":4,\"name\":\"照度センサー\",\"value\":500,\"status\":\"正常\"}]", "payloadType": "json", "x": 150, "y": 100, "wires": [["tb1_table"]] }, { "id": "tb1_table", "type": "ui-table", "z": "tb1_tab", "group": "tb1_ui_group", "name": "センサー一覧", "label": "", "order": 1, "width": "0", "height": "0", "maxrows": 10, "autocols": true, "showSearch": true, "deselect": true, "selectionType": "none", "columns": [], "mobileBreakpoint": "sm", "mobileBreakpointType": "defaults", "action": "replace", "className": "", "x": 370, "y": 100, "wires": [[]] } ]

演習 TB-2: 行選択と詳細表示中級

📋 課題:

複数センサーの状態を一覧表示し、行を選択すると詳細が表示されるダッシュボードを作成してください。

🎯 要求仕様:

💡 ヒント

テーブルデータの形式:

msg.payload = [ { "id": 1, "name": "温度センサー", "value": 25.5, "status": "正常" }, { "id": 2, "name": "湿度センサー", "value": 65, "status": "正常" }, { "id": 3, "name": "圧力センサー", "value": 1013, "status": "警告" } ];

ui-tableの出力:

行を選択すると、その行のオブジェクトがmsg.payloadとして出力されます。

✅ 解答例フロー
[ { "id": "ex3_tab", "type": "tab", "label": "演習3: センサーテーブル", "disabled": false, "info": "" }, { "id": "ex3_ui_base", "type": "ui-base", "name": "My Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 5, "showDisconnectNotification": true, "allowInstall": true }, { "id": "ex3_ui_page", "type": "ui-page", "name": "センサー管理", "ui": "ex3_ui_base", "path": "/exercise3", "icon": "mdi-table", "layout": "grid", "theme": "ex3_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Small Desktop", "px": 768, "cols": 9}, {"name": "Desktop", "px": 1024, "cols": 12} ], "order": 1, "visible": "true", "disabled": "false", "className": "" }, { "id": "ex3_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#b92d5d", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" }, "sizes": { "pagePadding": "12px", "groupGap": "12px", "groupBorderRadius": "4px", "widgetGap": "12px", "density": "default" } }, { "id": "ex3_group_table", "type": "ui-group", "name": "📋 センサー一覧", "page": "ex3_ui_page", "width": "6", "height": "6", "order": 1, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex3_group_detail", "type": "ui-group", "name": "📝 選択詳細", "page": "ex3_ui_page", "width": "6", "height": "3", "order": 2, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex3_inject", "type": "inject", "z": "ex3_tab", "name": "データ更新", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 100, "wires": [["ex3_function"]] }, { "id": "ex3_function", "type": "function", "z": "ex3_tab", "name": "テーブルデータ生成", "func": "msg.payload = [\n { \"id\": 1, \"name\": \"温度センサー\", \"value\": (Math.random() * 10 + 20).toFixed(1), \"status\": \"正常\" },\n { \"id\": 2, \"name\": \"湿度センサー\", \"value\": Math.floor(Math.random() * 30 + 50), \"status\": \"正常\" },\n { \"id\": 3, \"name\": \"圧力センサー\", \"value\": Math.floor(Math.random() * 50 + 990), \"status\": Math.random() > 0.7 ? \"警告\" : \"正常\" },\n { \"id\": 4, \"name\": \"照度センサー\", \"value\": Math.floor(Math.random() * 500 + 100), \"status\": \"正常\" }\n];\nreturn msg;", "outputs": 1, "x": 330, "y": 100, "wires": [["ex3_table"]] }, { "id": "ex3_table", "type": "ui-table", "z": "ex3_tab", "group": "ex3_group_table", "name": "センサー一覧", "label": "", "order": 1, "width": "6", "height": "5", "maxrows": 10, "autocols": true, "showSearch": true, "deselect": true, "selectionType": "click", "columns": [], "mobileBreakpoint": "sm", "mobileBreakpointType": "defaults", "action": "replace", "className": "", "x": 530, "y": 100, "wires": [["ex3_template"]] }, { "id": "ex3_template", "type": "template", "z": "ex3_tab", "name": "詳細フォーマット", "field": "payload", "fieldType": "msg", "format": "text", "syntax": "mustache", "template": "選択: {{name}} (ID: {{id}})\n現在値: {{value}}\n状態: {{status}}", "output": "str", "x": 720, "y": 100, "wires": [["ex3_text"]] }, { "id": "ex3_text", "type": "ui-text", "z": "ex3_tab", "group": "ex3_group_detail", "name": "選択詳細", "label": "選択されたセンサー:", "order": 1, "width": "0", "height": "0", "fontSize": 14, "fontStyle": false, "fontWeight": false, "alignment": "left", "className": "", "x": 910, "y": 100, "wires": [] } ]

演習 TB-3: カスタム列定義とソート上級

📋 課題:

テーブルの列を手動定義し、列幅・表示順序・ソート機能をカスタマイズしたダッシュボードを作成してください。

🎯 要求仕様:

💡 ヒント

カスタム列定義(Columnsプロパティ):

[ { "key": "id", "label": "ID", "width": "60px" }, { "key": "name", "label": "センサー名", "width": "150px" }, { "key": "value", "label": "現在値", "width": "100px", "align": "right" }, { "key": "status", "label": "状態", "width": "80px" } ]

ソート用Functionノード:

// フロー変数でソート順を管理 let sortAsc = flow.get("sortAsc") || true; let data = flow.get("tableData") || []; data.sort((a, b) => sortAsc ? a.value - b.value : b.value - a.value); flow.set("sortAsc", !sortAsc); msg.payload = data; return msg;

データ追加用Functionノード:

// 新しいデータを1件追加 let data = flow.get("tableData") || []; let newId = data.length > 0 ? Math.max(...data.map(d => d.id)) + 1 : 1; msg.payload = [{ "id": newId, "name": "センサー" + newId, "value": Math.floor(Math.random() * 50 + 10), "status": Math.random() > 0.3 ? "正常" : "警告" }]; msg.action = "append"; return msg;

ui-tableの設定:

  • Auto Columns: 無効(手動定義を使用)
  • Columns: 上記のカスタム定義を設定
  • Max Rows: 10
✅ 解答例フロー
[ { "id": "tb3_tab", "type": "tab", "label": "演習 TB-3: カスタムテーブル", "disabled": false, "info": "" }, { "id": "tb3_inject_init", "type": "inject", "z": "tb3_tab", "name": "初期データ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 80, "wires": [["tb3_func_init"]] }, { "id": "tb3_func_init", "type": "function", "z": "tb3_tab", "name": "初期データ生成", "func": "let data = [\n { \"id\": 1, \"name\": \"温度センサー\", \"value\": 25.5, \"status\": \"正常\" },\n { \"id\": 2, \"name\": \"湿度センサー\", \"value\": 60, \"status\": \"正常\" },\n { \"id\": 3, \"name\": \"圧力センサー\", \"value\": 45, \"status\": \"警告\" },\n { \"id\": 4, \"name\": \"照度センサー\", \"value\": 30, \"status\": \"正常\" }\n];\nflow.set(\"tableData\", data);\nflow.set(\"sortAsc\", true);\nmsg.payload = data;\nreturn msg;", "outputs": 1, "x": 330, "y": 80, "wires": [["tb3_table"]] }, { "id": "tb3_btn_sort", "type": "ui-button", "z": "tb3_tab", "group": "", "name": "値でソート", "label": "🔄 値でソート", "order": 1, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "date", "topic": "sort", "x": 130, "y": 140, "wires": [["tb3_func_sort"]] }, { "id": "tb3_func_sort", "type": "function", "z": "tb3_tab", "name": "ソート処理", "func": "let sortAsc = flow.get(\"sortAsc\") || true;\nlet data = flow.get(\"tableData\") || [];\n\ndata.sort((a, b) => sortAsc ? a.value - b.value : b.value - a.value);\nflow.set(\"sortAsc\", !sortAsc);\nflow.set(\"tableData\", data);\n\nmsg.payload = data;\nreturn msg;", "outputs": 1, "x": 330, "y": 140, "wires": [["tb3_table"]] }, { "id": "tb3_btn_add", "type": "ui-button", "z": "tb3_tab", "group": "", "name": "データ追加", "label": "➕ データ追加", "order": 2, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "#4caf50", "className": "", "icon": "", "payload": "", "payloadType": "date", "topic": "add", "x": 130, "y": 200, "wires": [["tb3_func_add"]] }, { "id": "tb3_func_add", "type": "function", "z": "tb3_tab", "name": "データ追加処理", "func": "let data = flow.get(\"tableData\") || [];\nlet newId = data.length > 0 ? Math.max(...data.map(d => d.id)) + 1 : 1;\n\nlet newItem = {\n \"id\": newId,\n \"name\": \"センサー\" + newId,\n \"value\": Math.floor(Math.random() * 50 + 10),\n \"status\": Math.random() > 0.3 ? \"正常\" : \"警告\"\n};\n\ndata.push(newItem);\nflow.set(\"tableData\", data);\n\nmsg.payload = [newItem];\nmsg.action = \"append\";\nreturn msg;", "outputs": 1, "x": 330, "y": 200, "wires": [["tb3_table"]] }, { "id": "tb3_table", "type": "ui-table", "z": "tb3_tab", "group": "", "name": "カスタムテーブル", "label": "", "order": 3, "width": "6", "height": "5", "maxrows": 10, "autocols": false, "showSearch": true, "deselect": true, "selectionType": "none", "columns": [ { "key": "id", "label": "ID", "width": "60px" }, { "key": "name", "label": "センサー名", "width": "150px" }, { "key": "value", "label": "現在値", "width": "100px", "align": "right" }, { "key": "status", "label": "状態", "width": "80px" } ], "mobileBreakpoint": "sm", "mobileBreakpointType": "defaults", "action": "replace", "className": "", "x": 540, "y": 140, "wires": [[]] } ]

📊 progress ウィジェット演習

演習 P-1: シンプルなプログレスバー初級

📋 課題:

スライダーで値を変更すると、リニア(横棒)プログレスバーに反映されるフローを作成してください。

🎯 要求仕様:

💡 ヒント

ui-sliderの設定:

  • Min: 0, Max: 100
  • Step: 1
  • Label: 進捗率

ui-progressの設定:

  • Color: 任意(例: #b92d5d)
✅ 解答例フロー
[ { "id": "p1_tab", "type": "tab", "label": "演習 P-1: シンプルプログレス", "disabled": false, "info": "" }, { "id": "p1_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false }, { "id": "p1_ui_page", "type": "ui-page", "name": "演習P-1", "ui": "p1_ui_base", "path": "/p1", "icon": "home", "layout": "grid", "theme": "p1_ui_theme", "order": 1, "visible": true, "disabled": false }, { "id": "p1_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#0094ce", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" } }, { "id": "p1_ui_group", "type": "ui-group", "name": "プログレス", "page": "p1_ui_page", "width": "6", "height": "1", "order": 1, "showTitle": true }, { "id": "p1_slider", "type": "ui-slider", "z": "p1_tab", "group": "p1_ui_group", "name": "進捗スライダー", "label": "進捗率", "order": 1, "width": "6", "height": "1", "outs": "all", "topic": "", "topicType": "str", "min": 0, "max": 100, "step": 1, "className": "", "x": 150, "y": 100, "wires": [["p1_progress"]] }, { "id": "p1_progress", "type": "ui-progress", "z": "p1_tab", "group": "p1_ui_group", "name": "進捗バー", "order": 2, "width": "6", "height": "1", "color": "#b92d5d", "className": "", "x": 370, "y": 100, "wires": [] } ]

演習 P-2: 円形プログレスと自動更新中級

📋 課題:

処理進捗をシミュレートし、円形プログレスバーで0%から100%まで自動的に進行するフローを作成してください。

🎯 要求仕様:

💡 ヒント

ui-buttonの設定:

  • Label: 🚀 開始
  • Payload: 0(初期値)
  • Payload Type: num

Functionノード(進捗制御):

// 現在の進捗を取得 let progress = flow.get("progress") || 0; // 初期化(ボタンから0が来た場合) if (msg.payload === 0) { progress = 0; } // 10%増加 progress += 10; if (progress > 100) progress = 100; flow.set("progress", progress); msg.payload = progress; // 100%未満なら次のトリガーを送信 if (progress < 100) { node.send([msg, { payload: true }]); } else { node.send([msg, null]); } return null;

Triggerノード(遅延):

  • Action: 0.5秒後にメッセージを送信
  • 出力をFunctionノードに接続(ループ)

ui-progressの設定:

  • Color: 任意
✅ 解答例フロー
[ { "id": "p2_tab", "type": "tab", "label": "演習 P-2: 円形プログレス", "disabled": false, "info": "" }, { "id": "p2_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false }, { "id": "p2_ui_page", "type": "ui-page", "name": "演習P-2", "ui": "p2_ui_base", "path": "/p2", "icon": "home", "layout": "grid", "theme": "p2_ui_theme", "order": 1, "visible": true, "disabled": false }, { "id": "p2_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#0094ce", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" } }, { "id": "p2_ui_group", "type": "ui-group", "name": "円形プログレス", "page": "p2_ui_page", "width": "6", "height": "1", "order": 1, "showTitle": true }, { "id": "p2_btn_start", "type": "ui-button", "z": "p2_tab", "group": "p2_ui_group", "name": "開始", "label": "🚀 開始", "order": 1, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "#4caf50", "className": "", "icon": "", "payload": "0", "payloadType": "num", "topic": "start", "x": 130, "y": 100, "wires": [["p2_func"]] }, { "id": "p2_func", "type": "function", "z": "p2_tab", "name": "進捗制御", "func": "let progress = flow.get(\"progress\") || 0;\n\nif (msg.payload === 0) {\n progress = 0;\n}\n\nprogress += 10;\nif (progress > 100) progress = 100;\n\nflow.set(\"progress\", progress);\nmsg.payload = progress;\n\nif (progress < 100) {\n return [msg, { payload: true }];\n} else {\n return [msg, null];\n}", "outputs": 2, "x": 310, "y": 100, "wires": [["p2_progress"], ["p2_trigger"]] }, { "id": "p2_trigger", "type": "trigger", "z": "p2_tab", "name": "0.5秒待機", "op1": "", "op2": "true", "op1type": "nul", "op2type": "bool", "duration": "500", "extend": false, "overrideDelay": false, "units": "ms", "reset": "", "bytopic": "all", "topic": "topic", "outputs": 1, "x": 320, "y": 160, "wires": [["p2_func"]] }, { "id": "p2_progress", "type": "ui-progress", "z": "p2_tab", "group": "p2_ui_group", "name": "処理進捗", "order": 2, "width": "3", "height": "3", "color": "#2196f3", "className": "", "x": 510, "y": 100, "wires": [] } ]

演習 P-3: 動的色変更プログレス上級

📋 課題:

値に応じてプログレスバーの色が動的に変化するダッシュボードを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノード:

  • 繰り返し: 2秒
  • 起動時に1回実行

Functionノード(色制御):

let value = Math.floor(Math.random() * 101); msg.payload = value; let color; let label; if (value <= 30) { color = "#f44336"; // 赤 label = "危険"; } else if (value <= 70) { color = "#ff9800"; // オレンジ label = "注意"; } else { color = "#4caf50"; // 緑 label = "正常"; } msg.ui_update = { "color": color, "label": label + " (" + value + "%)" }; return msg;

ui-progressの設定:

  • Color: 任意(動的に変更される)
✅ 解答例フロー
[ { "id": "p3_tab", "type": "tab", "label": "演習 P-3: 動的色プログレス", "disabled": false, "info": "" }, { "id": "p3_inject", "type": "inject", "z": "p3_tab", "name": "2秒毎", "props": [{"p": "payload"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 100, "wires": [["p3_func"]] }, { "id": "p3_func", "type": "function", "z": "p3_tab", "name": "色動的変更", "func": "let value = Math.floor(Math.random() * 101);\nmsg.payload = value;\n\nlet color;\nlet label;\n\nif (value <= 30) {\n color = \"#f44336\";\n label = \"危険\";\n} else if (value <= 70) {\n color = \"#ff9800\";\n label = \"注意\";\n} else {\n color = \"#4caf50\";\n label = \"正常\";\n}\n\nmsg.ui_update = {\n \"color\": color,\n \"label\": label + \" (\" + value + \"%)\"\n};\n\nreturn msg;", "outputs": 1, "x": 310, "y": 100, "wires": [["p3_progress"]] }, { "id": "p3_progress", "type": "ui-progress", "z": "p3_tab", "group": "", "name": "動的プログレス", "order": 1, "width": "6", "height": "1", "color": "#4caf50", "className": "", "x": 510, "y": 100, "wires": [] } ]

🏆 総合演習

演習4: 総合ダッシュボード 上級

📋 課題:

工場の環境監視ダッシュボードを作成してください。温度・湿度・消費電力を監視し、異常時にはアラートを表示します。

🎯 要求仕様:

💡 ヒント

アラート判定のFunctionノード:

// 温度が35度を超えた場合 if (msg.payload > 35) { let alert = { "time": new Date().toLocaleTimeString(), "type": "温度異常", "value": msg.payload + "°C" }; // グローバル変数でアラート履歴を管理 let history = global.get("alertHistory") || []; history.unshift(alert); if (history.length > 10) history.pop(); global.set("alertHistory", history); return [msg, { payload: history }, { payload: "温度警告: " + msg.payload + "°C", topic: "alert" }]; } return [msg, null, null];

各ウィジェットの配置:

  • 左上: 温度ゲージ(Tile)
  • 右上: 湿度ゲージ(Battery)
  • 中央: 電力チャート(Bar)
  • 下部: アラート履歴テーブル + 稼働率プログレス
✅ 解答例フロー
[ { "id": "ex4_tab", "type": "tab", "label": "演習4: 総合ダッシュボード", "disabled": false, "info": "" }, { "id": "ex4_ui_base", "type": "ui-base", "name": "My Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 5, "showDisconnectNotification": true, "allowInstall": true }, { "id": "ex4_ui_page", "type": "ui-page", "name": "工場監視", "ui": "ex4_ui_base", "path": "/exercise4", "icon": "mdi-factory", "layout": "grid", "theme": "ex4_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Small Desktop", "px": 768, "cols": 9}, {"name": "Desktop", "px": 1024, "cols": 12} ], "order": 1, "visible": "true", "disabled": "false", "className": "" }, { "id": "ex4_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#b92d5d", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" }, "sizes": { "pagePadding": "12px", "groupGap": "12px", "groupBorderRadius": "4px", "widgetGap": "12px", "density": "default" } }, { "id": "ex4_group_temp", "type": "ui-group", "name": "🌡️ 温度", "page": "ex4_ui_page", "width": "3", "height": "4", "order": 1, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex4_group_humid", "type": "ui-group", "name": "💧 湿度", "page": "ex4_ui_page", "width": "3", "height": "4", "order": 2, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex4_group_power", "type": "ui-group", "name": "⚡ 消費電力", "page": "ex4_ui_page", "width": "6", "height": "5", "order": 3, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex4_group_alert", "type": "ui-group", "name": "🚨 アラート履歴", "page": "ex4_ui_page", "width": "4", "height": "6", "order": 4, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex4_group_status", "type": "ui-group", "name": "📊 システム状態", "page": "ex4_ui_page", "width": "2", "height": "3", "order": 5, "showTitle": true, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { "id": "ex4_inject_temp", "type": "inject", "z": "ex4_tab", "name": "温度データ", "props": [{"p": "payload"}], "repeat": "3", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 80, "wires": [["ex4_func_temp"]] }, { "id": "ex4_func_temp", "type": "function", "z": "ex4_tab", "name": "温度生成(20-40)", "func": "msg.payload = Math.floor(Math.random() * 21) + 20;\nreturn msg;", "outputs": 1, "x": 330, "y": 80, "wires": [["ex4_gauge_temp", "ex4_func_alert"]] }, { "id": "ex4_gauge_temp", "type": "ui-gauge", "z": "ex4_tab", "name": "温度", "group": "ex4_group_temp", "order": 1, "width": "0", "height": "0", "gtype": "gauge-tile", "gstyle": "rounded", "title": "温度", "units": "°C", "icon": "mdi-thermometer", "prefix": "", "suffix": "", "segments": [ {"from": 0, "color": "#2196f3"}, {"from": 25, "color": "#4caf50"}, {"from": 35, "color": "#ff9800"}, {"from": 38, "color": "#f44336"} ], "min": 0, "max": 50, "className": "", "x": 550, "y": 60, "wires": [] }, { "id": "ex4_func_alert", "type": "function", "z": "ex4_tab", "name": "アラート判定", "func": "if (msg.payload > 35) {\n let alert = {\n \"time\": new Date().toLocaleTimeString(),\n \"type\": \"温度異常\",\n \"value\": msg.payload + \"°C\"\n };\n let history = global.get(\"alertHistory\") || [];\n history.unshift(alert);\n if (history.length > 10) history.pop();\n global.set(\"alertHistory\", history);\n return [{ payload: history }, { payload: \"⚠️ 温度警告: \" + msg.payload + \"°C\" }];\n}\nreturn [null, null];", "outputs": 2, "x": 550, "y": 100, "wires": [["ex4_table"], ["ex4_notification"]] }, { "id": "ex4_inject_humid", "type": "inject", "z": "ex4_tab", "name": "湿度データ", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.3, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 160, "wires": [["ex4_func_humid"]] }, { "id": "ex4_func_humid", "type": "function", "z": "ex4_tab", "name": "湿度生成(40-80)", "func": "msg.payload = Math.floor(Math.random() * 41) + 40;\nreturn msg;", "outputs": 1, "x": 330, "y": 160, "wires": [["ex4_gauge_humid"]] }, { "id": "ex4_gauge_humid", "type": "ui-gauge", "z": "ex4_tab", "name": "湿度", "group": "ex4_group_humid", "order": 1, "width": "0", "height": "0", "gtype": "gauge-battery", "gstyle": "rounded", "title": "湿度", "units": "%", "icon": "", "prefix": "", "suffix": "", "segments": [ {"from": 0, "color": "#ff9800"}, {"from": 30, "color": "#4caf50"}, {"from": 70, "color": "#2196f3"} ], "min": 0, "max": 100, "className": "", "x": 550, "y": 160, "wires": [] }, { "id": "ex4_inject_power", "type": "inject", "z": "ex4_tab", "name": "電力データ", "props": [{"p": "payload"},{"p": "topic", "vt": "str"}], "repeat": "4", "crontab": "", "once": true, "onceDelay": 0.5, "topic": "電力", "payload": "", "payloadType": "date", "x": 130, "y": 240, "wires": [["ex4_func_power"]] }, { "id": "ex4_func_power", "type": "function", "z": "ex4_tab", "name": "電力生成(100-200kW)", "func": "msg.payload = Math.floor(Math.random() * 101) + 100;\nreturn msg;", "outputs": 1, "x": 350, "y": 240, "wires": [["ex4_chart_power"]] }, { "id": "ex4_chart_power", "type": "ui-chart", "z": "ex4_tab", "group": "ex4_group_power", "name": "消費電力", "label": "消費電力 (kW)", "order": 1, "chartType": "bar", "category": "topic", "categoryType": "msg", "xAxisProperty": "", "xAxisPropertyType": "msg", "xAxisType": "time", "xAxisFormat": "{HH}:{mm}", "xAxisFormatType": "custom", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "action": "append", "pointShape": "rect", "pointRadius": 4, "showLegend": false, "removeOlder": 1, "removeOlderUnit": "3600", "colors": ["#b92d5d"], "width": "0", "height": "0", "className": "", "x": 570, "y": 240, "wires": [[]] }, { "id": "ex4_table", "type": "ui-table", "z": "ex4_tab", "group": "ex4_group_alert", "name": "アラート履歴", "label": "", "order": 1, "width": "0", "height": "0", "maxrows": 5, "autocols": true, "showSearch": false, "deselect": true, "selectionType": "none", "columns": [], "mobileBreakpoint": "sm", "mobileBreakpointType": "defaults", "action": "replace", "className": "", "x": 750, "y": 80, "wires": [[]] }, { "id": "ex4_notification", "type": "ui-notification", "z": "ex4_tab", "ui": "ex4_ui_base", "name": "アラート通知", "position": "top right", "colorDefault": false, "color": "#f44336", "displayTime": 5, "showCountdown": true, "outputs": 0, "allowDismiss": true, "dismissText": "閉じる", "raw": false, "className": "", "x": 750, "y": 120, "wires": [] }, { "id": "ex4_inject_status", "type": "inject", "z": "ex4_tab", "name": "稼働率", "props": [{"p": "payload"}], "repeat": "10", "crontab": "", "once": true, "onceDelay": 0.2, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 320, "wires": [["ex4_func_status"]] }, { "id": "ex4_func_status", "type": "function", "z": "ex4_tab", "name": "稼働率(85-100)", "func": "msg.payload = Math.floor(Math.random() * 16) + 85;\nreturn msg;", "outputs": 1, "x": 330, "y": 320, "wires": [["ex4_progress"]] }, { "id": "ex4_progress", "type": "ui-progress", "z": "ex4_tab", "group": "ex4_group_status", "name": "稼働率", "order": 1, "width": "0", "height": "0", "color": "#4caf50", "className": "", "x": 530, "y": 320, "wires": [] } ]

✅ 8. まとめ

📊 データ可視化ウィジェットの使い分け

ウィジェット 最適な用途 入力データ形式
chart (Line) 時系列データの推移、トレンド分析 数値 + topic
chart (Bar) カテゴリ別比較、期間別集計 配列 / オブジェクト
chart (Pie) 構成比、割合の表示 配列 / オブジェクト
gauge (3/4, Half) 現在値の直感的表示、しきい値監視 数値
gauge (Tile) コンパクトな数値表示 数値
gauge (Battery, Tank) 残量、レベル表示 数値(%)
table 一覧表示、詳細データ確認 オブジェクト配列
progress 進捗状況、完了率 数値(%)

💡 重要ポイント

🔧 9. トラブルシューティング

問題 原因 解決方法
チャートにデータが表示されない Y軸のプロパティ指定ミス データ構造を確認し、正しいキーを指定
複数のラインが1本にまとまる Seriesの設定不足 msg.topicを設定するか、Seriesでkeyを指定
ゲージの色が変わらない Segmentsの設定ミス from値が昇順になっているか確認
テーブルの列が表示されない データ形式の問題 配列内のオブジェクトにキーが存在するか確認
テーブルのデータが縦に並ぶ mobileBreakpointの設定 mobileBreakpointを「sm」に設定(レスポンシブ表示の切替点)
テーブルに行データが表示されない(ページネーションのみ表示) グループの高さ不足、またはmobileBreakpoint未設定 ui-groupの高さを明示的に設定(例: 6)、mobileBreakpointを「sm」に設定
チャートがリセットされる Action設定がreplace appendに変更するか、msg.actionで制御
X軸の時間表示がおかしい タイムスタンプ形式の問題 ミリ秒単位のUnixタイムスタンプを使用

🏭 10. 実務活用例

ケース1: 工場環境モニタリング

温度・湿度・気圧センサー → chart(時系列推移) → gauge(現在値表示) → 閾値超過時にnotification

ケース2: 生産ライン稼働状況

各設備の稼働率データ → chart(棒グラフで比較) → progress(全体稼働率) → table(設備別詳細)

ケース3: エネルギー管理ダッシュボード

電力・ガス・水道使用量 → chart(時間帯別消費量) → gauge(リアルタイム消費) → chart(円グラフで構成比)

ケース4: IoTセンサーネットワーク

複数センサーからのMQTTデータ → table(センサー一覧・状態) → chart(選択センサーの履歴) → gauge(バッテリー残量)

📚 11. 追加リソース


このガイドが役に立ちましたら、実際のプロジェクトで練習してみてください!
データ可視化はIoTダッシュボードの核心機能です。
サンプルフローの提供元: FlowFuse Dashboard 2.0

🏠