import { Component, ReactNode } from "react"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { useSettingWithEvent, IMixinSettingProps, Themes } from "@Context/Setting"; import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Tooltip, Legend, Decimation, PointElement, LineElement, Title } from 'chart.js'; 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( CategoryScale, LinearScale, BarElement, Tooltip, Legend, PointElement, LineElement, Title, Decimation ); interface IStatisticsProps { } @useSettingWithEvent("themes", "language", "lineChartType") @useStatusWithEvent("focusClipChange", "actuatorStartChange", "fileLoad", "modelUpdate", "recordLoop", "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 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) { 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); } } // 正在录制中 else if ( this.props.status?.actuator.mod === ActuatorModel.Record || this.props.status?.actuator.mod === ActuatorModel.Offline ) { if (this.props.status.actuator.recordClip && lineChartType) { return this.clipLineChart(this.props.status.actuator.recordClip, themes); } 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() }
; } } export { Statistics };