more stats
This commit is contained in:
parent
36c6d87cd3
commit
d12a82acdd
|
@ -7,10 +7,10 @@ import {
|
||||||
setBrightness,
|
setBrightness,
|
||||||
getDateTimeString,
|
getDateTimeString,
|
||||||
} from '../core/utils';
|
} from '../core/utils';
|
||||||
|
import { selectIsDarkMode } from '../store/selectors/gui';
|
||||||
import { parseParagraph } from '../core/MarkdownParser';
|
import { parseParagraph } from '../core/MarkdownParser';
|
||||||
|
|
||||||
|
|
||||||
const selectStyle = (state) => state.gui.style.indexOf('dark') !== -1;
|
|
||||||
|
|
||||||
function ChatMessage({
|
function ChatMessage({
|
||||||
name,
|
name,
|
||||||
|
@ -20,9 +20,7 @@ function ChatMessage({
|
||||||
ts,
|
ts,
|
||||||
openCm,
|
openCm,
|
||||||
}) {
|
}) {
|
||||||
const isDarkMode = useSelector(
|
const isDarkMode = useSelector(selectIsDarkMode);
|
||||||
selectStyle,
|
|
||||||
);
|
|
||||||
const refEmbed = useRef();
|
const refEmbed = useRef();
|
||||||
|
|
||||||
const isInfo = (name === 'info');
|
const isInfo = (name === 'info');
|
||||||
|
|
|
@ -17,11 +17,24 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
LineController,
|
LineController,
|
||||||
|
ArcElement,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { Line } from 'react-chartjs-2';
|
import { Line, Pie } from 'react-chartjs-2';
|
||||||
|
|
||||||
|
import { selectIsDarkMode } from '../store/selectors/gui';
|
||||||
import { selectStats } from '../store/selectors/ranks';
|
import { selectStats } from '../store/selectors/ranks';
|
||||||
import { colorFromText } from '../core/utils';
|
import {
|
||||||
|
getCHistChartOpts,
|
||||||
|
getCHistChartData,
|
||||||
|
getOnlineStatsOpts,
|
||||||
|
getOnlineStatsData,
|
||||||
|
getHistChartOpts,
|
||||||
|
getHistChartData,
|
||||||
|
getCPieOpts,
|
||||||
|
getCPieData,
|
||||||
|
getPDailyStatsOpts,
|
||||||
|
getPDailyStatsData,
|
||||||
|
} from '../core/chartSettings';
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
|
@ -32,88 +45,10 @@ ChartJS.register(
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
LineController,
|
LineController,
|
||||||
|
// for pie chart
|
||||||
|
ArcElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = {
|
|
||||||
responsive: true,
|
|
||||||
aspectRatio: 1.2,
|
|
||||||
color: '#e6e6e6',
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
grid: {
|
|
||||||
drawBorder: false,
|
|
||||||
color: '#656565',
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: '#e6e6e6',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
grid: {
|
|
||||||
drawBorder: false,
|
|
||||||
color: '#656565',
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: '#e6e6e6',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false,
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
position: 'top',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
color: '#e6e6e6',
|
|
||||||
text: 'Top 10 Countries [pxls / day]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const onlineStatsOptions = {
|
|
||||||
responsive: true,
|
|
||||||
color: '#e6e6e6',
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
grid: {
|
|
||||||
drawBorder: false,
|
|
||||||
color: '#656565',
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: '#e6e6e6',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
grid: {
|
|
||||||
drawBorder: false,
|
|
||||||
color: '#656565',
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: '#e6e6e6',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false,
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
color: '#e6e6e6',
|
|
||||||
text: 'Players Online per full hour',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const Rankings = () => {
|
const Rankings = () => {
|
||||||
const [area, setArea] = useState('total');
|
const [area, setArea] = useState('total');
|
||||||
const [
|
const [
|
||||||
|
@ -124,82 +59,80 @@ const Rankings = () => {
|
||||||
onlineStats,
|
onlineStats,
|
||||||
cHistStats,
|
cHistStats,
|
||||||
histStats,
|
histStats,
|
||||||
|
pDailyStats,
|
||||||
] = useSelector(selectStats, shallowEqual);
|
] = useSelector(selectStats, shallowEqual);
|
||||||
|
const isDarkMode = useSelector(selectIsDarkMode);
|
||||||
|
|
||||||
const cHistData = useMemo(() => {
|
const cHistData = useMemo(() => {
|
||||||
if (area !== 'charts') {
|
if (area !== 'charts') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const dataPerCountry = {};
|
return getCHistChartData(cHistStats);
|
||||||
const labels = [];
|
|
||||||
let ts = Date.now();
|
|
||||||
let c = cHistStats.length;
|
|
||||||
while (c) {
|
|
||||||
const dAmount = cHistStats.length - c;
|
|
||||||
c -= 1;
|
|
||||||
// x label
|
|
||||||
const date = new Date(ts);
|
|
||||||
labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`);
|
|
||||||
ts -= 1000 * 3600 * 24;
|
|
||||||
// y data per country
|
|
||||||
const dailyRanks = cHistStats[c];
|
|
||||||
for (let i = 0; i < dailyRanks.length; i += 1) {
|
|
||||||
const { cc, px } = dailyRanks[i];
|
|
||||||
if (!dataPerCountry[cc]) {
|
|
||||||
dataPerCountry[cc] = [];
|
|
||||||
}
|
|
||||||
const countryDat = dataPerCountry[cc];
|
|
||||||
while (countryDat.length < dAmount) {
|
|
||||||
countryDat.push(null);
|
|
||||||
}
|
|
||||||
countryDat.push(px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(dataPerCountry);
|
|
||||||
const countries = Object.keys(dataPerCountry);
|
|
||||||
const datasets = countries.map((cc) => {
|
|
||||||
const color = colorFromText(`${cc}${cc}${cc}${cc}${cc}`);
|
|
||||||
return {
|
|
||||||
label: cc,
|
|
||||||
data: dataPerCountry[cc],
|
|
||||||
borderColor: color,
|
|
||||||
backgroundColor: color,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
labels,
|
|
||||||
datasets,
|
|
||||||
};
|
|
||||||
}, [area, cHistStats]);
|
}, [area, cHistStats]);
|
||||||
|
|
||||||
|
const cHistOpts = useMemo(() => {
|
||||||
|
if (area !== 'charts') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getCHistChartOpts(isDarkMode);
|
||||||
|
}, [area, isDarkMode]);
|
||||||
|
|
||||||
const onlineData = useMemo(() => {
|
const onlineData = useMemo(() => {
|
||||||
if (area !== 'charts') {
|
if (area !== 'charts') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const labels = [];
|
return getOnlineStatsData(onlineStats);
|
||||||
const data = [];
|
|
||||||
let ts = Date.now();
|
|
||||||
let c = onlineStats.length;
|
|
||||||
while (c) {
|
|
||||||
c -= 1;
|
|
||||||
const date = new Date(ts);
|
|
||||||
const hours = date.getHours();
|
|
||||||
const key = hours || `${date.getMonth() + 1} / ${date.getDate()}`;
|
|
||||||
labels.unshift(String(key));
|
|
||||||
ts -= 1000 * 3600;
|
|
||||||
data.push(onlineStats[c]);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
labels,
|
|
||||||
datasets: [{
|
|
||||||
label: 'Players',
|
|
||||||
data,
|
|
||||||
borderColor: '#3fadda',
|
|
||||||
backgroundColor: '#3fadda',
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
}, [area, onlineStats]);
|
}, [area, onlineStats]);
|
||||||
|
|
||||||
|
const onlineOpts = useMemo(() => {
|
||||||
|
if (area !== 'charts') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getOnlineStatsOpts(isDarkMode);
|
||||||
|
}, [area, isDarkMode]);
|
||||||
|
|
||||||
|
const histData = useMemo(() => {
|
||||||
|
if (area !== 'charts') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getHistChartData(histStats);
|
||||||
|
}, [area, histStats]);
|
||||||
|
|
||||||
|
const histOpts = useMemo(() => {
|
||||||
|
if (area !== 'charts') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getHistChartOpts(isDarkMode);
|
||||||
|
}, [area, isDarkMode]);
|
||||||
|
|
||||||
|
const pDailyData = useMemo(() => {
|
||||||
|
if (area !== 'charts') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getPDailyStatsData(pDailyStats);
|
||||||
|
}, [area, pDailyStats]);
|
||||||
|
|
||||||
|
const pDailyOpts = useMemo(() => {
|
||||||
|
if (area !== 'charts') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getPDailyStatsOpts(isDarkMode);
|
||||||
|
}, [area, isDarkMode]);
|
||||||
|
|
||||||
|
const cPieData = useMemo(() => {
|
||||||
|
if (area !== 'countries') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getCPieData(dailyCRanking);
|
||||||
|
}, [area, dailyCRanking]);
|
||||||
|
|
||||||
|
const cPieOpts = useMemo(() => {
|
||||||
|
if (area !== 'countries') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getCPieOpts(isDarkMode);
|
||||||
|
}, [area, isDarkMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
|
@ -246,8 +179,15 @@ const Rankings = () => {
|
||||||
(area === 'charts') ? 'modallink selected' : 'modallink'
|
(area === 'charts') ? 'modallink selected' : 'modallink'
|
||||||
}
|
}
|
||||||
onClick={() => setArea('charts')}
|
onClick={() => setArea('charts')}
|
||||||
>{t`Chart`}</span>
|
> {t`Charts`}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
|
{(area === 'countries') && (
|
||||||
|
<>
|
||||||
|
<Pie options={cPieOpts} data={cPieData} />
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{(['total', 'today', 'yesterday', 'countries'].includes(area)) && (
|
{(['total', 'today', 'yesterday', 'countries'].includes(area)) && (
|
||||||
<table style={{
|
<table style={{
|
||||||
display: 'inline',
|
display: 'inline',
|
||||||
|
@ -336,8 +276,10 @@ const Rankings = () => {
|
||||||
)}
|
)}
|
||||||
{(area === 'charts') && (
|
{(area === 'charts') && (
|
||||||
<>
|
<>
|
||||||
<Line options={options} data={cHistData} />
|
<Line options={cHistOpts} data={cHistData} />
|
||||||
<Line options={onlineStatsOptions} data={onlineData} />
|
<Line options={onlineOpts} data={onlineData} />
|
||||||
|
<Line options={pDailyOpts} data={pDailyData} />
|
||||||
|
<Line options={histOpts} data={histData} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -12,6 +12,10 @@ import {
|
||||||
getCountryDailyHistory,
|
getCountryDailyHistory,
|
||||||
getCountryRanks,
|
getCountryRanks,
|
||||||
getTopDailyHistory,
|
getTopDailyHistory,
|
||||||
|
storeHourlyPixelsPlaced,
|
||||||
|
getHourlyPixelStats,
|
||||||
|
getDailyPixelStats,
|
||||||
|
populateDailyTotal,
|
||||||
} from '../data/redis/ranks';
|
} from '../data/redis/ranks';
|
||||||
import socketEvents from '../socket/socketEvents';
|
import socketEvents from '../socket/socketEvents';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
@ -22,13 +26,24 @@ import { DailyCron, HourlyCron } from '../utils/cron';
|
||||||
class Ranks {
|
class Ranks {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.ranks = {
|
this.ranks = {
|
||||||
|
// ranking today of users by pixels
|
||||||
dailyRanking: [],
|
dailyRanking: [],
|
||||||
|
// ranking of users by pixels
|
||||||
ranking: [],
|
ranking: [],
|
||||||
|
// ranking today of countries by pixels
|
||||||
dailyCRanking: [],
|
dailyCRanking: [],
|
||||||
|
// yesterdays ranking of users by pixels
|
||||||
prevTop: [],
|
prevTop: [],
|
||||||
|
// online user amount by hour
|
||||||
onlineStats: [],
|
onlineStats: [],
|
||||||
|
// ranking of countries by day
|
||||||
cHistStats: [],
|
cHistStats: [],
|
||||||
|
// ranking of users by day
|
||||||
histStats: [],
|
histStats: [],
|
||||||
|
// pixels placed by hour
|
||||||
|
pHourlyStats: [],
|
||||||
|
// pixels placed by day
|
||||||
|
pDailyStats: [],
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
* we go through socketEvents for sharding
|
* we go through socketEvents for sharding
|
||||||
|
@ -42,6 +57,7 @@ class Ranks {
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
|
await populateDailyTotal();
|
||||||
try {
|
try {
|
||||||
let someRanks = await Ranks.dailyUpdateRanking();
|
let someRanks = await Ranks.dailyUpdateRanking();
|
||||||
this.ranks = {
|
this.ranks = {
|
||||||
|
@ -95,10 +111,12 @@ class Ranks {
|
||||||
const cHistStats = await getCountryDailyHistory();
|
const cHistStats = await getCountryDailyHistory();
|
||||||
const histStats = await getTopDailyHistory();
|
const histStats = await getTopDailyHistory();
|
||||||
histStats.users = await populateRanking(histStats.users);
|
histStats.users = await populateRanking(histStats.users);
|
||||||
|
const pHourlyStats = await getHourlyPixelStats();
|
||||||
const ret = {
|
const ret = {
|
||||||
onlineStats,
|
onlineStats,
|
||||||
cHistStats,
|
cHistStats,
|
||||||
histStats,
|
histStats,
|
||||||
|
pHourlyStats,
|
||||||
};
|
};
|
||||||
if (socketEvents.amIImportant()) {
|
if (socketEvents.amIImportant()) {
|
||||||
// only main shard sends to others
|
// only main shard sends to others
|
||||||
|
@ -111,8 +129,10 @@ class Ranks {
|
||||||
const prevTop = await populateRanking(
|
const prevTop = await populateRanking(
|
||||||
await getPrevTop(),
|
await getPrevTop(),
|
||||||
);
|
);
|
||||||
|
const pDailyStats = await getDailyPixelStats();
|
||||||
const ret = {
|
const ret = {
|
||||||
prevTop,
|
prevTop,
|
||||||
|
pDailyStats,
|
||||||
};
|
};
|
||||||
if (socketEvents.amIImportant()) {
|
if (socketEvents.amIImportant()) {
|
||||||
// only main shard sends to others
|
// only main shard sends to others
|
||||||
|
@ -127,6 +147,7 @@ class Ranks {
|
||||||
}
|
}
|
||||||
const amount = socketEvents.onlineCounter.total;
|
const amount = socketEvents.onlineCounter.total;
|
||||||
await storeOnlinUserAmount(amount);
|
await storeOnlinUserAmount(amount);
|
||||||
|
await storeHourlyPixelsPlaced();
|
||||||
await Ranks.hourlyUpdateRanking();
|
await Ranks.hourlyUpdateRanking();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
364
src/core/chartSettings.js
Normal file
364
src/core/chartSettings.js
Normal file
|
@ -0,0 +1,364 @@
|
||||||
|
import { t } from 'ttag';
|
||||||
|
import { colorFromText } from './utils';
|
||||||
|
|
||||||
|
export function getCHistChartOpts(isDarkMode) {
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
aspectRatio: 1.2,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: t`Top 10 Countries [pxls / day]`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (isDarkMode) {
|
||||||
|
const sColor = '#e6e6e6';
|
||||||
|
const lColor = '#656565';
|
||||||
|
options.color = sColor;
|
||||||
|
options.scales.x.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.x.grid.color = lColor;
|
||||||
|
options.scales.y.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.y.grid.color = lColor;
|
||||||
|
options.plugins.title.color = sColor;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCHistChartData(cHistStats) {
|
||||||
|
const dataPerCountry = {};
|
||||||
|
const labels = [];
|
||||||
|
let ts = Date.now();
|
||||||
|
let c = cHistStats.length;
|
||||||
|
while (c) {
|
||||||
|
const dAmount = cHistStats.length - c;
|
||||||
|
c -= 1;
|
||||||
|
// x label
|
||||||
|
const date = new Date(ts);
|
||||||
|
labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`);
|
||||||
|
ts -= 1000 * 3600 * 24;
|
||||||
|
// y data per country
|
||||||
|
const dailyRanks = cHistStats[c];
|
||||||
|
for (let i = 0; i < dailyRanks.length; i += 1) {
|
||||||
|
const { cc, px } = dailyRanks[i];
|
||||||
|
if (!dataPerCountry[cc]) {
|
||||||
|
dataPerCountry[cc] = [];
|
||||||
|
}
|
||||||
|
const countryDat = dataPerCountry[cc];
|
||||||
|
while (countryDat.length < dAmount) {
|
||||||
|
countryDat.push(null);
|
||||||
|
}
|
||||||
|
countryDat.push(px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const countries = Object.keys(dataPerCountry);
|
||||||
|
const datasets = countries.map((cc) => {
|
||||||
|
const color = colorFromText(`${cc}${cc}${cc}${cc}${cc}`);
|
||||||
|
return {
|
||||||
|
label: cc,
|
||||||
|
data: dataPerCountry[cc],
|
||||||
|
borderColor: color,
|
||||||
|
backgroundColor: color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOnlineStatsOpts(isDarkMode) {
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: t`Players Online per full hour`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (isDarkMode) {
|
||||||
|
const sColor = '#e6e6e6';
|
||||||
|
const lColor = '#656565';
|
||||||
|
options.color = sColor;
|
||||||
|
options.scales.x.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.x.grid.color = lColor;
|
||||||
|
options.scales.y.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.y.grid.color = lColor;
|
||||||
|
options.plugins.title.color = sColor;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOnlineStatsData(onlineStats) {
|
||||||
|
const labels = [];
|
||||||
|
const data = [];
|
||||||
|
let ts = Date.now();
|
||||||
|
let c = onlineStats.length;
|
||||||
|
while (c) {
|
||||||
|
c -= 1;
|
||||||
|
const date = new Date(ts);
|
||||||
|
const hours = date.getHours();
|
||||||
|
const key = hours || `${date.getMonth() + 1} / ${date.getDate()}`;
|
||||||
|
labels.unshift(String(key));
|
||||||
|
ts -= 1000 * 3600;
|
||||||
|
data.push(onlineStats[c]);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Players',
|
||||||
|
data,
|
||||||
|
borderColor: '#3fadda',
|
||||||
|
backgroundColor: '#3fadda',
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHistChartOpts(isDarkMode) {
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
aspectRatio: 1.4,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
mode: 'nearest',
|
||||||
|
axis: 'xy',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: t`Top 10 Players [pxls / day]`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (isDarkMode) {
|
||||||
|
const sColor = '#e6e6e6';
|
||||||
|
const lColor = '#656565';
|
||||||
|
options.color = sColor;
|
||||||
|
options.scales.x.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.x.grid.color = lColor;
|
||||||
|
options.scales.y.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.y.grid.color = lColor;
|
||||||
|
options.plugins.title.color = sColor;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHistChartData(histStats) {
|
||||||
|
const { users, stats } = histStats;
|
||||||
|
const dataPerUser = {};
|
||||||
|
users.forEach((u) => { dataPerUser[u.id] = { name: u.name, data: [] }; });
|
||||||
|
const labels = [];
|
||||||
|
let ts = Date.now();
|
||||||
|
let c = stats.length;
|
||||||
|
// skipping todays dataset
|
||||||
|
while (c > 1) {
|
||||||
|
const dAmount = stats.length - c;
|
||||||
|
c -= 1;
|
||||||
|
// x label
|
||||||
|
ts -= 1000 * 3600 * 24;
|
||||||
|
const date = new Date(ts);
|
||||||
|
labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`);
|
||||||
|
// y data per user
|
||||||
|
const dailyRanks = stats[c];
|
||||||
|
for (let i = 0; i < dailyRanks.length; i += 1) {
|
||||||
|
const { id, px } = dailyRanks[i];
|
||||||
|
const userDat = dataPerUser[id].data;
|
||||||
|
while (userDat.length < dAmount) {
|
||||||
|
userDat.push(null);
|
||||||
|
}
|
||||||
|
userDat.push(px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const userIds = Object.keys(dataPerUser);
|
||||||
|
const datasets = userIds.map((id) => {
|
||||||
|
const { name, data } = dataPerUser[id];
|
||||||
|
const color = colorFromText(name);
|
||||||
|
return {
|
||||||
|
label: name,
|
||||||
|
data,
|
||||||
|
borderColor: color,
|
||||||
|
backgroundColor: color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCPieOpts(isDarkMode) {
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: t`Countries by Pixels Today`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (isDarkMode) {
|
||||||
|
const sColor = '#e6e6e6';
|
||||||
|
options.plugins.title.color = sColor;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCPieData(dailyCRanking) {
|
||||||
|
const labels = [];
|
||||||
|
const data = [];
|
||||||
|
const backgroundColor = [];
|
||||||
|
dailyCRanking.forEach((r) => {
|
||||||
|
const { cc, px } = r;
|
||||||
|
labels.push(cc);
|
||||||
|
data.push(px);
|
||||||
|
const color = colorFromText(`${cc}${cc}${cc}${cc}${cc}`);
|
||||||
|
backgroundColor.push(color);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [{
|
||||||
|
label: '# of Pixels',
|
||||||
|
data,
|
||||||
|
backgroundColor,
|
||||||
|
borderWidth: 1,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPDailyStatsOpts(isDarkMode) {
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: t`Total Pixels placed per day`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (isDarkMode) {
|
||||||
|
const sColor = '#e6e6e6';
|
||||||
|
const lColor = '#656565';
|
||||||
|
options.color = sColor;
|
||||||
|
options.scales.x.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.x.grid.color = lColor;
|
||||||
|
options.scales.y.ticks = {
|
||||||
|
color: sColor,
|
||||||
|
};
|
||||||
|
options.scales.y.grid.color = lColor;
|
||||||
|
options.plugins.title.color = sColor;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPDailyStatsData(pDailyStats) {
|
||||||
|
const labels = [];
|
||||||
|
const data = [];
|
||||||
|
let ts = Date.now();
|
||||||
|
let c = pDailyStats.length;
|
||||||
|
while (c) {
|
||||||
|
c -= 1;
|
||||||
|
ts -= 1000 * 3600 * 24;
|
||||||
|
const date = new Date(ts);
|
||||||
|
labels.unshift(`${date.getUTCMonth() + 1} / ${date.getUTCDate()}`);
|
||||||
|
data.push(pDailyStats[c]);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Pixels',
|
||||||
|
data,
|
||||||
|
borderColor: '#3fadda',
|
||||||
|
backgroundColor: '#3fadda',
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
|
@ -11,6 +11,9 @@ const PREV_DAY_TOP_KEY = 'prankd';
|
||||||
const DAY_STATS_RANKS_KEY = 'ds';
|
const DAY_STATS_RANKS_KEY = 'ds';
|
||||||
const CDAY_STATS_RANKS_KEY = 'cds';
|
const CDAY_STATS_RANKS_KEY = 'cds';
|
||||||
const ONLINE_CNTR_KEY = 'tonl';
|
const ONLINE_CNTR_KEY = 'tonl';
|
||||||
|
const PREV_HOURLY_PLACED_KEY = 'tmph';
|
||||||
|
const HOURLY_PXL_CNTR_KEY = 'thpx';
|
||||||
|
const DAILY_PXL_CNTR_KEY = 'tdpx';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get pixelcount and ranking
|
* get pixelcount and ranking
|
||||||
|
@ -116,11 +119,11 @@ export async function getPrevTop() {
|
||||||
*/
|
*/
|
||||||
export async function storeOnlinUserAmount(amount) {
|
export async function storeOnlinUserAmount(amount) {
|
||||||
await client.lPush(ONLINE_CNTR_KEY, String(amount));
|
await client.lPush(ONLINE_CNTR_KEY, String(amount));
|
||||||
await client.lTrim(ONLINE_CNTR_KEY, 0, 14 * 24);
|
await client.lTrim(ONLINE_CNTR_KEY, 0, 7 * 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get list of online counters
|
* get list of online counters per hour
|
||||||
*/
|
*/
|
||||||
export async function getOnlineUserStats() {
|
export async function getOnlineUserStats() {
|
||||||
let onlineStats = await client.lRange(ONLINE_CNTR_KEY, 0, -1);
|
let onlineStats = await client.lRange(ONLINE_CNTR_KEY, 0, -1);
|
||||||
|
@ -128,6 +131,63 @@ export async function getOnlineUserStats() {
|
||||||
return onlineStats;
|
return onlineStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* calculate sum of scores of zset
|
||||||
|
* do NOT use it for large seets
|
||||||
|
*/
|
||||||
|
async function sumZSet(key) {
|
||||||
|
const ranks = await client.zRangeWithScores(key, 0, -1);
|
||||||
|
let total = 0;
|
||||||
|
ranks.forEach((r) => { total += Number(r.score); });
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* save hourly pixels placed by substracting
|
||||||
|
* the current daily total pixels set with the ones of an hour ago
|
||||||
|
*/
|
||||||
|
export async function storeHourlyPixelsPlaced() {
|
||||||
|
const tsNow = Date.now();
|
||||||
|
const prevData = await client.get(PREV_HOURLY_PLACED_KEY);
|
||||||
|
let prevTs;
|
||||||
|
let prevSum;
|
||||||
|
if (prevData) {
|
||||||
|
[prevTs, prevSum] = prevData.split(',').map((z) => Number(z));
|
||||||
|
}
|
||||||
|
|
||||||
|
let curSum = await sumZSet(DAILY_CRANKED_KEY);
|
||||||
|
await client.set(PREV_HOURLY_PLACED_KEY, `${tsNow},${curSum}`);
|
||||||
|
|
||||||
|
if (prevTs && prevTs > tsNow - 1000 * 3600 * 1.5) {
|
||||||
|
if (prevSum > curSum) {
|
||||||
|
// assume day change, add amount of yesterday
|
||||||
|
const dateKey = getDateKeyOfTs(tsNow - 1000 * 3600 * 24);
|
||||||
|
curSum += await sumZSet(`${CDAY_STATS_RANKS_KEY}:${dateKey}`);
|
||||||
|
}
|
||||||
|
const hourlyPixels = curSum - prevSum;
|
||||||
|
await client.lPush(HOURLY_PXL_CNTR_KEY, String(hourlyPixels));
|
||||||
|
await client.lTrim(HOURLY_PXL_CNTR_KEY, 0, 7 * 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get list of pixels placed per hour
|
||||||
|
*/
|
||||||
|
export async function getHourlyPixelStats() {
|
||||||
|
let pxlStats = await client.lRange(HOURLY_PXL_CNTR_KEY, 0, -1);
|
||||||
|
pxlStats = pxlStats.map((s) => Number(s));
|
||||||
|
return pxlStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get list of pixels placed per day
|
||||||
|
*/
|
||||||
|
export async function getDailyPixelStats() {
|
||||||
|
let pxlStats = await client.lRange(DAILY_PXL_CNTR_KEY, 0, -1);
|
||||||
|
pxlStats = pxlStats.map((s) => Number(s));
|
||||||
|
return pxlStats;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get top 10 of daily pixels over the past days
|
* get top 10 of daily pixels over the past days
|
||||||
*/
|
*/
|
||||||
|
@ -167,6 +227,20 @@ export async function getTopDailyHistory() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* for populating past daily totals
|
||||||
|
*/
|
||||||
|
export async function populateDailyTotal() {
|
||||||
|
await client.del(DAILY_PXL_CNTR_KEY);
|
||||||
|
for (let i = 14; i > 0; i -= 1) {
|
||||||
|
const ts = Date.now() - 1000 * 3600 * 24 * i;
|
||||||
|
const key = `${CDAY_STATS_RANKS_KEY}:${getDateKeyOfTs(ts)}`;
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const sum = await sumZSet(key);
|
||||||
|
client.lPush(DAILY_PXL_CNTR_KEY, String(sum));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get top 10 countries over the past days
|
* get top 10 countries over the past days
|
||||||
*/
|
*/
|
||||||
|
@ -209,12 +283,24 @@ export async function resetDailyRanks() {
|
||||||
const dateKey = getDateKeyOfTs(
|
const dateKey = getDateKeyOfTs(
|
||||||
Date.now() - 1000 * 3600 * 24,
|
Date.now() - 1000 * 3600 * 24,
|
||||||
);
|
);
|
||||||
|
// daily user rank
|
||||||
await client.rename(
|
await client.rename(
|
||||||
DAILY_RANKED_KEY,
|
DAILY_RANKED_KEY,
|
||||||
`${DAY_STATS_RANKS_KEY}:${dateKey}`,
|
`${DAY_STATS_RANKS_KEY}:${dateKey}`,
|
||||||
);
|
);
|
||||||
|
// daily country rank
|
||||||
await client.rename(
|
await client.rename(
|
||||||
DAILY_CRANKED_KEY,
|
DAILY_CRANKED_KEY,
|
||||||
`${CDAY_STATS_RANKS_KEY}:${dateKey}`,
|
`${CDAY_STATS_RANKS_KEY}:${dateKey}`,
|
||||||
);
|
);
|
||||||
|
// daily pixel counter
|
||||||
|
const sum = await sumZSet(`${CDAY_STATS_RANKS_KEY}:${dateKey}`);
|
||||||
|
await client.lPush(DAILY_PXL_CNTR_KEY, String(sum));
|
||||||
|
await client.lTrim(DAILY_PXL_CNTR_KEY, 0, 28);
|
||||||
|
// purge old data
|
||||||
|
const purgeDateKey = getDateKeyOfTs(
|
||||||
|
Date.now() - 1000 * 3600 * 24 * 21,
|
||||||
|
);
|
||||||
|
await client.del(`${DAY_STATS_RANKS_KEY}:${purgeDateKey}`);
|
||||||
|
await client.del(`${CDAY_STATS_RANKS_KEY}:${purgeDateKey}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,7 +324,7 @@ export function requestDeleteAccount(password) {
|
||||||
|
|
||||||
export function requestRankings() {
|
export function requestRankings() {
|
||||||
return makeAPIGETRequest(
|
return makeAPIGETRequest(
|
||||||
'https://pixelplanet.fun/ranking',
|
'/ranking',
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,6 +279,8 @@ export function receiveStats(
|
||||||
onlineStats,
|
onlineStats,
|
||||||
cHistStats,
|
cHistStats,
|
||||||
histStats,
|
histStats,
|
||||||
|
pDailyStats,
|
||||||
|
pHourlyStats,
|
||||||
} = rankings;
|
} = rankings;
|
||||||
return {
|
return {
|
||||||
type: 'REC_STATS',
|
type: 'REC_STATS',
|
||||||
|
@ -289,6 +291,8 @@ export function receiveStats(
|
||||||
onlineStats,
|
onlineStats,
|
||||||
cHistStats,
|
cHistStats,
|
||||||
histStats,
|
histStats,
|
||||||
|
pDailyStats,
|
||||||
|
pHourlyStats,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,9 @@ const initialState = {
|
||||||
prevTop: [],
|
prevTop: [],
|
||||||
onlineStats: [],
|
onlineStats: [],
|
||||||
cHistStats: [],
|
cHistStats: [],
|
||||||
histStats: [],
|
histStats: { users: [], stats: [] },
|
||||||
|
pDailyStats: [],
|
||||||
|
pHourlyStats: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ranks(
|
export default function ranks(
|
||||||
|
@ -83,11 +85,10 @@ export default function ranks(
|
||||||
onlineStats,
|
onlineStats,
|
||||||
cHistStats,
|
cHistStats,
|
||||||
histStats,
|
histStats,
|
||||||
|
pDailyStats,
|
||||||
|
pHourlyStats,
|
||||||
} = action;
|
} = action;
|
||||||
const lastFetch = Date.now();
|
const newStats = {
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
lastFetch,
|
|
||||||
totalRanking,
|
totalRanking,
|
||||||
totalDailyRanking,
|
totalDailyRanking,
|
||||||
dailyCRanking,
|
dailyCRanking,
|
||||||
|
@ -95,6 +96,14 @@ export default function ranks(
|
||||||
onlineStats,
|
onlineStats,
|
||||||
cHistStats,
|
cHistStats,
|
||||||
histStats,
|
histStats,
|
||||||
|
pDailyStats,
|
||||||
|
pHourlyStats,
|
||||||
|
};
|
||||||
|
const lastFetch = Date.now();
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
lastFetch,
|
||||||
|
...newStats,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
src/store/selectors/gui.js
Normal file
9
src/store/selectors/gui.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* selectors related to gui
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
|
||||||
|
export const selectIsDarkMode = (state) => (
|
||||||
|
state.gui.style.indexOf('dark') !== -1
|
||||||
|
);
|
|
@ -12,4 +12,5 @@ export const selectStats = (state) => [
|
||||||
state.ranks.onlineStats,
|
state.ranks.onlineStats,
|
||||||
state.ranks.cHistStats,
|
state.ranks.cHistStats,
|
||||||
state.ranks.histStats,
|
state.ranks.histStats,
|
||||||
|
state.ranks.pDailyStats,
|
||||||
];
|
];
|
||||||
|
|
|
@ -15,7 +15,7 @@ import canvas from './reducers/canvas';
|
||||||
import chat from './reducers/chat';
|
import chat from './reducers/chat';
|
||||||
import fetching from './reducers/fetching';
|
import fetching from './reducers/fetching';
|
||||||
|
|
||||||
export const CURRENT_VERSION = 12;
|
export const CURRENT_VERSION = 14;
|
||||||
|
|
||||||
export const migrate = (state, version) => {
|
export const migrate = (state, version) => {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
|
Loading…
Reference in New Issue
Block a user