📐 Node-RED Dashboard 2.0 Layout機能 ガイド

このガイドでは、Dashboard 2.0のLayout機能について詳しく解説します。レイアウトはダッシュボードの見た目と使い勝手を決定する重要な要素です。

📌 このガイドで学べること:

📑 目次

  1. Layout機能の概要
  2. Dashboard階層構造の理解
  3. サンプルフロー
  4. Grid Layout(グリッドレイアウト)
  5. Fixed Layout(固定レイアウト)
  6. Notebook Layout(ノートブックレイアウト)
  7. Tabs Layout(タブレイアウト)
  8. レスポンシブ設定とブレークポイント
  9. Theme設定によるスペーシング調整
  10. 実践演習
  11. まとめ
  12. トラブルシューティング
  13. 実務での活用例
  14. 追加リソース

🎯 1. Layout機能の概要

🤔 レイアウトって何?

レイアウトは、ダッシュボード上のウィジェットをどのように配置するかを決める仕組みです。 日常生活で例えると、建物の間取り図のようなものです。

🏠 建物の間取りに例えると:

📊 4つのレイアウトタイプ比較

レイアウト 特徴 widthの意味 最適な用途
Grid レスポンシブ対応
画面幅に応じて自動調整
12カラム中の割合
(6 = 50%)
一般的なダッシュボード
監視画面
Fixed 固定ピクセル幅
画面サイズに依存しない
ピクセル値
(90px × width)
産業用HMI
タッチパネル
Notebook 最大幅1024px
中央揃え・縦スクロール
6カラム中の割合 レポート・分析
ストーリーテリング
Tabs 各グループがタブに
切り替え表示
タブ内のカラム数 多機能パネル
設定画面

🏗️ 2. Dashboard階層構造の理解

レイアウトを理解するには、まずDashboard 2.0の階層構造を理解することが重要です。

ui-base(ダッシュボード全体)
パス: /dashboard、ナビゲーションスタイル
ui-page(ページ)
レイアウトタイプ、ブレークポイント設定
ui-group(グループ)
width(カラム数)、height
ウィジェット(Button, Chart等)
width, height(グループ内での配置)

📌 重要なポイント:

🔹 ui-base レイアウト関連プロパティ

プロパティ 説明 デフォルト
Path ダッシュボードのURLパス /dashboard
App Icon アプリケーションアイコン
Header Content ヘッダーに表示するコンテンツ(page等) page
Navigation Style サイドナビゲーションのスタイル default
Title Bar Style タイトルバーのスタイル default
Show Path In Sidebar サイドバーにパスを表示 false
Include Client Data クライアントデータを含める true
Accepts Client Config クライアント設定を受け付けるノードタイプ ["ui-notification", "ui-control"]
Show Reconnect Notification 再接続通知の表示 true
Notification Display Time 通知の表示時間(秒) 1
Show Disconnect Notification 切断通知の表示 true
Allow Install PWAインストールを許可 false

🔹 ui-page レイアウト関連プロパティ

プロパティ 説明 デフォルト
Layout レイアウトタイプ(grid/fixed/notebook/tabs) -(必須)
Icon ナビゲーションに表示するアイコン home
Theme 適用するテーマ default
Breakpoints レスポンシブ対応のブレークポイント設定 4段階(0px:3col, 576px:6col, 768px:9col, 1024px:12col)
Class name CSSクラス名
Visibility ページの表示/非表示 Visible
Interactivity ページの有効/無効 Active

🔹 ui-group レイアウト関連プロパティ

プロパティ 説明 デフォルト
Page 所属するページ -(必須)
Width グループの幅(カラム数) 6
Height グループの高さ 1
Display group name グループタイトルの表示 true
Type グループのタイプ default
Class name CSSクラス名
Visibility グループの表示/非表示 Visible
Interactivity グループの有効/無効 Active

📥 3. サンプルフロー

📥 まずサンプルフローをインポートしましょう!

以下の統合サンプルフローには、すべてのレイアウト機能が含まれています:

一度のインポートで全機能を試せます。実際に動作を確認しながら読み進めましょう。

  1. 下のサンプルフローJSONをコピー
  2. Node-REDエディタで メニュー → 読み込み を選択
  3. JSONをペーストして「読み込み」をクリック
  4. デプロイしてダッシュボードで動作確認
📋 Layout機能 統合サンプルフロー(クリックで展開)

このサンプルには Grid, Fixed, Notebook, Tabs, ブレークポイント設定, Theme比較 の全機能が含まれています(62ノード)

[{"id": "layout_demo_flow", "type": "tab", "label": "Layout Demo", "disabled": false, "info": "Dashboard 2.0 Layout機能のデモフロー"}, {"id": "layout_comment_grid", "type": "comment", "z": "layout_demo_flow", "name": "━━━ 📐 Grid Layout サンプル ━━━", "info": "", "x": 180, "y": 40, "wires": []}, {"id": "grid_inject_1", "type": "inject", "z": "layout_demo_flow", "name": "温度データ", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "$floor($random() * 30 + 10)", "payloadType": "jsonata", "x": 140, "y": 100, "wires": [["grid_gauge_temp"]]}, {"id": "grid_gauge_temp", "type": "ui-gauge", "z": "layout_demo_flow", "name": "温度ゲージ", "group": "layout_grid_group1", "order": 1, "width": "3", "height": "4", "gtype": "gauge-half", "gstyle": "needle", "title": "温度", "units": "℃", "icon": "", "prefix": "", "suffix": "", "segments": [{"from": "0", "color": "#2196f3"}, {"from": "20", "color": "#4caf50"}, {"from": "30", "color": "#ff9800"}, {"from": "40", "color": "#f44336"}], "min": 0, "max": 50, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 350, "y": 100, "wires": []}, {"id": "grid_inject_2", "type": "inject", "z": "layout_demo_flow", "name": "湿度データ", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.2, "topic": "", "payload": "$floor($random() * 60 + 30)", "payloadType": "jsonata", "x": 140, "y": 160, "wires": [["grid_gauge_humid"]]}, {"id": "grid_gauge_humid", "type": "ui-gauge", "z": "layout_demo_flow", "name": "湿度ゲージ", "group": "layout_grid_group1", "order": 2, "width": "3", "height": "4", "gtype": "gauge-half", "gstyle": "needle", "title": "湿度", "units": "%", "icon": "", "prefix": "", "suffix": "", "segments": [{"from": "0", "color": "#ff9800"}, {"from": "40", "color": "#4caf50"}, {"from": "70", "color": "#2196f3"}], "min": 0, "max": 100, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 350, "y": 160, "wires": []}, {"id": "grid_inject_chart", "type": "inject", "z": "layout_demo_flow", "name": "チャートデータ", "props": [{"p": "payload"}], "repeat": "3", "crontab": "", "once": true, "onceDelay": 0.3, "topic": "", "payload": "$floor($random() * 100)", "payloadType": "jsonata", "x": 150, "y": 220, "wires": [["grid_chart"]]}, {"id": "grid_chart", "type": "ui-chart", "z": "layout_demo_flow", "group": "layout_grid_group2", "name": "リアルタイムチャート", "label": "センサー値推移", "order": 1, "chartType": "line", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisProperty": "", "xAxisPropertyType": "msg", "xAxisType": "time", "xAxisFormat": "", "xAxisFormatType": "auto", "yAxisLabel": "", "yAxisProperty": "", "ymin": "", "ymax": "", "action": "append", "stackSeries": false, "pointShape": "circle", "pointRadius": 4, "showLegend": true, "removeOlder": 1, "removeOlderUnit": "3600", "removeOlderPoints": "", "colors": ["#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "6", "height": "4", "className": "", "x": 370, "y": 220, "wires": [[]]}, {"id": "layout_comment_notebook", "type": "comment", "z": "layout_demo_flow", "name": "━━━ 📖 Notebook Layout サンプル ━━━", "info": "", "x": 190, "y": 300, "wires": []}, {"id": "notebook_func_md", "type": "function", "z": "layout_demo_flow", "name": "Markdown生成", "func": "const lines = [\n '# センサーデータレポート',\n '',\n '## 概要',\n '',\n 'このレポートは、IoTセンサーから収集したデータの分析結果を示しています。',\n '',\n '### 測定項目',\n '',\n '- **温度**: 環境温度(℃)',\n '- **湿度**: 相対湿度(%)',\n '- **気圧**: 大気圧(hPa)',\n '',\n '> **注意**: データは5秒ごとに更新されます。'\n];\nmsg.payload = lines.join(String.fromCharCode(10));\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 360, "y": 360, "wires": [["notebook_markdown"]]}, {"id": "notebook_inject_md", "type": "inject", "z": "layout_demo_flow", "name": "起動時", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.5, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 360, "wires": [["notebook_func_md"]]}, {"id": "notebook_markdown", "type": "ui-markdown", "z": "layout_demo_flow", "group": "layout_notebook_group1", "name": "レポートヘッダー", "order": 1, "width": "6", "height": "1", "content": "", "className": "", "x": 570, "y": 360, "wires": [[]]}, {"id": "notebook_inject_table", "type": "inject", "z": "layout_demo_flow", "name": "テーブルデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.6, "topic": "", "payload": "[{\"id\":1,\"sensor\":\"温度センサー\",\"value\":\"25.3℃\",\"status\":\"正常\"},{\"id\":2,\"sensor\":\"湿度センサー\",\"value\":\"60%\",\"status\":\"正常\"},{\"id\":3,\"sensor\":\"気圧センサー\",\"value\":\"1013hPa\",\"status\":\"正常\"}]", "payloadType": "json", "x": 150, "y": 420, "wires": [["notebook_table"]]}, {"id": "notebook_table", "type": "ui-table", "z": "layout_demo_flow", "group": "layout_notebook_group2", "name": "センサー一覧", "label": "センサーステータス", "order": 1, "width": "6", "height": "4", "maxrows": 10, "autocols": true, "columns": [], "className": "", "x": 360, "y": 420, "wires": [[]]}, {"id": "layout_comment_tabs", "type": "comment", "z": "layout_demo_flow", "name": "━━━ 🗂️ Tabs Layout サンプル ━━━", "info": "", "x": 180, "y": 500, "wires": []}, {"id": "tabs_button_1", "type": "ui-button", "z": "layout_demo_flow", "group": "layout_tabs_group1", "name": "LED ON", "label": "LED ON", "order": 1, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "#4caf50", "className": "", "icon": "mdi-lightbulb-on", "iconPosition": "left", "payload": "on", "payloadType": "str", "topic": "led", "topicType": "str", "buttonColor": "", "textColor": "", "iconColor": "", "x": 130, "y": 560, "wires": [["tabs_debug"]]}, {"id": "tabs_button_2", "type": "ui-button", "z": "layout_demo_flow", "group": "layout_tabs_group1", "name": "LED OFF", "label": "LED OFF", "order": 2, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "#f44336", "className": "", "icon": "mdi-lightbulb-off", "iconPosition": "left", "payload": "off", "payloadType": "str", "topic": "led", "topicType": "str", "buttonColor": "", "textColor": "", "iconColor": "", "x": 130, "y": 620, "wires": [["tabs_debug"]]}, {"id": "tabs_slider", "type": "ui-slider", "z": "layout_demo_flow", "group": "layout_tabs_group2", "name": "明るさ調整", "label": "明るさ", "order": 1, "width": "6", "height": "1", "passthru": false, "outs": "end", "topic": "brightness", "topicType": "str", "thumbLabel": "true", "showTicks": "always", "min": 0, "max": 100, "step": 1, "className": "", "iconPrepend": "", "iconAppend": "", "color": "", "colorTrack": "", "colorThumb": "", "x": 140, "y": 680, "wires": [["tabs_debug"]]}, {"id": "tabs_debug", "type": "debug", "z": "layout_demo_flow", "name": "Tabs出力", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 350, "y": 620, "wires": []}, {"id": "layout_ui_base", "type": "ui-base", "name": "Layout Demo Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false}, {"id": "layout_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": "layout_page_grid", "type": "ui-page", "name": "Grid Demo", "ui": "layout_ui_base", "path": "/grid-demo", "icon": "mdi-grid", "layout": "grid", "theme": "layout_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, "className": "", "visible": "true", "disabled": "false"}, {"id": "layout_page_notebook", "type": "ui-page", "name": "Notebook Demo", "ui": "layout_ui_base", "path": "/notebook-demo", "icon": "mdi-notebook", "layout": "notebook", "theme": "layout_ui_theme", "breakpoints": [{"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}], "order": 2, "className": "", "visible": "true", "disabled": "false"}, {"id": "layout_page_tabs", "type": "ui-page", "name": "Tabs Demo", "ui": "layout_ui_base", "path": "/tabs-demo", "icon": "mdi-tab", "layout": "tabs", "theme": "layout_ui_theme", "breakpoints": [{"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}], "order": 3, "className": "", "visible": "true", "disabled": "false"}, {"id": "layout_grid_group1", "type": "ui-group", "name": "📊 センサーゲージ", "page": "layout_page_grid", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "layout_grid_group2", "type": "ui-group", "name": "📈 チャート", "page": "layout_page_grid", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "layout_notebook_group1", "type": "ui-group", "name": "📝 レポートヘッダー", "page": "layout_page_notebook", "width": "6", "height": "-1", "order": 1, "showTitle": false, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "layout_notebook_group2", "type": "ui-group", "name": "📋 データテーブル", "page": "layout_page_notebook", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "layout_tabs_group1", "type": "ui-group", "name": "💡 LED制御", "page": "layout_page_tabs", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "layout_tabs_group2", "type": "ui-group", "name": "🔆 調光設定", "page": "layout_page_tabs", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "layout_comment_fixed", "type": "comment", "z": "layout_demo_flow", "name": "━━━ 📏 Fixed Layout サンプル ━━━", "info": "固定ピクセル幅レイアウト(90px × width)", "x": 180, "y": 580, "wires": []}, {"id": "fixed_inject_status", "type": "inject", "z": "layout_demo_flow", "name": "ステータス更新", "props": [{"p": "payload"}], "repeat": "3", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "{\"line1\": \"稼働中\", \"line2\": \"停止\", \"line3\": \"メンテ中\"}", "payloadType": "json", "x": 160, "y": 640, "wires": [["fixed_func_status"]]}, {"id": "fixed_func_status", "type": "function", "z": "layout_demo_flow", "name": "ステータス分配", "func": "let statuses = ['稼働中', '停止', 'メンテ中', '準備完了'];\nlet colors = ['#4caf50', '#f44336', '#ff9800', '#2196f3'];\nlet idx = Math.floor(Math.random() * statuses.length);\nmsg.payload = statuses[idx];\nmsg.ui_update = { color: colors[idx] };\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 360, "y": 640, "wires": [["fixed_text_status"]]}, {"id": "fixed_text_status", "type": "ui-text", "z": "layout_demo_flow", "group": "layout_fixed_group1", "order": 1, "width": "2", "height": "1", "name": "ライン状態", "label": "ライン1", "format": "{{msg.payload}}", "layout": "row-spread", "style": false, "font": "", "fontSize": 16, "color": "#4caf50", "wrapText": false, "className": "", "value": "payload", "valueType": "msg", "x": 550, "y": 640, "wires": []}, {"id": "fixed_inject_count", "type": "inject", "z": "layout_demo_flow", "name": "生産数更新", "props": [{"p": "payload"}], "repeat": "2", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "$floor($random() * 1000 + 500)", "payloadType": "jsonata", "x": 160, "y": 700, "wires": [["fixed_text_count"]]}, {"id": "fixed_text_count", "type": "ui-text", "z": "layout_demo_flow", "group": "layout_fixed_group1", "order": 2, "width": "2", "height": "1", "name": "生産数", "label": "生産数", "format": "{{msg.payload}} 個", "layout": "row-spread", "style": false, "font": "", "fontSize": 16, "color": "#000000", "wrapText": false, "className": "", "value": "payload", "valueType": "msg", "x": 350, "y": 700, "wires": []}, {"id": "fixed_button_start", "type": "ui-button", "z": "layout_demo_flow", "group": "layout_fixed_group2", "name": "開始ボタン", "label": "開始", "order": 1, "width": "1", "height": "1", "emulateClick": false, "color": "#ffffff", "bgcolor": "#4caf50", "className": "", "icon": "mdi-play", "iconPosition": "left", "payload": "start", "payloadType": "str", "topic": "control", "topicType": "str", "buttonColor": "", "textColor": "", "iconColor": "", "x": 150, "y": 760, "wires": [["fixed_debug"]]}, {"id": "fixed_button_stop", "type": "ui-button", "z": "layout_demo_flow", "group": "layout_fixed_group2", "name": "停止ボタン", "label": "停止", "order": 2, "width": "1", "height": "1", "emulateClick": false, "color": "#ffffff", "bgcolor": "#f44336", "className": "", "icon": "mdi-stop", "iconPosition": "left", "payload": "stop", "payloadType": "str", "topic": "control", "topicType": "str", "buttonColor": "", "textColor": "", "iconColor": "", "x": 150, "y": 820, "wires": [["fixed_debug"]]}, {"id": "fixed_debug", "type": "debug", "z": "layout_demo_flow", "name": "Fixed操作ログ", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 360, "y": 790, "wires": []}, {"id": "layout_page_fixed", "type": "ui-page", "name": "Fixed Demo", "ui": "layout_ui_base", "path": "/fixed-demo", "icon": "mdi-view-grid-outline", "layout": "fixed", "theme": "layout_ui_theme", "breakpoints": [{"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Desktop", "px": 1024, "cols": 12}], "order": 4, "className": "", "visible": "true", "disabled": "false"}, {"id": "layout_fixed_group1", "type": "ui-group", "name": "📊 ステータスモニター", "page": "layout_page_fixed", "width": "4", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "layout_fixed_group2", "type": "ui-group", "name": "🎮 制御パネル", "page": "layout_page_fixed", "width": "2", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "bp_demo_flow", "type": "tab", "label": "Breakpoint Demo", "disabled": false, "info": "カスタムブレークポイント設定のデモ"}, {"id": "bp_comment", "type": "comment", "z": "bp_demo_flow", "name": "━━━ 📱 カスタムブレークポイント ━━━", "info": "5段階のブレークポイント設定例", "x": 200, "y": 40, "wires": []}, {"id": "bp_inject_data", "type": "inject", "z": "bp_demo_flow", "name": "テストデータ", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "$floor($random() * 100)", "payloadType": "jsonata", "x": 150, "y": 100, "wires": [["bp_gauge"]]}, {"id": "bp_gauge", "type": "ui-gauge", "z": "bp_demo_flow", "name": "レスポンシブゲージ", "group": "bp_group1", "order": 1, "width": "3", "height": "3", "gtype": "gauge-half", "gstyle": "needle", "title": "値", "units": "%", "icon": "", "prefix": "", "suffix": "", "segments": [{"from": "0", "color": "#4caf50"}, {"from": "50", "color": "#ff9800"}, {"from": "80", "color": "#f44336"}], "min": 0, "max": 100, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 370, "y": 100, "wires": []}, {"id": "bp_text_info", "type": "ui-text", "z": "bp_demo_flow", "group": "bp_group2", "order": 1, "width": "6", "height": "1", "name": "画面幅情報", "label": "説明", "format": "画面幅を変更してカラム数の変化を確認してください", "layout": "row-spread", "style": false, "font": "", "fontSize": 14, "color": "#666666", "wrapText": false, "className": "", "value": "payload", "valueType": "msg", "x": 360, "y": 160, "wires": []}, {"id": "bp_page", "type": "ui-page", "name": "ブレークポイントデモ", "ui": "layout_ui_base", "path": "/breakpoint-demo", "icon": "mdi-cellphone-link", "layout": "grid", "theme": "layout_ui_theme", "breakpoints": [{"name": "Mobile S", "px": 0, "cols": 3}, {"name": "Mobile L", "px": 480, "cols": 6}, {"name": "Tablet", "px": 768, "cols": 9}, {"name": "Desktop", "px": 1024, "cols": 12}, {"name": "Large", "px": 1440, "cols": 18}], "order": 1, "className": "", "visible": "true", "disabled": "false"}, {"id": "bp_group1", "type": "ui-group", "name": "📊 ゲージ(width: 3)", "page": "bp_page", "width": "3", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "bp_group2", "type": "ui-group", "name": "📝 情報(width: 6)", "page": "bp_page", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "theme_demo_flow", "type": "tab", "label": "Theme Demo", "disabled": false, "info": "Theme設定によるスペーシング調整のデモ"}, {"id": "theme_comment", "type": "comment", "z": "theme_demo_flow", "name": "━━━ 🎨 Theme スペーシング比較 ━━━", "info": "コンパクトとゆったりの2種類", "x": 200, "y": 40, "wires": []}, {"id": "theme_inject_1", "type": "inject", "z": "theme_demo_flow", "name": "データ1", "props": [{"p": "payload"}], "repeat": "3", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "$floor($random() * 50 + 20)", "payloadType": "jsonata", "x": 130, "y": 100, "wires": [["theme_gauge_compact"]]}, {"id": "theme_gauge_compact", "type": "ui-gauge", "z": "theme_demo_flow", "name": "コンパクトゲージ", "group": "theme_compact_group", "order": 1, "width": "3", "height": "3", "gtype": "gauge-half", "gstyle": "needle", "title": "温度", "units": "℃", "icon": "", "prefix": "", "suffix": "", "segments": [{"from": "0", "color": "#2196f3"}, {"from": "30", "color": "#4caf50"}, {"from": "40", "color": "#f44336"}], "min": 0, "max": 60, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 350, "y": 100, "wires": []}, {"id": "theme_inject_2", "type": "inject", "z": "theme_demo_flow", "name": "データ2", "props": [{"p": "payload"}], "repeat": "3", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "$floor($random() * 50 + 20)", "payloadType": "jsonata", "x": 130, "y": 180, "wires": [["theme_gauge_spacious"]]}, {"id": "theme_gauge_spacious", "type": "ui-gauge", "z": "theme_demo_flow", "name": "ゆったりゲージ", "group": "theme_spacious_group", "order": 1, "width": "3", "height": "3", "gtype": "gauge-half", "gstyle": "needle", "title": "温度", "units": "℃", "icon": "", "prefix": "", "suffix": "", "segments": [{"from": "0", "color": "#2196f3"}, {"from": "30", "color": "#4caf50"}, {"from": "40", "color": "#f44336"}], "min": 0, "max": 60, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 350, "y": 180, "wires": []}, {"id": "theme_compact", "type": "ui-theme", "name": "Compact Theme", "colors": {"surface": "#ffffff", "primary": "#e91e63", "bgPage": "#fafafa", "groupBg": "#ffffff", "groupOutline": "#e0e0e0"}, "sizes": {"pagePadding": "4px", "groupGap": "4px", "groupBorderRadius": "2px", "widgetGap": "4px", "density": "default"}}, {"id": "theme_spacious", "type": "ui-theme", "name": "Spacious Theme", "colors": {"surface": "#ffffff", "primary": "#009688", "bgPage": "#eceff1", "groupBg": "#ffffff", "groupOutline": "#b0bec5"}, "sizes": {"pagePadding": "24px", "groupGap": "24px", "groupBorderRadius": "12px", "widgetGap": "16px", "density": "default"}}, {"id": "theme_page_compact", "type": "ui-page", "name": "コンパクト表示", "ui": "layout_ui_base", "path": "/compact", "icon": "mdi-view-compact", "layout": "grid", "theme": "theme_compact", "breakpoints": [{"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Desktop", "px": 1024, "cols": 12}], "order": 1, "className": "", "visible": "true", "disabled": "false"}, {"id": "theme_page_spacious", "type": "ui-page", "name": "ゆったり表示", "ui": "layout_ui_base", "path": "/spacious", "icon": "mdi-view-comfy", "layout": "grid", "theme": "theme_spacious", "breakpoints": [{"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Desktop", "px": 1024, "cols": 12}], "order": 2, "className": "", "visible": "true", "disabled": "false"}, {"id": "theme_compact_group", "type": "ui-group", "name": "📊 コンパクト(4px gap)", "page": "theme_page_compact", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}, {"id": "theme_spacious_group", "type": "ui-group", "name": "📊 ゆったり(24px gap)", "page": "theme_page_spacious", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false}]

📐 4. Grid Layout(グリッドレイアウト)

レスポンシブ Grid Layout

Bootstrapスタイルの12カラムグリッドシステムです。画面幅に応じてウィジェットの配置が自動調整されます。最も汎用的なレイアウトで、デフォルト設定です。

📊 12カラムグリッドの仕組み

width: 12(100%)
width: 6(50%)
width: 6(50%)
width: 4
width: 4
width: 4
3
3
3
3

📋 主要プロパティ

プロパティ 説明 デフォルト
Group Width グループが占める列数(1-12) 6
Widget Width ウィジェットがグループ内で占める列数 グループ幅と同じ
Breakpoints 画面幅ごとの列数設定 0px:3, 576px:6, 768px:9, 1024px:12

使用パターン: 監視ダッシュボード

センサーゲージとチャートを横並びに配置する典型的な監視画面です。

ui-page
layout: grid
ui-group
width: 6
gauge
width: 3
// グループ設定 Width: 6 (画面の50%) // ウィジェット設定(グループ内) Gauge Width: 3 (グループの50% = 画面の25%) // 結果: 2つのゲージがグループ内に横並び

💡 行の高さについて:

📏 5. Fixed Layout(固定レイアウト)

固定幅 Fixed Layout

ウィジェットを固定ピクセル幅で配置します。画面サイズが変わっても配置が変わらないため、産業用HMIやタッチパネルに最適です。

📋 ピクセル計算

width値 ピクセル幅 計算式
1 90px 90 × 1
3 270px 90 × 3
6 540px 90 × 6
12 1080px 90 × 12

⚠️ モバイル対応について:

576px以下の画面幅では、Fixed LayoutはGridモードに自動切り替えされます。これはモバイルデバイスでの操作性を確保するための仕様です。

使用パターン: 産業用HMI

タッチパネル用の固定サイズボタン配置です。

ui-page
layout: fixed
ui-group
width: 6
button
width: 2
// グループ設定 Width: 6 → 540px (90 × 6) // ボタン設定 Width: 2 → 180px (90 × 2) // 結果: 540px幅のグループ内に180pxのボタンを3つ配置可能

📖 6. Notebook Layout(ノートブックレイアウト)

ストーリーテリング Notebook Layout

Jupyter NotebookやObservableHQスタイルの中央揃えレイアウトです。最大幅1024pxで、コンテンツを上から下へ順番に読ませるUIに最適です。

📋 主要特性

特性 説明
最大幅 1024px これ以上は広がらない
配置 中央揃え 左右に余白ができる
デフォルト列数 6 Grid同様の列システム
グループ配置 縦積み 上から下へスクロール

使用パターン: データ分析レポート

Markdownでの説明文と、Tableでのデータ表示を組み合わせた分析レポートです。

ui-page
layout: notebook
ui-group markdown
ui-page
layout: notebook
ui-group table
// Notebookレイアウトの最適な組み合わせ 1. markdown: レポートの見出し・説明文 2. chart: データビジュアライゼーション 3. table: 詳細データの表形式表示 // グループは縦に積み重なり、スクロールで順に読める

💡 Notebookに最適なウィジェット:

🗂️ 7. Tabs Layout(タブレイアウト)

タブ切替 Tabs Layout

各グループを独立したタブとして表示します。グループ間の切り替えがタブクリックで行えるため、多機能パネルや設定画面に最適です。

📋 主要特性

特性 説明
タブ幅 画面全幅 各タブは常に100%幅
グループwidth タブ内の列数 タブ内でのウィジェット配置に使用
初期表示 最初のタブ 特定タブへの直接ナビゲートは不可

⚠️ Tabsレイアウトの制限:

使用パターン: 設定パネル

LED制御と調光設定を別タブに分離した設定画面です。

ui-page
layout: tabs
ui-group
💡 LED制御
ON/OFF
ui-page
layout: tabs
ui-group
🔆 調光設定
明るさ
// グループ名がタブのラベルになる グループ1: "💡 LED制御" → タブ1 グループ2: "🔆 調光設定" → タブ2 // タブ内のカラム数はグループのwidthで決定 Width: 6 → タブ内は6カラムグリッド

📱 8. レスポンシブ設定とブレークポイント

🎯 ブレークポイントとは?

ブレークポイントは、画面幅に応じてカラム数を変更するための設定です。これにより、デスクトップでは12カラム、タブレットでは6カラム、スマホでは3カラムといった対応が可能です。

📋 デフォルトブレークポイント

名前 画面幅 カラム数 対象デバイス
Default 0px〜 3 スマートフォン
Tablet 576px〜 6 タブレット
Small Desktop 768px〜 9 小型PC
Desktop 1024px〜 12 デスクトップPC

💡 ブレークポイント設定のコツ:

// ブレークポイント設定例(JSON形式) "breakpoints": [ {"name": "Mobile", "px": 0, "cols": 3}, {"name": "Tablet", "px": 600, "cols": 6}, {"name": "Desktop", "px": 1200, "cols": 12} ] // width: 6のグループの表示 // Mobile (0-599px): 3カラム全幅表示 // Tablet (600-1199px): 6カラムの100%表示 // Desktop (1200px〜): 12カラムの50%表示

🎨 9. Theme設定によるスペーシング調整

📐 スペーシング設定

ui-themeでページ全体の余白やグループ間隔を調整できます。

設定項目 説明 デフォルト
Page Padding ページ全体の外側余白 12px
Group Gap グループ間の間隔 12px
Group Border Radius グループの角丸 4px
Widget Gap ウィジェット間の間隔 12px
Density 行の高さ密度設定 default
// ui-theme設定例(JSON形式) "sizes": { "pagePadding": "16px", // ページ余白を広く "groupGap": "8px", // グループ間隔を狭く "groupBorderRadius": "8px", // 角丸を大きく "widgetGap": "8px" // ウィジェット間隔を狭く }

🏋️ 10. 実践演習

演習1: Grid Layoutでセンサーダッシュボード初級

📝 課題:

Grid Layoutを使用して、温度と湿度のゲージを横並びに配置してください。

🎯 要求仕様:

📊 期待される結果:

💡 ヒント

ui-pageの設定:

  • Layout: Grid
  • Breakpoints: デフォルトのままでOK

ui-groupの設定:

  • Width: 6
  • Show Title: true(グループ名を表示)

ui-gaugeの設定:

  • Width: 3(グループの半分)
  • Height: 4
  • Type: Half Gauge

Injectノードの設定:

  • Repeat: interval 5 seconds
  • Payload: JSONata式 $floor($random() * 50)
✅ 解答例フロー
[ { "id": "ex1_tab", "type": "tab", "label": "演習1: Grid Layout", "disabled": false, "info": "" }, { "id": "ex1_inject_temp", "type": "inject", "z": "ex1_tab", "name": "温度データ", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "$floor($random() * 30 + 15)", "payloadType": "jsonata", "x": 140, "y": 100, "wires": [["ex1_gauge_temp"]] }, { "id": "ex1_inject_humid", "type": "inject", "z": "ex1_tab", "name": "湿度データ", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.2, "topic": "", "payload": "$floor($random() * 40 + 40)", "payloadType": "jsonata", "x": 140, "y": 160, "wires": [["ex1_gauge_humid"]] }, { "id": "ex1_gauge_temp", "type": "ui-gauge", "z": "ex1_tab", "name": "温度", "group": "ex1_group", "order": 1, "width": "3", "height": "4", "gtype": "gauge-half", "gstyle": "needle", "title": "温度", "units": "℃", "icon": "", "prefix": "", "suffix": "", "segments": [ {"from": "0", "color": "#2196f3"}, {"from": "20", "color": "#4caf50"}, {"from": "30", "color": "#ff9800"} ], "min": 0, "max": 50, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 340, "y": 100, "wires": [] }, { "id": "ex1_gauge_humid", "type": "ui-gauge", "z": "ex1_tab", "name": "湿度", "group": "ex1_group", "order": 2, "width": "3", "height": "4", "gtype": "gauge-half", "gstyle": "needle", "title": "湿度", "units": "%", "icon": "", "prefix": "", "suffix": "", "segments": [ {"from": "0", "color": "#ff9800"}, {"from": "40", "color": "#4caf50"}, {"from": "70", "color": "#2196f3"} ], "min": 0, "max": 100, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 340, "y": 160, "wires": [] }, { "id": "ex1_ui_base", "type": "ui-base", "name": "Exercise Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false }, { "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_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": "Desktop", "px": 1024, "cols": 12} ], "order": 1, "className": "", "visible": "true", "disabled": "false" }, { "id": "ex1_group", "type": "ui-group", "name": "📊 センサー値", "page": "ex1_page", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false } ]

演習2: Notebook Layoutでレポート作成中級

📝 課題:

Notebook Layoutを使用して、Markdownヘッダーとデータテーブルを縦並びに配置してください。

🎯 要求仕様:

💡 ヒント

ui-markdownの設定:

  • Functionノードで改行を含むMarkdown文字列を生成
  • 改行は String.fromCharCode(10) または array.join('\n') で生成

ui-tableの設定:

  • Auto Columns: true(自動列生成)
  • 入力データは配列形式のJSON
✅ 解答例フロー
[ { "id": "ex2_tab", "type": "tab", "label": "演習2: Notebook Layout", "disabled": false, "info": "" }, { "id": "ex2_inject_md", "type": "inject", "z": "ex2_tab", "name": "起動時", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.5, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 100, "wires": [["ex2_func_md"]] }, { "id": "ex2_func_md", "type": "function", "z": "ex2_tab", "name": "Markdown生成", "func": "const lines = [\n '# 📊 センサーレポート',\n '',\n '## 概要',\n '',\n '本レポートは、IoTセンサーから収集したデータを分析した結果です。',\n '',\n '### 測定期間',\n '',\n '- **開始**: 2024年1月1日',\n '- **終了**: 2024年1月31日',\n '',\n '> 以下のテーブルに詳細データを示します。'\n];\nmsg.payload = lines.join(String.fromCharCode(10));\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 320, "y": 100, "wires": [["ex2_markdown"]] }, { "id": "ex2_markdown", "type": "ui-markdown", "z": "ex2_tab", "group": "ex2_group1", "name": "レポートヘッダー", "order": 1, "width": "6", "height": "1", "content": "", "className": "", "x": 530, "y": 100, "wires": [[]] }, { "id": "ex2_inject_table", "type": "inject", "z": "ex2_tab", "name": "テーブルデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.6, "topic": "", "payload": "[{\"センサーID\":\"S001\",\"種類\":\"温度\",\"平均値\":\"23.5℃\",\"最大値\":\"28.2℃\",\"最小値\":\"18.1℃\"},{\"センサーID\":\"S002\",\"種類\":\"湿度\",\"平均値\":\"55%\",\"最大値\":\"72%\",\"最小値\":\"38%\"},{\"センサーID\":\"S003\",\"種類\":\"気圧\",\"平均値\":\"1013hPa\",\"最大値\":\"1025hPa\",\"最小値\":\"998hPa\"}]", "payloadType": "json", "x": 150, "y": 180, "wires": [["ex2_table"]] }, { "id": "ex2_table", "type": "ui-table", "z": "ex2_tab", "group": "ex2_group2", "name": "センサーデータ", "label": "センサー統計", "order": 1, "width": "6", "height": "4", "maxrows": 10, "autocols": true, "columns": [], "className": "", "x": 360, "y": 180, "wires": [[]] }, { "id": "ex2_ui_base", "type": "ui-base", "name": "Exercise Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false }, { "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_page", "type": "ui-page", "name": "レポート", "ui": "ex2_ui_base", "path": "/exercise2", "icon": "mdi-file-document", "layout": "notebook", "theme": "ex2_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6} ], "order": 1, "className": "", "visible": "true", "disabled": "false" }, { "id": "ex2_group1", "type": "ui-group", "name": "📝 ヘッダー", "page": "ex2_page", "width": "6", "height": "-1", "order": 1, "showTitle": false, "groupType": "default", "className": "", "visible": true, "disabled": false }, { "id": "ex2_group2", "type": "ui-group", "name": "📋 データ", "page": "ex2_page", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false } ]

演習3: Tabs Layoutで設定画面中級

📝 課題:

Tabs Layoutを使用して、「デバイス制御」と「通知設定」の2つのタブを持つ設定画面を作成してください。

🎯 要求仕様:

💡 ヒント

Tabsレイアウトのポイント:

  • グループ名がタブのラベルになる
  • グループのOrder設定でタブの順番を制御
  • 各グループのwidthがタブ内のカラム数を決定
✅ 解答例フロー
[ { "id": "ex3_tab", "type": "tab", "label": "演習3: Tabs Layout", "disabled": false, "info": "" }, { "id": "ex3_button_on", "type": "ui-button", "z": "ex3_tab", "group": "ex3_group1", "name": "電源ON", "label": "電源 ON", "order": 1, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "#4caf50", "className": "", "icon": "mdi-power", "iconPosition": "left", "payload": "on", "payloadType": "str", "topic": "power", "topicType": "str", "x": 130, "y": 100, "wires": [["ex3_debug"]] }, { "id": "ex3_button_off", "type": "ui-button", "z": "ex3_tab", "group": "ex3_group1", "name": "電源OFF", "label": "電源 OFF", "order": 2, "width": "3", "height": "1", "emulateClick": false, "color": "", "bgcolor": "#f44336", "className": "", "icon": "mdi-power-off", "iconPosition": "left", "payload": "off", "payloadType": "str", "topic": "power", "topicType": "str", "x": 130, "y": 160, "wires": [["ex3_debug"]] }, { "id": "ex3_slider", "type": "ui-slider", "z": "ex3_tab", "group": "ex3_group1", "name": "レベル", "label": "出力レベル", "order": 3, "width": "6", "height": "1", "passthru": false, "outs": "end", "topic": "level", "topicType": "str", "thumbLabel": "true", "showTicks": "always", "min": 0, "max": 100, "step": 10, "className": "", "iconPrepend": "", "iconAppend": "", "color": "", "colorTrack": "", "colorThumb": "", "x": 120, "y": 220, "wires": [["ex3_debug"]] }, { "id": "ex3_switch", "type": "ui-switch", "z": "ex3_tab", "group": "ex3_group2", "name": "通知ON/OFF", "label": "通知を有効にする", "order": 1, "width": "6", "height": "1", "passthru": false, "decouple": false, "topic": "notification", "topicType": "str", "onvalue": "true", "onvalueType": "bool", "offvalue": "false", "offvalueType": "bool", "onicon": "", "officon": "", "oncolor": "", "offcolor": "", "className": "", "x": 150, "y": 300, "wires": [["ex3_debug"]] }, { "id": "ex3_dropdown", "type": "ui-dropdown", "z": "ex3_tab", "group": "ex3_group2", "name": "通知方法", "label": "通知方法", "order": 2, "width": "6", "height": "1", "passthru": false, "multiple": false, "chips": false, "clearable": false, "options": [ {"label": "メール", "value": "email", "type": "str"}, {"label": "Slack", "value": "slack", "type": "str"}, {"label": "LINE", "value": "line", "type": "str"} ], "topic": "method", "topicType": "str", "className": "", "x": 140, "y": 360, "wires": [["ex3_debug"]] }, { "id": "ex3_debug", "type": "debug", "z": "ex3_tab", "name": "設定出力", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "auto", "x": 350, "y": 220, "wires": [] }, { "id": "ex3_ui_base", "type": "ui-base", "name": "Exercise Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false }, { "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_page", "type": "ui-page", "name": "設定画面", "ui": "ex3_ui_base", "path": "/exercise3", "icon": "mdi-cog", "layout": "tabs", "theme": "ex3_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6} ], "order": 1, "className": "", "visible": "true", "disabled": "false" }, { "id": "ex3_group1", "type": "ui-group", "name": "⚡ デバイス制御", "page": "ex3_page", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false }, { "id": "ex3_group2", "type": "ui-group", "name": "🔔 通知設定", "page": "ex3_page", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false } ]

演習4: 複合レイアウトダッシュボード上級

📝 課題:

複数のページを作成し、各ページで異なるレイアウトを使用した総合ダッシュボードを構築してください。

🎯 要求仕様:

💡 ヒント

複数ページのポイント:

  • すべてのページは同じui-baseを参照
  • 各ページのpathは一意に設定(/monitor, /report, /settings等)
  • サイドバーにはページのiconとnameが表示される
  • ページのOrderで表示順を制御
✅ 解答例フロー
[ { "id": "ex4_tab", "type": "tab", "label": "演習4: 複合ダッシュボード", "disabled": false, "info": "" }, { "id": "ex4_comment1", "type": "comment", "z": "ex4_tab", "name": "━━━ ページ1: 監視(Grid) ━━━", "info": "", "x": 170, "y": 40, "wires": [] }, { "id": "ex4_inject_temp", "type": "inject", "z": "ex4_tab", "name": "温度", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "$floor($random() * 30 + 15)", "payloadType": "jsonata", "x": 110, "y": 100, "wires": [["ex4_gauge_temp"]] }, { "id": "ex4_gauge_temp", "type": "ui-gauge", "z": "ex4_tab", "name": "温度", "group": "ex4_group_monitor1", "order": 1, "width": "3", "height": "4", "gtype": "gauge-half", "gstyle": "needle", "title": "温度", "units": "℃", "icon": "", "prefix": "", "suffix": "", "segments": [{"from": "0", "color": "#2196f3"}, {"from": "25", "color": "#4caf50"}, {"from": "35", "color": "#f44336"}], "min": 0, "max": 50, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 290, "y": 100, "wires": [] }, { "id": "ex4_inject_humid", "type": "inject", "z": "ex4_tab", "name": "湿度", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.2, "topic": "", "payload": "$floor($random() * 40 + 40)", "payloadType": "jsonata", "x": 110, "y": 160, "wires": [["ex4_gauge_humid"]] }, { "id": "ex4_gauge_humid", "type": "ui-gauge", "z": "ex4_tab", "name": "湿度", "group": "ex4_group_monitor1", "order": 2, "width": "3", "height": "4", "gtype": "gauge-half", "gstyle": "needle", "title": "湿度", "units": "%", "icon": "", "prefix": "", "suffix": "", "segments": [{"from": "0", "color": "#ff9800"}, {"from": "40", "color": "#4caf50"}, {"from": "70", "color": "#2196f3"}], "min": 0, "max": 100, "sizeThickness": 16, "sizeGap": 4, "sizeKeyThickness": 8, "styleRounded": true, "styleGlow": false, "className": "", "x": 290, "y": 160, "wires": [] }, { "id": "ex4_inject_chart", "type": "inject", "z": "ex4_tab", "name": "チャート", "props": [{"p": "payload"}], "repeat": "3", "crontab": "", "once": true, "onceDelay": 0.3, "topic": "", "payload": "$floor($random() * 100)", "payloadType": "jsonata", "x": 120, "y": 220, "wires": [["ex4_chart"]] }, { "id": "ex4_chart", "type": "ui-chart", "z": "ex4_tab", "group": "ex4_group_monitor2", "name": "推移", "label": "センサー推移", "order": 1, "chartType": "line", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisProperty": "", "xAxisPropertyType": "msg", "xAxisType": "time", "xAxisFormat": "", "xAxisFormatType": "auto", "yAxisLabel": "", "yAxisProperty": "", "ymin": "", "ymax": "", "action": "append", "stackSeries": false, "pointShape": "circle", "pointRadius": 4, "showLegend": true, "removeOlder": 1, "removeOlderUnit": "3600", "removeOlderPoints": "", "colors": ["#1f77b4"], "textColor": ["#666666"], "textColorDefault": true, "gridColor": ["#e5e5e5"], "gridColorDefault": true, "width": "6", "height": "4", "className": "", "x": 290, "y": 220, "wires": [[]] }, { "id": "ex4_comment2", "type": "comment", "z": "ex4_tab", "name": "━━━ ページ2: レポート(Notebook) ━━━", "info": "", "x": 190, "y": 300, "wires": [] }, { "id": "ex4_inject_md", "type": "inject", "z": "ex4_tab", "name": "Markdown", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.5, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 360, "wires": [["ex4_func_md"]] }, { "id": "ex4_func_md", "type": "function", "z": "ex4_tab", "name": "Markdown生成", "func": "const lines = [\n '# 📊 システムレポート',\n '',\n '## 本日のサマリー',\n '',\n '- センサー正常稼働中',\n '- 異常値検出なし',\n '- 次回メンテナンス: 来月1日'\n];\nmsg.payload = lines.join(String.fromCharCode(10));\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 320, "y": 360, "wires": [["ex4_markdown"]] }, { "id": "ex4_markdown", "type": "ui-markdown", "z": "ex4_tab", "group": "ex4_group_report", "name": "レポート", "order": 1, "width": "6", "height": "1", "content": "", "className": "", "x": 520, "y": 360, "wires": [[]] }, { "id": "ex4_comment3", "type": "comment", "z": "ex4_tab", "name": "━━━ ページ3: 設定(Tabs) ━━━", "info": "", "x": 170, "y": 440, "wires": [] }, { "id": "ex4_switch", "type": "ui-switch", "z": "ex4_tab", "group": "ex4_group_settings1", "name": "自動運転", "label": "自動運転モード", "order": 1, "width": "6", "height": "1", "passthru": false, "decouple": false, "topic": "auto", "topicType": "str", "onvalue": "true", "onvalueType": "bool", "offvalue": "false", "offvalueType": "bool", "onicon": "", "officon": "", "oncolor": "", "offcolor": "", "className": "", "x": 130, "y": 500, "wires": [["ex4_debug"]] }, { "id": "ex4_text_input", "type": "ui-text-input", "z": "ex4_tab", "group": "ex4_group_settings2", "name": "ユーザー名", "label": "ユーザー名", "order": 1, "width": "6", "height": "1", "topic": "username", "topicType": "str", "mode": "text", "delay": 300, "passthru": false, "sendOnBlur": true, "sendOnEnter": true, "className": "", "x": 150, "y": 560, "wires": [["ex4_debug"]] }, { "id": "ex4_debug", "type": "debug", "z": "ex4_tab", "name": "設定出力", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 350, "y": 530, "wires": [] }, { "id": "ex4_ui_base", "type": "ui-base", "name": "総合Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false }, { "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_page_monitor", "type": "ui-page", "name": "監視", "ui": "ex4_ui_base", "path": "/monitor", "icon": "mdi-monitor-dashboard", "layout": "grid", "theme": "ex4_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6}, {"name": "Desktop", "px": 1024, "cols": 12} ], "order": 1, "className": "", "visible": "true", "disabled": "false" }, { "id": "ex4_page_report", "type": "ui-page", "name": "レポート", "ui": "ex4_ui_base", "path": "/report", "icon": "mdi-file-document", "layout": "notebook", "theme": "ex4_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6} ], "order": 2, "className": "", "visible": "true", "disabled": "false" }, { "id": "ex4_page_settings", "type": "ui-page", "name": "設定", "ui": "ex4_ui_base", "path": "/settings", "icon": "mdi-cog", "layout": "tabs", "theme": "ex4_ui_theme", "breakpoints": [ {"name": "Default", "px": 0, "cols": 3}, {"name": "Tablet", "px": 576, "cols": 6} ], "order": 3, "className": "", "visible": "true", "disabled": "false" }, { "id": "ex4_group_monitor1", "type": "ui-group", "name": "📊 センサー", "page": "ex4_page_monitor", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false }, { "id": "ex4_group_monitor2", "type": "ui-group", "name": "📈 チャート", "page": "ex4_page_monitor", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false }, { "id": "ex4_group_report", "type": "ui-group", "name": "📝 レポート", "page": "ex4_page_report", "width": "6", "height": "-1", "order": 1, "showTitle": false, "groupType": "default", "className": "", "visible": true, "disabled": false }, { "id": "ex4_group_settings1", "type": "ui-group", "name": "⚙️ デバイス設定", "page": "ex4_page_settings", "width": "6", "height": "-1", "order": 1, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false }, { "id": "ex4_group_settings2", "type": "ui-group", "name": "👤 ユーザー設定", "page": "ex4_page_settings", "width": "6", "height": "-1", "order": 2, "showTitle": true, "groupType": "default", "className": "", "visible": true, "disabled": false } ]

🎓 11. まとめ

Layout機能の重要ポイント

⚠️ よくある間違い

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

よくある問題と解決方法

問題 原因 解決方法
ウィジェットが横並びにならない widthの合計がグループ幅を超えている 各ウィジェットのwidth合計をグループ幅以内に調整
Fixedで配置が崩れる(モバイル) 576px以下でGridモードに切替 仕様として理解、またはCSSで上書き
Tabsで特定タブが開かない 初期表示は常に最初のタブ グループのOrderで順番を調整
Notebookの幅が広すぎる 最大幅1024pxの仕様 Themeでpage paddingを調整
グループ間に余白がない ThemeのGroup Gapが0 ui-themeのsizes.groupGapを設定
ページが表示されない ui-baseへの参照がない ui-pageのui設定を確認

💡 13. 実務での活用例

ケース1: 工場監視ダッシュボード(Grid)

Grid Layout(12カラム) ├── グループ1: ライン1状態(width: 4) │ ├── 稼働状態ゲージ(width: 2) │ └── 生産カウンター(width: 2) ├── グループ2: ライン2状態(width: 4) │ └── 同上 ├── グループ3: ライン3状態(width: 4) │ └── 同上 └── グループ4: 全体チャート(width: 12) └── リアルタイムチャート(width: 12)

ケース2: タッチパネルHMI(Fixed)

Fixed Layout ├── グループ1: 操作ボタン(width: 6 = 540px) │ ├── 起動ボタン(width: 2 = 180px) │ ├── 停止ボタン(width: 2 = 180px) │ └── 緊急停止(width: 2 = 180px) └── グループ2: 状態表示(width: 6 = 540px) └── 大型ゲージ(width: 6 = 540px)

ケース3: データ分析レポート(Notebook)

Notebook Layout(中央揃え、最大1024px) ├── グループ1: タイトル │ └── Markdown(レポートタイトル・概要) ├── グループ2: 可視化 │ └── Chart(トレンド分析) ├── グループ3: 詳細データ │ └── Table(詳細データ一覧) └── グループ4: 結論 └── Markdown(分析結果・推奨事項)

ケース4: デバイス設定画面(Tabs)

Tabs Layout ├── タブ1「接続設定」 │ ├── Text Input: IPアドレス │ ├── Text Input: ポート │ └── Switch: SSL有効化 ├── タブ2「通知設定」 │ ├── Switch: 通知有効化 │ ├── Dropdown: 通知方法 │ └── Slider: 通知間隔 └── タブ3「詳細設定」 ├── Form: 各種パラメータ └── Button: 設定リセット

📚 14. 追加リソース


このガイドが役に立ちましたら、実際のプロジェクトで練習してみてください!
Layout機能はダッシュボードのUXを決定する重要な要素です。

🏠