From a8b1c21ed6191694c66c56bd594a59c93f9fe239 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Tue, 24 May 2022 11:38:56 +0800 Subject: [PATCH 1/5] Fix ZH_CN I18N num error --- source/Localization/ZH-CN.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index fe00be8..15b2c6a 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -26,10 +26,10 @@ const ZH_CN = { "Command.Bar.Camera.Info": "渲染器设置", "Command.Bar.Setting.Info": "全局设置", "Input.Error.Not.Number": "请输入数字", - "Input.Error.Max": "输入数值须小于 {number}", - "Input.Error.Min": "输入数值须大于 {number}", - "Input.Error.Length": "输入内容长度须小于 {number}", - "Input.Error.Length.Less": "输入内容长度须大于 {number}", + "Input.Error.Max": "输入数值须小于 {num}", + "Input.Error.Min": "输入数值须大于 {num}", + "Input.Error.Length": "输入内容长度须小于 {num}", + "Input.Error.Length.Less": "输入内容长度须大于 {num}", "Input.Error.Select": "选择对象 ...", "Input.Error.Combo": "选择选项 ...", "Object.List.New.Group": "群对象 {id}", -- 2.45.2 From ce99b17fcb6e04a708ba8ef635fc67b7317ab192 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Tue, 24 May 2022 14:07:47 +0800 Subject: [PATCH 2/5] Add statistics panel --- source/Localization/EN-US.ts | 2 ++ source/Localization/ZH-CN.ts | 2 ++ .../SimulatorDesktop/SimulatorDesktop.tsx | 2 +- source/Page/SimulatorWeb/SimulatorWeb.tsx | 2 +- source/Panel/Panel.tsx | 6 +++++ source/Panel/Statistics/Statistics.scss | 8 +++++++ source/Panel/Statistics/Statistics.tsx | 22 +++++++++++++++++++ 7 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 source/Panel/Statistics/Statistics.scss create mode 100644 source/Panel/Statistics/Statistics.tsx diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 4fcbd3f..cda3522 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -62,6 +62,8 @@ const EN_US = { "Panel.Info.Behavior.Clip.Player": "Pre render recorded data", "Panel.Title.Behavior.Clip.Details": "Clip", "Panel.Info.Behavior.Clip.Details": "Edit view clip attributes", + "Panel.Info.Statistics": "View statistics", + "Panel.Title.Statistics": "Statistics", "Panel.Info.Behavior.Clip.Time.Formate": "{current} / {all} / {fps}fps", "Panel.Info.Behavior.Clip.Record.Formate": "Record: {time}", "Panel.Info.Behavior.Clip.Uname.Clip": "Waiting for recording...", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 15b2c6a..99b8a19 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -62,6 +62,8 @@ const ZH_CN = { "Panel.Info.Behavior.Clip.Player": "预渲染录制数据", "Panel.Title.Behavior.Clip.Details": "剪辑", "Panel.Info.Behavior.Clip.Details": "编辑查看剪辑片段属性", + "Panel.Info.Statistics": "查看统计信息", + "Panel.Title.Statistics": "统计", "Panel.Info.Behavior.Clip.Time.Formate": "{current} / {all} / {fps} fps", "Panel.Info.Behavior.Clip.Record.Formate": "录制: {time}", "Panel.Info.Behavior.Clip.Uname.Clip": "等待录制...", diff --git a/source/Page/SimulatorDesktop/SimulatorDesktop.tsx b/source/Page/SimulatorDesktop/SimulatorDesktop.tsx index 06b5639..c6f04aa 100644 --- a/source/Page/SimulatorDesktop/SimulatorDesktop.tsx +++ b/source/Page/SimulatorDesktop/SimulatorDesktop.tsx @@ -74,7 +74,7 @@ class SimulatorDesktop extends Component { }, { items: [{ - panels: ["ObjectList"] + panels: ["ObjectList", "Statistics"] }, { panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails", "ClipDetails"] }], diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index a520ff0..36058af 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -65,7 +65,7 @@ class SimulatorWeb extends Component { }, { items: [{ - panels: ["ObjectList"] + panels: ["ObjectList", "Statistics"] }, { panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails", "ClipDetails"] }], diff --git a/source/Panel/Panel.tsx b/source/Panel/Panel.tsx index b4cc8c1..4aa6644 100644 --- a/source/Panel/Panel.tsx +++ b/source/Panel/Panel.tsx @@ -13,6 +13,7 @@ import { BehaviorDetails } from "@Panel/BehaviorDetails/BehaviorDetails"; import { ClipPlayer } from "@Panel/ClipPlayer/ClipPlayer"; import { ClipRecorder } from "@Panel/ClipPlayer/ClipRecorder"; import { ClipDetails } from "@Panel/ClipDetails/ClipDetails"; +import { Statistics } from "@Panel/Statistics/Statistics"; interface IPanelInfo { nameKey: string; @@ -36,6 +37,7 @@ type PanelId = "" | "BehaviorDetails" // 行为属性 | "ClipPlayer" // 剪辑影片 | "ClipDetails" // 剪辑详情 +| "Statistics" // 统计信息 ; const PanelInfoMap = new Map(); @@ -79,6 +81,10 @@ PanelInfoMap.set("ClipDetails", { nameKey: "Panel.Title.Behavior.Clip.Details", introKay: "Panel.Info.Behavior.Clip.Details", class: ClipDetails }); +PanelInfoMap.set("Statistics", { + nameKey: "Panel.Title.Statistics", introKay: "Panel.Info.Statistics", + class: Statistics +}); function getPanelById(panelId: PanelId): ReactNode { switch (panelId) { diff --git a/source/Panel/Statistics/Statistics.scss b/source/Panel/Statistics/Statistics.scss new file mode 100644 index 0000000..20a0d20 --- /dev/null +++ b/source/Panel/Statistics/Statistics.scss @@ -0,0 +1,8 @@ +@import "../../Component/Theme/Theme.scss"; + +div.statistics-panel { + width: 100%; + min-height: 100%; + padding: 10px; + box-sizing: border-box; +} \ No newline at end of file diff --git a/source/Panel/Statistics/Statistics.tsx b/source/Panel/Statistics/Statistics.tsx new file mode 100644 index 0000000..a893fb3 --- /dev/null +++ b/source/Panel/Statistics/Statistics.tsx @@ -0,0 +1,22 @@ +import { Component, ReactNode } from "react"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; +import { useSetting, IMixinSettingProps } from "@Context/Setting"; +import Theme from "@Component/Theme/Theme"; +import "./Statistics.scss"; + +interface IStatisticsProps { + +} + +@useSetting +@useStatusWithEvent("labelChange", "focusLabelChange", "labelAttrChange") +class Statistics extends Component { + + public render(): ReactNode { + return + + ; + } +} + +export { Statistics }; \ No newline at end of file -- 2.45.2 From 2dbbd0952add40bf6c38176de041f8df4de2f83a Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 25 May 2022 22:00:13 +0800 Subject: [PATCH 3/5] Add statistics panel --- package-lock.json | 27 +++++ package.json | 2 + source/Context/Setting.tsx | 5 + source/Context/Status.tsx | 2 + source/Localization/EN-US.ts | 1 + source/Localization/ZH-CN.ts | 1 + source/Model/Actuator.ts | 2 + source/Panel/Statistics/Statistics.scss | 59 ++++++++++- source/Panel/Statistics/Statistics.tsx | 127 +++++++++++++++++++++++- 9 files changed, 220 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b31c0c..a850dcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,13 @@ "dependencies": { "@fluentui/react": "^8.56.0", "@juggle/resize-observer": "^3.3.1", + "chart.js": "^3.7.1", "detect-port": "^1.3.0", "downloadjs": "^1.4.7", "express": "^4.17.3", "gl-matrix": "^3.4.3", "react": "^17.0.2", + "react-chartjs-2": "^4.1.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^17.0.2", @@ -2200,6 +2202,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chart.js": { + "version": "3.7.1", + "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-3.7.1.tgz", + "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==" + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -6210,6 +6217,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/react-chartjs-2/-/react-chartjs-2-4.1.0.tgz", + "integrity": "sha512-AsUihxEp8Jm1oBhbEovE+w50m9PVNhz1sfwEIT4hZduRC0m14gHWHd0cUaxkFDb8HNkdMIGzsNlmVqKiOpU74g==", + "peerDependencies": { + "chart.js": "^3.5.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dnd": { "version": "16.0.1", "resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-16.0.1.tgz", @@ -10113,6 +10129,11 @@ "supports-color": "^7.1.0" } }, + "chart.js": { + "version": "3.7.1", + "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-3.7.1.tgz", + "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==" + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -13200,6 +13221,12 @@ "object-assign": "^4.1.1" } }, + "react-chartjs-2": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/react-chartjs-2/-/react-chartjs-2-4.1.0.tgz", + "integrity": "sha512-AsUihxEp8Jm1oBhbEovE+w50m9PVNhz1sfwEIT4hZduRC0m14gHWHd0cUaxkFDb8HNkdMIGzsNlmVqKiOpU74g==", + "requires": {} + }, "react-dnd": { "version": "16.0.1", "resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-16.0.1.tgz", diff --git a/package.json b/package.json index afef7db..c919826 100644 --- a/package.json +++ b/package.json @@ -71,11 +71,13 @@ "dependencies": { "@fluentui/react": "^8.56.0", "@juggle/resize-observer": "^3.3.1", + "chart.js": "^3.7.1", "detect-port": "^1.3.0", "downloadjs": "^1.4.7", "express": "^4.17.3", "gl-matrix": "^3.4.3", "react": "^17.0.2", + "react-chartjs-2": "^4.1.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^17.0.2", diff --git a/source/Context/Setting.tsx b/source/Context/Setting.tsx index 5996ff4..e58d435 100644 --- a/source/Context/Setting.tsx +++ b/source/Context/Setting.tsx @@ -44,6 +44,11 @@ class Setting extends Emitter { */ public layout: Layout = new Layout(); + /** + * 是否显示线性图表 + */ + public lineChartType: boolean = false; + /** * 设置参数 */ diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index 3bcb630..74520e3 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -38,6 +38,7 @@ interface IStatusEvent { physicsLoop: number; recordLoop: number; offlineLoop: number; + modelUpdate: void; mouseModChange: void; focusObjectChange: void; focusLabelChange: void; @@ -132,6 +133,7 @@ class Status extends Emitter { this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); this.actuator.on("record", (t) => { this.emit("recordLoop", t) }); this.actuator.on("offline", (t) => { this.emit("offlineLoop", t) }); + this.actuator.on("modelUpdate", () => { this.emit("modelUpdate") }); // 对象变化事件 this.model.on("objectChange", () => this.emit("objectChange")); diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index cda3522..d8473ee 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -170,6 +170,7 @@ const EN_US = { "Panel.Info.Behavior.Details.Parameter.Key.Vec.Z": "{key} Z", "Panel.Info.Clip.List.Error.Nodata": "There is no clip, please click the record button to record, or click the plus sign to create", "Panel.Info.Clip.Details.Error.Nodata": "Specify a clip to view an attribute", + "Panel.Info.Statistics.Nodata": "There are no groups in the model or clip", "Info.Hint.Save.After.Close": "Any unsaved progress will be lost. Are you sure you want to continue?", "Info.Hint.Load.File.Title": "Load save", "Info.Hint.Load.File.Intro": "Release to load the dragged save file", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 99b8a19..f1661a8 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -170,6 +170,7 @@ const ZH_CN = { "Panel.Info.Behavior.Details.Parameter.Key.Vec.Z": "{key} Z 坐标", "Panel.Info.Clip.List.Error.Nodata": "没有剪辑片段,请点击录制按钮录制,或者点击加号创建", "Panel.Info.Clip.Details.Error.Nodata": "请指定一个剪辑片段以查看属性", + "Panel.Info.Statistics.Nodata": "模型或剪辑中不存在任何群", "Info.Hint.Save.After.Close": "任何未保存的进度都会丢失, 确定要继续吗?", "Info.Hint.Load.File.Title": "加载存档", "Info.Hint.Load.File.Intro": "释放以加载拽入的存档", diff --git a/source/Model/Actuator.ts b/source/Model/Actuator.ts index 20314c4..e59b76f 100644 --- a/source/Model/Actuator.ts +++ b/source/Model/Actuator.ts @@ -14,6 +14,7 @@ interface IActuatorEvent { record: number; loop: number; offline: number; + modelUpdate: void; } /** @@ -401,6 +402,7 @@ class Actuator extends Emitter { } this.emit("loop", this.alignTimer); + this.emit("modelUpdate"); this.alignTimer = 0; } } diff --git a/source/Panel/Statistics/Statistics.scss b/source/Panel/Statistics/Statistics.scss index 20a0d20..9e2b23e 100644 --- a/source/Panel/Statistics/Statistics.scss +++ b/source/Panel/Statistics/Statistics.scss @@ -2,7 +2,62 @@ div.statistics-panel { width: 100%; + height: 100%; min-height: 100%; - padding: 10px; - box-sizing: border-box; + + div.statistics-chart { + box-sizing: border-box; + padding-top: 10px; + max-width: 300px; + min-height: 100%; + height: 100%; + width: 100%; + } + + div.statistics-switch { + width: 100%; + height: 0; + display: flex; + justify-content: flex-end; + + div.switch-button { + width: 24px; + height: 24px; + position: relative; + user-select: none; + right: -10px; + top: -2px; + border-radius: 3px; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + } + } +} + +div.statistics-panel.dark { + + div.switch-button { + background-color: $lt-bg-color-lvl2-dark; + color: $lt-font-color-lvl2-dark; + } + + div.switch-button:hover { + background-color: $lt-bg-color-lvl1-dark; + color: $lt-font-color-lvl1-dark; + } +} + +div.statistics-panel.light { + + div.switch-button { + background-color: $lt-bg-color-lvl2-light; + color: $lt-font-color-lvl2-light; + } + + div.switch-button:hover { + background-color: $lt-bg-color-lvl1-light; + color: $lt-font-color-lvl1-light; + } } \ No newline at end of file diff --git a/source/Panel/Statistics/Statistics.tsx b/source/Panel/Statistics/Statistics.tsx index a893fb3..b8c64e4 100644 --- a/source/Panel/Statistics/Statistics.tsx +++ b/source/Panel/Statistics/Statistics.tsx @@ -1,20 +1,139 @@ import { Component, ReactNode } from "react"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; -import { useSetting, IMixinSettingProps } from "@Context/Setting"; -import Theme from "@Component/Theme/Theme"; +import { useSettingWithEvent, IMixinSettingProps, Themes } from "@Context/Setting"; +import { + Chart as ChartJS, CategoryScale, LinearScale, + BarElement, Tooltip, Legend +} from 'chart.js'; +import { Bar } from 'react-chartjs-2'; +import { Theme } from "@Component/Theme/Theme"; +import { Icon } from "@fluentui/react"; +import { Model } from "@Model/Model"; +import { Group } from "@Model/Group"; +import { ActuatorModel } from "@Model/Actuator"; +import { Message } from "@Input/Message/Message"; import "./Statistics.scss"; +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Tooltip, + Legend +); + +enum ChartType { + +} + interface IStatisticsProps { } -@useSetting -@useStatusWithEvent("labelChange", "focusLabelChange", "labelAttrChange") +@useSettingWithEvent("themes", "language", "lineChartType") +@useStatusWithEvent("focusClipChange", "actuatorStartChange", "fileLoad", "modelUpdate", "individualChange") class Statistics extends Component { + public barDarkOption = { + responsive: true, + maintainAspectRatio: false, + plugins: { legend: { + position: 'bottom' as const, + labels: { boxWidth: 10, boxHeight: 10, color: 'rgba(255, 255, 255, .5)' } + }}, + scales: { + x: { grid: { color: 'rgba(255, 255, 255, .2)' }, title: { color: 'rgba(255, 255, 255, .5)'} }, + y: { grid: { color: 'rgba(255, 255, 255, .2)', borderDash: [3, 3] }, title: { color: 'rgba(255, 255, 255, .5)'} } + } + }; + + public barLightOption = { + responsive: true, + maintainAspectRatio: false, + plugins: { legend: { + position: 'bottom' as const, + labels: { boxWidth: 10, boxHeight: 10, color: 'rgba(0, 0, 0, .5)' } + }}, + scales: { + x: { grid: { color: 'rgba(0, 0, 0, .2)' }, title: { color: 'rgba(0, 0, 0, .5)'} }, + y: { grid: { color: 'rgba(0, 0, 0, .2)', borderDash: [3, 3] }, title: { color: 'rgba(0, 0, 0, .5)'} } + } + }; + + private modelBarChart(model: Model, theme: boolean) { + + const datasets: any[] = []; + const labels: any[] = ["Group"]; + + // 收集数据 + model.objectPool.forEach((obj) => { + let label = obj.displayName; + let color = `rgb(${obj.color.map((v) => Math.floor(v * 255)).join(",")})`; + + if (obj instanceof Group) { + datasets.push({label, data: [obj.individuals.size], backgroundColor: color}); + } + }); + + if (datasets.length <= 0) { + return + } + + return + } + + private renderChart() { + + let themes = this.props.setting?.themes === Themes.light; + + // 播放模式 + if (this.props.status?.focusClip) { + return this.modelBarChart(this.props.status.model, themes); + } + + // 正在录制中 + else if ( + this.props.status?.actuator.mod === ActuatorModel.Record || + this.props.status?.actuator.mod === ActuatorModel.Offline + ) { + return this.modelBarChart(this.props.status.model, themes); + } + + // 主时钟运行 + else if (this.props.status) { + return this.modelBarChart(this.props.status.model, themes); + } + } + public render(): ReactNode { return + { + ( + this.props.status?.focusClip || + this.props.status?.actuator.mod === ActuatorModel.Record || + this.props.status?.actuator.mod === ActuatorModel.Offline + ) ? + +
+
{ + this.props.setting?.setProps("lineChartType", !this.props.setting?.lineChartType); + }}> + +
+
+ + : null + } + +
+ { this.renderChart() } +
; } } -- 2.45.2 From 4ea3c9e1f46c6a03e3040accc152cff348466002 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Thu, 26 May 2022 17:08:04 +0800 Subject: [PATCH 4/5] Add statistics panel --- source/Panel/Statistics/Statistics.tsx | 131 +++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 9 deletions(-) diff --git a/source/Panel/Statistics/Statistics.tsx b/source/Panel/Statistics/Statistics.tsx index b8c64e4..230940f 100644 --- a/source/Panel/Statistics/Statistics.tsx +++ b/source/Panel/Statistics/Statistics.tsx @@ -3,15 +3,17 @@ import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { useSettingWithEvent, IMixinSettingProps, Themes } from "@Context/Setting"; import { Chart as ChartJS, CategoryScale, LinearScale, - BarElement, Tooltip, Legend + BarElement, Tooltip, Legend, Decimation, + PointElement, LineElement, Title } from 'chart.js'; -import { Bar } from 'react-chartjs-2'; +import { Bar, Line } from 'react-chartjs-2'; import { Theme } from "@Component/Theme/Theme"; import { Icon } from "@fluentui/react"; import { Model } from "@Model/Model"; import { Group } from "@Model/Group"; import { ActuatorModel } from "@Model/Actuator"; import { Message } from "@Input/Message/Message"; +import { Clip, IFrame } from "@Model/Clip"; import "./Statistics.scss"; ChartJS.register( @@ -19,19 +21,19 @@ ChartJS.register( LinearScale, BarElement, Tooltip, - Legend + Legend, + PointElement, + LineElement, + Title, + Decimation ); -enum ChartType { - -} - interface IStatisticsProps { } @useSettingWithEvent("themes", "language", "lineChartType") -@useStatusWithEvent("focusClipChange", "actuatorStartChange", "fileLoad", "modelUpdate", "individualChange") +@useStatusWithEvent("focusClipChange", "actuatorStartChange", "fileLoad", "modelUpdate", "recordLoop", "individualChange") class Statistics extends Component { public barDarkOption = { @@ -85,13 +87,121 @@ class Statistics extends Component } + private clipBarChart(frame: IFrame, theme: boolean) { + + const datasets: any[] = []; + const labels: any[] = ["Group"]; + + // 收集数据 + frame.commands.forEach((command) => { + let label = command.name; + let color = `rgb(${command.parameter?.color.map((v: number) => Math.floor(v * 255)).join(",")})`; + + if (command.type === "points") { + datasets.push({label, data: [(command.data?.length ?? 0) / 3], backgroundColor: color}); + } + }); + + if (datasets.length <= 0) { + return + } + + return + } + + public lineDarkOption = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom' as const, + labels: { boxWidth: 10, boxHeight: 10, color: 'rgba(255, 255, 255, .5)' }, + decimation: { enabled: true } + }, + decimation: { enabled: true, algorithm: "lttb" as const, samples: 100 } + }, + scales: { + x: { grid: { color: 'rgba(255, 255, 255, .2)' }, type: "linear", title: { color: 'rgba(255, 255, 255, .5)'} }, + y: { grid: { color: 'rgba(255, 255, 255, .2)', borderDash: [3, 3] }, title: { color: 'rgba(255, 255, 255, .5)'} } + } + }; + + public lineLightOption = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom' as const, + labels: { boxWidth: 10, boxHeight: 10, color: 'rgba(0, 0, 0, .5)' }, + }, + decimation: { enabled: true, algorithm: "lttb" as const, samples: 100 } + }, + scales: { + x: { grid: { color: 'rgba(0, 0, 0, .2)' }, title: { color: 'rgba(0, 0, 0, .5)'} }, + y: { grid: { color: 'rgba(0, 0, 0, .2)', borderDash: [3, 3] }, title: { color: 'rgba(0, 0, 0, .5)'} } + } + }; + + private clipLineChart(clip: Clip, theme: boolean) { + + type IDataSet = {label: string, data: number[], backgroundColor: string, id: string}; + const datasets: IDataSet[] = []; + const labels: number[] = []; + + // 收集数据 + clip.frames.forEach((frame) => { + labels.push(frame.process); + frame.commands.forEach((command) => { + + if (command.type !== "points") return; + + let findKey: IDataSet | undefined; + for (let i = 0; i < datasets.length; i++) { + if (datasets[i].id === command.id) { + findKey = datasets[i]; + break; + } + } + + if (findKey) { + findKey.data.push((command.data?.length ?? 0) / 3); + } else { + findKey = {} as any; + findKey!.data = [(command.data?.length ?? 0) / 3]; + findKey!.label = command.name ?? ""; + findKey!.backgroundColor = `rgb(${command.parameter?.color.map((v: number) => Math.floor(v * 255)).join(",")})`; + findKey!.id = command.id; + datasets.push(findKey!); + } + }) + }); + + if (datasets.length <= 0) { + return + } + + return ; + } + private renderChart() { let themes = this.props.setting?.themes === Themes.light; + let lineChartType = this.props.setting?.lineChartType; // 播放模式 if (this.props.status?.focusClip) { - return this.modelBarChart(this.props.status.model, themes); + if (this.props.status.actuator.playClip && lineChartType) { + return this.clipLineChart(this.props.status.actuator.playClip, themes); + } + if (this.props.status.actuator.playFrame) { + return this.clipBarChart(this.props.status.actuator.playFrame, themes); + } } // 正在录制中 @@ -99,6 +209,9 @@ class Statistics extends Component Date: Thu, 26 May 2022 21:47:42 +0800 Subject: [PATCH 5/5] Add statistics panel --- source/Panel/Statistics/Statistics.scss | 2 +- source/Panel/Statistics/Statistics.tsx | 69 +++++++++++++++++++++---- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/source/Panel/Statistics/Statistics.scss b/source/Panel/Statistics/Statistics.scss index 9e2b23e..692925c 100644 --- a/source/Panel/Statistics/Statistics.scss +++ b/source/Panel/Statistics/Statistics.scss @@ -8,7 +8,7 @@ div.statistics-panel { div.statistics-chart { box-sizing: border-box; padding-top: 10px; - max-width: 300px; + max-width: 400px; min-height: 100%; height: 100%; width: 100%; diff --git a/source/Panel/Statistics/Statistics.tsx b/source/Panel/Statistics/Statistics.tsx index 230940f..3a132c1 100644 --- a/source/Panel/Statistics/Statistics.tsx +++ b/source/Panel/Statistics/Statistics.tsx @@ -9,7 +9,7 @@ import { import { Bar, Line } from 'react-chartjs-2'; import { Theme } from "@Component/Theme/Theme"; import { Icon } from "@fluentui/react"; -import { Model } from "@Model/Model"; +import { IAnyObject, Model } from "@Model/Model"; import { Group } from "@Model/Group"; import { ActuatorModel } from "@Model/Actuator"; import { Message } from "@Input/Message/Message"; @@ -147,13 +147,18 @@ class Statistics extends Component | undefined; + let lastProcess: number | undefined; // 收集数据 clip.frames.forEach((frame) => { - labels.push(frame.process); + + const frameData = new Map(); + frame.commands.forEach((command) => { if (command.type !== "points") return; @@ -165,27 +170,71 @@ class Statistics extends Component Math.floor(v * 255)).join(",")})`; - if (findKey) { - findKey.data.push((command.data?.length ?? 0) / 3); - } else { findKey = {} as any; - findKey!.data = [(command.data?.length ?? 0) / 3]; findKey!.label = command.name ?? ""; - findKey!.backgroundColor = `rgb(${command.parameter?.color.map((v: number) => Math.floor(v * 255)).join(",")})`; + findKey!.backgroundColor = color; + findKey!.borderColor = color; findKey!.id = command.id; + findKey!.pointRadius = 0; + findKey!.borderWidth = 1.5; + findKey!.borderCapStyle = "round"; + findKey!.borderJoinStyle = "round"; + findKey!.pointHitRadius = 8; + + // 补充数据 + findKey!.data = new Array(frameLen).fill(0); + datasets.push(findKey!); } - }) + }); + + // 与上一帧数据进行对比 + const isSameData = datasets.every((value: IDataSet) => { + if (value.data[frameLen - 1] === frameData.get(value.id)) { + return true; + } else { + return false; + } + }); + + lastDataSet = frameData; + lastProcess = frame.process; + + // 如果是不同数据 纪录 + if (!isSameData) { + datasets.forEach((value: IDataSet) => { + value.data.push(frameData.get(value.id) ?? 0); + }); + frameLen ++; + labels.push(frame.process); + } }); + // 记录最后一帧数据 + if (lastDataSet && lastProcess !== labels[labels.length - 1]) { + datasets.forEach((value: IDataSet) => { + value.data.push(lastDataSet!.get(value.id) ?? 0); + }); + frameLen ++; + labels.push(lastProcess!); + } + if (datasets.length <= 0) { return } return ; } -- 2.45.2