Add pixel statistics
This commit is contained in:
@@ -11,6 +11,7 @@ import { debounce } from "lodash";
|
||||
import { TC_LOGO } from "../../constants";
|
||||
import styled from "styled-components";
|
||||
import { MuralEx } from "../../interfaces";
|
||||
import { PieCharts } from "./pixelCharts";
|
||||
|
||||
const Img = styled.img`
|
||||
width: 25px;
|
||||
@@ -35,6 +36,7 @@ interface Props {
|
||||
interface State extends StoreSettings{
|
||||
selected?: MuralEx;
|
||||
overlays: number[];
|
||||
showCharts: boolean;
|
||||
phantomOverlay: number;
|
||||
overlayModify?: OverlayReturn;
|
||||
}
|
||||
@@ -46,6 +48,7 @@ export class Main extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showCharts: false,
|
||||
overlays: [],
|
||||
phantomOverlay: -1,
|
||||
opacity: 50,
|
||||
@@ -106,8 +109,14 @@ export class Main extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<MovableWindow title={this.title()} storage={this.props.storage} storageKey="main" >
|
||||
{this.state.showCharts ?
|
||||
<MovableWindow title={"Pixel chart"} storage={this.props.storage} storageKey="charts">
|
||||
<PieCharts palette={this.props.palette} store={this.props.store}/>
|
||||
</MovableWindow>
|
||||
: null}
|
||||
<MovableWindow title={this.title()} storage={this.props.storage} storageKey="main">
|
||||
<Menu
|
||||
showChart={() => this.setState({showCharts: !this.state.showCharts})}
|
||||
onOpacityChange={this.opacityChange}
|
||||
cords={this.props.cords}
|
||||
store={this.props.store}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { faCamera, faCaretDown, faCaretUp, faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCamera, faCaretDown, faCaretUp, faPieChart, faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
@@ -76,6 +76,7 @@ interface Props {
|
||||
storage: Storage;
|
||||
opacity: number;
|
||||
collapsed: boolean;
|
||||
showChart: () => void;
|
||||
onOpacityChange: (n: number) => void;
|
||||
onCollapsedChanged: (b: boolean) => void;
|
||||
}
|
||||
@@ -130,13 +131,30 @@ export class Menu extends React.Component<Props, State> {
|
||||
this.setState({takingCanvasShot: false});
|
||||
};
|
||||
render() {
|
||||
return <Container>
|
||||
return<Container>
|
||||
<Flex>
|
||||
<Btn onClick={this.screenshot} disabled={this.state.takingCanvasShot || !this.state.canvasShotAvailable} title="Screenshot"><FontAwesomeIcon icon={ faCamera } /></Btn>
|
||||
<Btn onClick={this.import} title="Import"><FontAwesomeIcon icon={ faUpload } /></Btn>
|
||||
<Btn
|
||||
onClick={this.screenshot}
|
||||
disabled={this.state.takingCanvasShot || !this.state.canvasShotAvailable}
|
||||
title="Screenshot">
|
||||
<FontAwesomeIcon icon={ faCamera } />
|
||||
</Btn>
|
||||
<Btn
|
||||
onClick={this.import}
|
||||
title="Import">
|
||||
<FontAwesomeIcon icon={ faUpload } />
|
||||
</Btn>
|
||||
<Btn
|
||||
onClick={() => this.props.showChart()}
|
||||
title="Pixels graph">
|
||||
<FontAwesomeIcon icon={ faPieChart } />
|
||||
</Btn>
|
||||
<InputRange type="range" min={0} max={100} value={this.props.opacity} onInput={this.inputElement} />
|
||||
<PercentageDiv>{this.props.opacity}%</PercentageDiv>
|
||||
<Btn onClick={() => this.props.onCollapsedChanged(!this.props.collapsed)}><FontAwesomeIcon icon={ this.props.collapsed ? faCaretDown : faCaretUp } /></Btn>
|
||||
<Btn
|
||||
onClick={() => this.props.onCollapsedChanged(!this.props.collapsed)}>
|
||||
<FontAwesomeIcon icon={ this.props.collapsed ? faCaretDown : faCaretUp } />
|
||||
</Btn>
|
||||
</Flex>
|
||||
{this.props.collapsed ? null : <MuralList store={this.props.store} palette={this.props.palette} cords={this.props.cords}/> }
|
||||
</Container>;
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from "react";
|
||||
import { Palette } from "../../lib/palette";
|
||||
import { Store, StoreEvents } from "../../lib/store";
|
||||
import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";
|
||||
import styled from "styled-components";
|
||||
import { formatNumber } from "../../lib/utils";
|
||||
|
||||
const ColorBlock = styled.div`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 5px;
|
||||
border: 1px solid black;
|
||||
border-radius: 12px;
|
||||
`;
|
||||
|
||||
const ColorLine = styled.div`
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 15px;
|
||||
`;
|
||||
const FlexList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
height: 100%;
|
||||
`;
|
||||
interface PixelData {
|
||||
color: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
total: number;
|
||||
groupData?: PixelData[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
store: Store;
|
||||
palette: Palette;
|
||||
}
|
||||
|
||||
export class PieCharts extends React.Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.refresh();
|
||||
this.props.store.on(StoreEvents.PixelLog, this.refresh);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.props.store.off(StoreEvents.PixelLog, this.refresh);
|
||||
}
|
||||
private refresh = async () => {
|
||||
const colors: string[] = [];
|
||||
const groupData: PixelData[] = [];
|
||||
let total = 0;
|
||||
for (let i = 0; i < this.props.palette.hex.length; i++) {
|
||||
const hex = this.props.palette.hex[i];
|
||||
colors.push(hex);
|
||||
const logs = await this.props.store.fetchPixelsLogByColor(i);
|
||||
if (logs.length) {
|
||||
total += logs.length;
|
||||
groupData.push({
|
||||
color: hex,
|
||||
value: logs.length
|
||||
});
|
||||
}
|
||||
}
|
||||
groupData.sort((a,b) => a.value > b.value ? -1 : 1);
|
||||
this.setState(({
|
||||
groupData,
|
||||
total
|
||||
}));
|
||||
};
|
||||
render(): React.ReactNode
|
||||
{
|
||||
if (!this.state.groupData ) {
|
||||
return <div>Loading</div>;
|
||||
}
|
||||
|
||||
if (!this.state.groupData.length) {
|
||||
return <div>No data! Please a pixel to see pixel chart</div>;
|
||||
}
|
||||
const style:React.CSSProperties = {
|
||||
width: Math.min(250, window.innerWidth - 20),
|
||||
height: Math.min(250, window.innerHeight - 20)
|
||||
};
|
||||
|
||||
return <div>
|
||||
<h5>Total pixels: {formatNumber(this.state.total)}</h5>
|
||||
<div style={style}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart width={400} height={400}>
|
||||
<Pie
|
||||
data={this.state.groupData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
>
|
||||
{this.state.groupData.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={entry.color}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<FlexList style={{ width: Math.min(250, window.innerWidth - 20)}}>
|
||||
{this.state.groupData.map((e, i) => {
|
||||
return <ColorLine key={i}>
|
||||
<ColorBlock style={{
|
||||
backgroundColor: e.color
|
||||
}}></ColorBlock>{formatNumber(e.value)}</ColorLine>;
|
||||
})}
|
||||
|
||||
</FlexList>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import styled from "styled-components";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSquare } from "@fortawesome/free-solid-svg-icons";
|
||||
import { formatNumber } from "../../lib/utils";
|
||||
import { StoreEvents } from "../../lib/store";
|
||||
|
||||
const CountBox = styled.div<{width: number}>`
|
||||
position: fixed;
|
||||
@@ -48,20 +49,16 @@ export class PixelCount extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.props.pixelCount.count.then(countMaybe => {
|
||||
if (typeof countMaybe === "number") {
|
||||
this.updateCount(countMaybe);
|
||||
}
|
||||
});
|
||||
this.props.pixelCount.on(this.updateCount);
|
||||
window.addEventListener("resize", this.onResize);
|
||||
this.updateCount();
|
||||
this.props.pixelCount.store.on(StoreEvents.PixelLog, this.updateCount);
|
||||
window.addEventListener("resize", this.onResize);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.props.pixelCount.on(this.updateCount);
|
||||
this.props.pixelCount.store.off(StoreEvents.PixelLog, this.updateCount);
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
}
|
||||
updateCount = (count: number) => {
|
||||
this.setState({count});
|
||||
updateCount = async () => {
|
||||
this.setState({count: await this.props.pixelCount.getCount()});
|
||||
};
|
||||
onResize = () => {
|
||||
this.setState({ width: window.innerWidth });
|
||||
|
||||
Reference in New Issue
Block a user