📥 サンプルフロー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": []
}
]
// セグメント設定例
[
{ "from": 0, "color": "#2196f3" }, // 青: 0-30
{ "from": 30, "color": "#4caf50" }, // 緑: 30-60
{ "from": 60, "color": "#ff9800" }, // オレンジ: 60-80
{ "from": 80, "color": "#f44336" } // 赤: 80-100
]
$round(payload, 1) // 小数点1桁に丸める
$round(payload, 0) // 整数に丸める
payload * 100 // パーセント変換
// Columnsプロパティの設定例
[
{ "key": "id", "label": "ID", "width": "60px" },
{ "key": "name", "label": "センサー名", "width": "150px" },
{ "key": "value", "label": "現在値", "width": "100px", "align": "right" },
{ "key": "status", "label": "状態", "width": "80px" }
]
演習 C-2: 複数センサー比較チャート中級
📋 課題:
3つのセンサーからのデータを1つのチャートで比較表示するダッシュボードを作成してください。
🎯 要求仕様:
- 3つのInjectノード: それぞれ異なるtopic(センサーA/B/C)を持つ
- 各センサーで異なる範囲のランダム値を生成
- ui-chart: 3つの折れ線を凡例付きで表示
- クリアボタン: チャートをリセット
💡 ヒント
各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": [[]]
}
]
演習 TB-2: 行選択と詳細表示中級
📋 課題:
複数センサーの状態を一覧表示し、行を選択すると詳細が表示されるダッシュボードを作成してください。
🎯 要求仕様:
- ui-table: センサー一覧(ID、名前、値、状態)を表示
- 行選択時: 選択したセンサーの情報をui-textで表示
- 更新ボタン: テーブルデータを更新
💡 ヒント
テーブルデータの形式:
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": []
}
]
演習4: 総合ダッシュボード 上級
📋 課題:
工場の環境監視ダッシュボードを作成してください。温度・湿度・消費電力を監視し、異常時にはアラートを表示します。
🎯 要求仕様:
- ui-gauge: 温度(Tileタイプ)、湿度(Batteryタイプ)
- ui-chart: 消費電力の時系列グラフ(棒グラフ)
- ui-table: 過去10件のアラート履歴
- ui-progress: システム稼働率
- しきい値超過時: ui-notificationでアラート表示
💡 ヒント
アラート判定の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": []
}
]