はじめに
WebアプリケーションサーバやDBの状態を監視するにはGrafanaの様なダッシュボードが使用されるケースが多いと思います。これで多くの場合問題ありませんが、工場設備やエレベータ、発送電、上下水等の監視にはもう少しグラフィカルな図付きの監視画面が使用されています。一般用途向けでも、ソーラーパネルの発電量を表示するシステムは、単に数値だけではなく図付きのポップな画面を良く見ます。今回はこの様なグラフィカルな監視画面をWebで作成してみました。
実現にはOSSであるWinterCardinal GraphicEditorを利用しています。また、作成したサンプルはGitHubにプッシュしてあります。
本記事では、作成したサンプルをベースに、WinterCardinal GraphicEditorの使い方について紹介します。
WinterCardinal GraphicEditorとは?
今回使用しているWinterCardinal GraphicEditorはブラウザ上で動作するグラフィックエディター兼ビューワーです。内部的にはWebGL、PixiJS、WinterCardinal UIが使用されており、InkScapeやMicrosoft Visioの様にグラフィックを作成するためのものです。ただしInkScapeやVisioが静的な絵を作成するものであるのに対し、GraphicEditorは電力量等の値に応じて動的に変化する絵を作成する事を目的としています。例えば電力量がある一定値を超えたら、文字を赤く大きくして警告すると言った表現が出来ます。この点ではPowerPointのアニメーションに似ています。PowerPointのアニメーションはクリックやページ遷移契機で動作するのに対し、GraphicEditorではセンサー値を契機として動作します。
InkScapeやVisioでも動きに関する情報を埋め込み、SVGとしてエクスポート、専用ビューワを開発する事で似たようなことは可能です。過去にはその様な事を行ってきましたが、どうしても無理や煩雑さが生じていました。またSVGゆえにグラフィックが大きくなるとロードに時間を要する問題もありました。GraphicEditorはそれ専用に設計されており、またSVGと比較し非常に軽量なファイルフォーマットとなっています。
またGraphicEditorはWeb上で動作するため、同じくWeb上で動作する監視システムであれば、GraphicEditorをシステムに組み込む事が可能です。これによって設備に変化が生じた時にもグラフィックをその場で変更して動かすと言う事が容易に出来るようになります。
作成したサンプルの概要
GitHubに置いたサンプルをREADMEに従ってビルド・起動すると、下記のような画面が表示されます。
ログイン後、左サイドバーのDiagramを選択すると下のような画面が表示されます。センサー値に応じて絵がアニメーションしますので、これを見ながら監視を行うという想定のサンプルになっています。
また、左サイドバーのGraphic Editorを選択し、plant
と言う名前のファイルを読み込むと上のDiagram画面の元となったグラフィックが表示されます。
このGraphic Editorで表示したいグラフィックを作成し、Diagram画面に切り替えて表示することによって、画面の作成・変更・確認をWebブラウザ上でグラフィカルかつシームレスに行えます。
上記サンプルではWinterCardinal GraphicEditorをホストするためのAPサーバとしてSpring BootとWinterCardinal、H2 DBを使用しています。アイコンはMaterial Iconsから拝借しています。
画面がブラウザ上に表示されるまでの流れは下記の通りです。
画面を最初に表示する時はブラウザ側からplant
のグラフィックデータ要求を出し、H2 DBに入っているグラフィックデータを返して表示します。グラフィックの表示はGraphicEditorが行ってくれます。
その後センサー値を収集し、GraphicEditorがグラフィックを更新する(=数値の更新とタンク容量アニメーション)と言う流れになります。センサー値のプッシュにWinterCardinal(=WebSocket)を使用していますが、更新頻度が低ければHTTPでポーリングしても良いかと思います。
グラフィックの作成・表示はGraphic Editorを使ってブラウザ上から行えますが、Graphic Editor自体のホスティングや、送られてくるセンサ値との連携などにはコーディング作業が必要となります。詳細はGitHubのサンプルコードをご覧いただければと思いますが、概要を次に説明します。
Graphic Editor画面を実装する
まずグラフィックを作成・編集するためのGraphic Editor画面を実装します。MVCコントローラを新設し/graphic-editor
にアクセスされたらsrc\main\resources\templates\graphic-editor\main.html
を表示するように設定します。
@Controller
public class MvcGraphicEditorController {
@GetMapping("/graphic-editor")
public String main() {
return "graphic-editor/main";
}
}
合わせてサイドメニューからGraphic Editorを選択すると/graphic-editor
に遷移する様に設定します。
-<button class="wcs-menu-item alert">
+<button class="wcs-menu-item alert" url="./graphic-editor">
<th:block th:replace="~{image/editor}"></th:block>
<span th:text="#{top.menu.graphic-editor}">Graphic Editor</span>
</button>
src\main\resources\templates\graphic-editor\main.html
側ではWinterCardinalやPixiJSと言った必要なJavaScriptを読み込みます。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:with="title=#{graphic-editor.label}">
<head>
<th:block th:insert="~{common/header}"></th:block>
<th:block th:insert="~{common/banner::head (${title})}"></th:block>
</head>
<body>
<th:block th:insert="~{common/banner::body (${title})}"></th:block>
<th:block th:insert="~{common/csrf}"></th:block>
+ <script th:src="@{/webjars/wcardinal/wcardinal.worker.min.js}"></script>
<script th:src="@{/graphic-controller}"></script>
+ <script th:src="@{/webjars/pixi.js/pixi.min.js}"></script>
+ <script th:src="@{/webjars/wcardinal-ui/wcardinal-ui.min.js}"></script>
+ <script th:src="@{|/webjars/wcardinal-ui/wcardinal-ui-theme-white-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
+ <script th:src="@{/webjars/wcardinal-geditor/wcardinal-geditor.min.js}"></script>
+ <script th:src="@{|/webjars/wcardinal-geditor/wcardinal-geditor-theme-default-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
+ <script th:src="@{|/asset/message-script/${product.version}/message-script-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
+ <script th:src="@{/webjars/wcardinal-starter/wcs-graphic-editor.min.js}"></script>
<script>
(function(){
"use strict";
new wcs.Main({
controller: graphicController,
csrf: window.csrf
});
}());
</script>
</body>
</html>
最後に読み込んでいるwebjars/wcardinal-starter/wcs-graphic-editor.min.js
はsrc\main\typescript\graphic-editor\main.ts
をRollupでバンドルしたものです。src\main\typescript\graphic-editor\main.ts
内のMain
クラスがwindow.wcs.Main
にエクスポートされる様に設定してあります。ですのであとはこのMain
クラスを実装すれば良いと言う事になります。
src\main\typescript\graphic-editor\main.ts
の実装
グラフィックエディターを画面に出すにはWinterCardinal GraphicEditorが提供するFGraphicEditor
と言うクラスを使用します。例えば以下の様にしますとエディタがbody
直下に生成されます。
export class Main extends FGraphicEditor {
constructor(options: MainOptions) {
super({
controller: options.controller,
header: newHeader("graphic-editor.label")
});
}
}
任意の位置にエディターを生成するには以下の様にroot
を指定します。
import { FGraphicEditor } from "@wcardinal/wcardinal-geditor";
export class Main extends FGraphicEditor {
constructor(options: MainOptions) {
super({
application: {
root: "#my-root" // DOM要素のセレクタまたはDOMそのもの
},
controller: options.controller,
header: newHeader("graphic-editor.label")
});
}
}
ここでoptions.controller
はHTML側で渡したgraphicController
となります。graphicController
はWinterCardinal GraphicEditorのFGraphicEditorControllerOptions
インターフェイスを実装したクラスで、グラフィックの検索やDBへの保存を担当するクラスです。GraphicEditorのサンプルに実装例があります。
今回作成した簡易監視画面ではJava側のapp.wcc.graphic.GraphicControllerをHTMLから<script th:src="@{/graphic-controller}"></script>
で読み込んで、それをそのまま渡すと言う実装になっています。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:with="title=#{graphic-editor.label}">
<head>
<th:block th:insert="~{common/header}"></th:block>
<th:block th:insert="~{common/banner::head (${title})}"></th:block>
</head>
<body>
<th:block th:insert="~{common/banner::body (${title})}"></th:block>
<th:block th:insert="~{common/csrf}"></th:block>
<script th:src="@{/webjars/wcardinal/wcardinal.worker.min.js}"></script>
+ <script th:src="@{/graphic-controller}"></script>
<script th:src="@{/webjars/pixi.js/pixi.min.js}"></script>
<script th:src="@{/webjars/wcardinal-ui/wcardinal-ui.min.js}"></script>
<script th:src="@{|/webjars/wcardinal-ui/wcardinal-ui-theme-white-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
<script th:src="@{/webjars/wcardinal-geditor/wcardinal-geditor.min.js}"></script>
<script th:src="@{|/webjars/wcardinal-geditor/wcardinal-geditor-theme-default-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
<script th:src="@{|/asset/message-script/${product.version}/message-script-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
<script th:src="@{/webjars/wcardinal-starter/wcs-graphic-editor.min.js}"></script>
<script>
(function(){
"use strict";
new wcs.Main({
+ controller: graphicController,
csrf: window.csrf
});
}());
</script>
</body>
</html>
Java側のapp.wcc.graphic.GraphicController
をJavaScript側で読み込む機能はWinterCardinalによるものです。<script th:src="@{/graphic-controller}"></script>
でクラスを読み込むと、window.graphicController
にクラスのインスタンスが生成されます。
GraphicController
に定義されたメソッドやフィールドの内、@Callable
や@Component
を付与されたものがJavaScriptから見える様になります。
app.wcc.graphic.GraphicController
の実装
GraphicController
はグラフィックの検索やDBへの保存を担いますが、今回の簡易監視画面ではグラフィックデータをDBからSELECT、UPSERT、DELETEする単純なものとなっています。
@Controller
public class GraphicController {
@Autowired
protected GraphicComponent graphic;
}
@Component
public class GraphicComponent {
@Autowired
GraphicService service;
@Autowired
GraphicPieceComponent piece;
/** 検索文字を名称に含むグラフィック一覧を返す */
@Callable
Iterable<GraphicFound> search(String word) {
return service.search(word);
}
/** 指定のIDのグラフィックデータを返す */
@Callable
Graphic get(final Long id) {
return service.find(id).orElse(null);
}
/** 指定の名称のグラフィックデータ返す */
@Callable
Graphic getByName(final String name) {
return service.find(name).orElse(null);
}
/** グラフィックを保存するメソッド */
@Callable
Long save(final Graphic graphic) {
return service.save(graphic).getId();
}
/** 指定のIDのグラフィックを削除する */
@Callable
void delete(Long id) {
service.delete(id);
}
}
Diagram画面を実装する
次にグラフィックを表示するDiagram画面を作成します。そのためまずMVCコントローラを新設し、/diagram
にアクセスするとsrc\main\resources\templates\diagram\main.html
を返却する様に設定します。
@Controller
public class MvcDiagramController {
@GetMapping("/diagram")
public String main() {
return "diagram/main";
}
}
合わせてサイドメニューからDiagramを選択すると/diagram
に遷移する様に設定します。
-<button class="wcs-menu-item diagram">
+<button class="wcs-menu-item diagram" url="./diagram">
<th:block th:replace="~{image/diagram}"></th:block>
<span th:text="#{top.menu.diagram}">Diagram</span>
</button>
src\main\resources\templates\diagram\main.html
側ではGraphic Editor画面と同様にWinterCardinalやPixiJSと言った必要なJavaScriptを読み込んでいます。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:with="title=#{diagram.label}">
<head>
<th:block th:insert="~{common/header}"></th:block>
<th:block th:insert="~{common/banner::head (${title})}"></th:block>
</head>
<body>
<th:block th:insert="~{common/banner::body (${title})}"></th:block>
<th:block th:insert="~{common/csrf}"></th:block>
+ <script th:src="@{/webjars/wcardinal/wcardinal.worker.min.js}"></script>
<script th:src="@{/diagram-controller}"></script>
+ <script th:src="@{/webjars/pixi.js/pixi.min.js}"></script>
+ <script th:src="@{/webjars/wcardinal-ui/wcardinal-ui.min.js}"></script>
+ <script th:src="@{|/webjars/wcardinal-ui/wcardinal-ui-theme-white-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
+ <script th:src="@{|/asset/message-script/${product.version}/message-script-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
+ <script th:src="@{/webjars/wcardinal-starter/wcs-diagram.min.js}"></script>
<script>
(function(){
"use strict";
new wcs.Main({
controller: diagramController,
csrf: window.csrf
});
}());
</script>
</body>
</html>
最後に読み込んでいるwebjars/wcardinal-starter/wcs-diagram.min.js
はsrc\main\typescript\diagram\main.ts
をRollupでバンドルしたものです。src\main\typescript\diagram\main.ts
内のMain
クラスがwindow.wcs.Main
にエクスポートされるように設定してあります。ですのであとはこのMain
クラスを実装していけばよいと言う事になります。
src\main\typescript\diagram\main.ts
の実装
WinterCardinal GraphicEditorはグラフィックを表示するためのクラスFGraphicViewer
も提供しています。FGraphicViewer
が生成する画面には、グラフィック選択ボタンがあり、そのボタンを押すとグラフィック選択&表示できると言う画面構成になっています。しかしながら今回はDiagram画面表示後即座にplant
グラフィックを表示したかったためFGraphicViewer
を使用しないで実装しました。WinterCardinal UIが提供するDDiagram
を使用すれば比較的簡単に実装する事が可能です。
export class Main {
protected _application: DApplication;
protected _controller: DiagramController;
protected _diagram?: DDiagram;
constructor(options: MainOptions) {
const application = new DApplication();
this._application = application;
// センサー値を受信した時の処理
const controller = options.controller;
this._controller = controller;
controller.instant.on("update", (e: unknown, values: Record<string, InstantValue>) => {
this.onInstantUpdate(values);
});
// グラフィックを表示するdiagramをステージに登録
const diagram = this.diagram;
application.stage.addChild(diagram);
}
// センサー値を受信した時に呼び出されるメソッド
protected onInstantUpdate(values: Record<string, InstantValue>): void {
const data = this.diagram.data;
for (const sensorName in values) {
const value = values[sensorName];
data.set(sensorName, value.value, value.time);
}
this._application.update();
}
// グラフィックを描画するDDiagram部品
protected get diagram(): DDiagram {
return (this._diagram ??= this.newDiagram());
}
protected newDiagram(): DDiagram {
const result = new DDiagram({ ... });
this._controller.graphic.getByName("plant").then((graphic) => {
result.set(DDiagrams.toSerialized(graphic));
});
return result;
}
}
DDiagram
クラスの#set(...)
メソッドにグラフィックデータを渡すことでグラフィックが表示されるようになります。newDiagram()
メソッドの中でresult.set(...)
している所がそれになります。
今回は使用しませんでしたが、仕様が合えばFGraphicViewer
なら上記の様な実装も省略できます。FGraphicViewer
を使用した実装例はGraphicEditorのサンプルから見る事ができます。
DDiagram
も様々なカスタマイズが可能です。WinterCardinal UIのサンプルからDiagramを参照してください。
センサー値はサーバからプッシュされて送られてきますが、プッシュされた時にoptions.controller.instant
においてupdate
イベントが発生する様にしています。このイベントの第2引数がセンサー値のマップですのでこれをthis.diagram.data.set(...)
に渡してグラフィックを更新しています。
options.controller
はHTML側で渡したdiagramController
ですが、これはGraphic Editor画面の場合と同様に、Java側のapp.wcc.diagram.DiagramController
を<script th:src="@{/webjars/wcardinal-starter/wcs-diagram.min.js}"></script>
で読み込んだものです。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:with="title=#{diagram.label}">
<head>
<th:block th:insert="~{common/header}"></th:block>
<th:block th:insert="~{common/banner::head (${title})}"></th:block>
</head>
<body>
<th:block th:insert="~{common/banner::body (${title})}"></th:block>
<th:block th:insert="~{common/csrf}"></th:block>
<script th:src="@{/webjars/wcardinal/wcardinal.worker.min.js}"></script>
+ <script th:src="@{/diagram-controller}"></script>
<script th:src="@{/webjars/pixi.js/pixi.min.js}"></script>
<script th:src="@{/webjars/wcardinal-ui/wcardinal-ui.min.js}"></script>
<script th:src="@{|/webjars/wcardinal-ui/wcardinal-ui-theme-white-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
<script th:src="@{|/asset/message-script/${product.version}/message-script-${#strings.toLowerCase(#locale.language)}-${#strings.toLowerCase(#locale.country)}.min.js|}"></script>
<script th:src="@{/webjars/wcardinal-starter/wcs-diagram.min.js}"></script>
<script>
(function(){
"use strict";
new wcs.Main({
+ controller: diagramController,
csrf: window.csrf
});
}());
</script>
</body>
</html>
app.wcc.diagram.DiagramController
の実装
DiagramController
の実装は以下の様になっています。GraphicComponent
はGraphic Editor画面を作成する時に作ったクラスです。新設したのはセンサー値を収集するInstantComponent
です。
@Controller
public class DiagramController {
@Autowired
protected InstantComponent instant;
@Autowired
protected GraphicComponent graphic;
}
InstantComponent
は以下の様に定義しています。sensors
に格納されたセンサーIDのリストに基づきtriggerDirect("update", values)
でJavaScript側にupdate
イベントを1秒周期で発火させると言うコードになっています。実際の監視システムでは物理デバイスから値を収集する事になりますが、今回はダミーの値を流しています。
@Component
@RequiredArgsConstructor
public class InstantComponent extends AbstractController {
// このフィールドは型がSClassのためJavaScript側から変更できます
// 値が変更されると@OnChange("sensors")が付与されているonSensorsChangeが呼び出されます
@Autowired
protected SClass<List<String>> sensors;
// sensorsが変更された時に呼び出されるメソッド
@OnChange("sensors")
protected void onSensorsChange(final List<String> sensors) {
final var values = new HashMap<String, InstantValue>();
for (int i = 0; i < sensors.size(); ++i) {
final var sensor = sensors.get(i);
values.put(sensor, new InstantValue(0, 0, i));
}
this.cancelAll(); // 定期処理を全て停止します
this.interval("send", 0, 1000, values); // 1秒周期でsendメソッドを実行する
}
@OnTime
protected void send(final Map<String, InstantValue> values) {
final var now = System.currentTimeMillis();
for (final var value : values.values()) {
value.update(now);
}
this.triggerDirect("update", values); // JavaScript側にupdateイベントを発生させます
}
}
triggerDirect
はAbstractController
が提供しているメソッドで、WinterCardinalの機能です。使用方法はWinterCardinalのCheatsheetかJavadocが参考になるかと思います。
DBの初期化
DBの初期化処理はsrc\main\resources\data.sql
で行っています。Spring BootがこのSQLをサーバ起動時に実行してくれます。ただしSpring Boot 2.5からdata.sql
の実行タイミングが変更されているためspring.jpa.defer-datasource-initialization=true
を指定しています。
insert into account (id, name, password) values
(nextval('account_seq'), 'Account1', '{bcrypt}$2a$10$vmG149.oKSDVFxishLcodelvvAoL.u1yw1Q5tlGObqfhbXQZoxHMe');
insert into sensor (id, name, color) values
(nextval('sensor_seq'), 'Sensor1', 0xc00000),
(nextval('sensor_seq'), 'Sensor2', 0xff0000),
(nextval('sensor_seq'), 'Sensor3', 0xffc000),
(nextval('sensor_seq'), 'Sensor4', 0xffff00),
(nextval('sensor_seq'), 'Sensor5', 0x92d050),
(nextval('sensor_seq'), 'Sensor6', 0x00b050),
(nextval('sensor_seq'), 'Sensor7', 0x00b0f0),
(nextval('sensor_seq'), 'Sensor8', 0x0070c0),
(nextval('sensor_seq'), 'Sensor9', 0x002060),
(nextval('sensor_seq'), 'SensorA', 0x7030a0);
insert into graphic (version, id, name, label, category, summary, description, data) values
(1, 1, 'plant', 'Plant', null, '', '', '{"width":1000,"height":500,"background":{"color":16777215,"alpha":1},"tile":{"mapping":{"enable":false,"from":{"lon":-180,"lat":85.05112877980659},"to":{"lon":180,"lat":-85.05112877980659}}},"resources":["","[1,12900086,1]","[0,5464442,1,2,0,15,0]","auto","[1,1]","[0,0]","[0,16777215,1,0.5]","[10,10]","[0,5197647,1,3,14,0,4,5,0,6,5,0,7,0,0]","[0,0,1]","[0,0,0,9,0,0,0,1,0]","[10]","data.value","[2,0,0,1,0.5,1,12]","[0,5197647,1,2,0,11,0]","[]","[1,16777215,0.5]","[1,5464442,1,2,0,15,0]","[1,16777215,1]","[0,15,15,18]","[19,19]","[[-40,-95,40,-95,-40,95,40,95],[],1,20]","[0,16777215,0.5]","[0,5197647,1,2,0,15,0]","Sensor1","[24,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","[1,0,0,9,0,0,0,1,0]","[26]","[9,0,2,0,12900086,1,12]","[2,15,15,18]","[29,19]","[[-50,-35,-50,35,50,35],[],2048,30]","[2,0,0,9,0,0,0,1,0]","[32]","[[-70,15,-70,-15,-40,-15,70,-15],[],0,30]","Sensor2","[35,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","[1,5197647,1,2,0,15,0]","[29,29]","[[-25,0,25,0],[],0,38]","[3,0,0,9,0,0,0,1,0]","[40]","[9,0,2,1,12900086,1,12]","[[-65,40,-65,-40,65,-40],[],2048,30]","Sensor3","[44,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","[[-39.5,0,39.5,0],[],0,20]","[[0,-20,0,20],[],0,30]","[[0,-45,0,45],[],0,38]","[[0,20,0,-20],[],0,30]","[4,0,0,9,0,0,0,1,0]","[50]","[[50,-30,50,30,-50,30],[],2048,30]","Sensor4","[53,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","[[30,15,30,-15,-30,-15],[],2048,30]","[[-35,10,-35,60,15,60,15,-60,35,-60],[],0,30]","Sensor5","[57,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","Sensor6","[59,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","Sensor7","[61,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","%d%%","[63,5464442,1,3,14,0,4,5,0,6,5,0,7,0,0]","Math.round(value * 100)","[0,0,65,9,0,0,0,1,0]","[66]","[6,0,1,12]","[5,0,65,9,0,0,0,1,0]","[69]","[6,0,0,9,0,0,0,1,0]","[71]","[6,0,65,9,0,0,0,1,0]","[73]","[3,0,65,9,0,0,0,1,0]","[75]","[2,0,65,9,0,0,0,1,0]","[77]","[1,0,65,9,0,0,0,1,0]","[79]","[4,0,65,9,0,0,0,1,0]","[81]","Base Layer","[0,16777215,1]"],"data":["Sensor7","Sensor1","Sensor2","Sensor3","Sensor4","Sensor5","Sensor6"],"pieces":[],"layers":[[83,3,0,0,500,500,84,1]],"items":[[1,0,895.3,205,79,188.20601184465414,0,0,1,2,-1,8,11,1,12,-1,0,[13],-1,-1,[],0,0,2,-1,-1,27,0,0,0],[24,0,895.2,298.4,79,79,-3.141592653589793,0,1,14,-1,8,15,0.25,15,-1,0,[],-1,-1,[],0,0,2,-1,-1,36,0,0,0],[2,0,320.67595027202145,205,83,270,0,0,16,17,-1,8,15,1,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,2,0,0,0],[5,0,321.00350966810487,206.26566856686998,80,190,0,0,16,17,-1,8,15,0.25,15,21,0,[],-1,-1,[],0,0,0,-1,-1,3,0,0,0],[1,0,173.2999087667538,38.765510492931185,150,40,0,0,22,23,-1,25,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,4,0,0,0],[5,0,221.12709768190766,105,100,70,0,0,16,17,-1,8,27,0.25,15,31,0,[28],-1,-1,[],0,0,0,-1,-1,5,0,0,0],[5,0,141.00350966810484,195,140,30,0,0,16,17,-1,8,33,0.25,15,34,0,[28],-1,-1,[],0,0,0,-1,-1,6,0,0,0],[1,0,71.00350966810485,230,150,40,0,0,22,23,-1,36,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,7,0,0,0],[5,0,246.00350966810484,180,50,0.001,0,0,16,37,-1,8,33,0.25,15,39,0,[28],-1,-1,[],0,0,0,-1,-1,8,0,0,0],[5,0,206.00350966810484,310,130,80,0,0,16,37,-1,8,41,0.25,15,43,0,[42],-1,-1,[],0,0,0,-1,-1,9,0,0,0],[1,0,141.00350966810484,370,150,40,0,0,22,23,-1,45,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,10,0,0,0],[2,0,505.56237190444716,158.77839459189352,82,270,0,0,16,17,-1,8,15,1,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,11,0,0,0],[5,0,505.10443771695526,65.45895714570631,79,0.001,0,0,16,17,-1,8,15,0.25,15,46,0,[],-1,-1,[],0,0,0,-1,-1,12,0,0,0],[5,0,506.0929247688943,254.5415428542937,79,0.001,0,0,16,17,-1,8,15,0.25,15,46,0,[],-1,-1,[],0,0,0,-1,-1,13,0,0,0],[5,0,321.00350966810487,360,0.001,40,0,0,16,17,-1,8,15,0.25,15,47,0,[],-1,-1,[],0,0,0,-1,-1,14,0,0,0],[5,0,321.00350966810487,435,0.001,90,0,0,16,17,-1,8,15,0.25,15,48,0,[],-1,-1,[],0,0,0,-1,-1,15,0,0,0],[3,0,614.2253687511643,270.86321251756783,80,40,0,0,16,17,-1,8,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,16,0,0,0],[0,0,614.2253687511643,240.86321251756783,80,80,0,0,18,17,-1,8,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,17,0,0,0],[2,0,556.911855533147,381,82,221,1.5707963267948966,0,16,17,-1,8,15,1,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,18,0,0,0],[5,0,505.0483404887911,313.2358646565492,0.001,40,0,0,16,17,-1,8,15,0.25,15,47,0,[],-1,-1,[],0,0,0,-1,-1,19,0,0,0],[5,0,612.8308671645913,320.9136787482432,0.001,40,0,0,16,17,-1,8,15,0.25,15,49,0,[],-1,-1,[],0,0,0,-1,-1,20,0,0,0],[5,0,604.4654957643252,98.76257940993423,100,60,0,0,16,17,-1,8,51,0.25,15,52,0,[28],-1,-1,[],0,0,0,-1,-1,21,0,0,0],[1,0,653.5071037852289,41.25179705856203,150,40,0,0,22,23,-1,54,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,22,0,0,0],[5,0,584.4654957643255,185.98913888463437,60,30,0,0,16,17,-1,8,15,0.25,15,55,0,[],-1,-1,[],0,0,0,-1,-1,23,0,0,0],[5,0,754.669098890451,360,0.001,40,0,0,16,17,-1,8,15,0.25,15,47,0,[],-1,-1,[],0,0,0,-1,-1,25,0,0,0],[5,0,754.669098890451,435,0.001,90,0,0,16,17,-1,8,15,0.25,15,48,0,[],-1,-1,[],0,0,0,-1,-1,26,0,0,0],[2,0,895.2828559916301,205,83,270,0,0,16,17,-1,8,15,1,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,37,0,0,0],[5,0,895.6104153877135,360,0.001,40,0,0,16,17,-1,8,15,0.25,15,47,0,[],-1,-1,[],0,0,0,-1,-1,28,0,0,0],[5,0,895.6104153877135,435,0.001,90,0,0,16,17,-1,8,15,0.25,15,48,0,[],-1,-1,[],0,0,0,-1,-1,29,0,0,0],[5,0,812.7482675081753,321.9531452959578,70,120,0,0,16,17,-1,8,15,0.25,15,56,0,[],-1,-1,[],0,0,0,-1,-1,30,0,0,0],[1,0,556.0035096681048,364.18250230763476,150,40,0,0,22,2,-1,58,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,31,0,0,0],[1,0,753.2592292869073,40,150,40,0,0,22,23,-1,60,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,32,0,0,0],[1,0,895.5149489057096,40,150,40,0,0,22,23,-1,62,15,0.25,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,1,0,0,0],[1,0,895,210,150,40,0,0,22,23,-1,64,67,0.25,15,-1,0,[68],-1,-1,[],0,0,0,-1,-1,34,0,0,0],[1,0,555,396.7671588564074,150,40,0,0,22,23,-1,64,70,0.25,15,-1,0,[68],-1,-1,[],0,0,0,-1,-1,35,0,0,0],[1,0,754.3308738467676,205.2353290191372,79,188.20601184465414,0,0,1,2,-1,8,72,1,12,-1,0,[13],-1,-1,[],0,0,2,-1,-1,38,0,0,0],[24,0,754.2308738467677,298.63532901913703,79,79,-3.141592653589793,0,1,14,-1,8,15,0.25,15,-1,0,[],-1,-1,[],0,0,2,-1,-1,39,0,0,0],[2,0,754.3415394943676,205,83,270,0,0,16,17,-1,8,15,1,15,-1,0,[],-1,-1,[],0,0,0,-1,-1,24,0,0,0],[1,0,755,210,150,40,0,0,22,23,-1,64,74,0.25,15,-1,0,[68],-1,-1,[],0,0,0,-1,-1,33,0,0,0],[1,0,140,390,150,40,0,0,22,23,-1,64,76,0.25,15,-1,0,[68],-1,-1,[],0,0,0,-1,-1,40,0,0,0],[1,0,70,250,150,40,0,0,22,23,-1,64,78,0.25,15,-1,0,[68],-1,-1,[],0,0,0,-1,-1,41,0,0,0],[1,0,170,59.535870583066384,150,40,0,0,22,23,-1,64,80,0.25,15,-1,0,[68],-1,-1,[],0,0,0,-1,-1,42,0,0,0],[1,0,655,60,150,40,0,0,22,23,-1,64,82,0.25,15,-1,0,[68],-1,-1,[],0,0,0,-1,-1,43,0,0,0]],"snap":[1,[1,1,[]],[1,1,10]]}');
画面の表示とカスタマイズ
サイドメニューからDiagramを選択し、Diagram画面が表示されるようになります。数値やタンク容量がInstantComponent
の生成するセンサー値に応じてアニメーションするのを確認できます。
試しにタンクを1つ消し、色味をグリーンに変更してみます。まず削除するタンクを選択します。左マウスボタンでクリックしながらドラッグすると範囲選択する事が出来ます。
選択されたらDeleteキーで削除します。
色を変更したい場所を選択し、塗り色(Fill)を変更します。
一部の線にアクションと呼ばれる動きの設定がされていますので、そのアクションの色も合わせて変更します。
この状態で保存し、Diagram画面を表示させると反映されていることを確認できます。
まとめ
今回はグラフィカルな簡易監視画面をWinterCardinal GraphicEditorで作成してみました。GraphicEditorがWebGLで出来ていることから、SVGと比較して比較的軽量に動作するかと思います。カスタマイズ例としてタンク削除と色変更を行いましたが、設備を増やしたり、クリックで画面遷移できるようにしたり、センサー割り当てを変更したりと言った事も可能です。またより応用的なカスタマイズとして、アプリケーション独自のシェイプやアクションを導入する事も可能になっています。サンプルページに実装例もありますので参考にして頂ければ幸いです。