gsd 1 day ago
commit
a68fed7012
  1. 4
      .gitignore
  2. 8
      .idea/.gitignore
  3. 6
      .idea/jsLibraryMappings.xml
  4. 6
      .idea/misc.xml
  5. 8
      .idea/modules.xml
  6. 6
      .idea/vcs.xml
  7. 8
      ChartJsSSR.iml
  8. 8
      Dockerfile
  9. 143
      chartGenerator.js
  10. 38
      chartWebServer.js
  11. 12
      docker-compose.yaml
  12. 11
      package.json
  13. 76
      trash/start.js
  14. 37
      trash/test1.js
  15. 2448
      trash/testData.js
  16. 46
      utils.js

4
.gitignore

@ -0,0 +1,4 @@
/node_modules/
chart.png
package-lock.json
image.svg

8
.idea/.gitignore

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/jsLibraryMappings.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

6
.idea/misc.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/ChartJsSSR.iml" filepath="$PROJECT_DIR$/ChartJsSSR.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

8
ChartJsSSR.iml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
Dockerfile

@ -0,0 +1,8 @@
FROM node:18.18-bullseye
WORKDIR /app
USER node
COPY ./ /app
RUN npm install --reg https://nexus.pblr-nyk.pro/repository/npm/
EXPOSE 3000
CMD ["nodejs", "chartWebServer.js"]

143
chartGenerator.js

@ -0,0 +1,143 @@
const { createCanvas, registerFont } = require('canvas');
const Chart = require('chart.js/auto');
const {testData} = require("./trash/testData");
const {formatSeconds} = require("./utils");
const moment = require('moment');
const fs = require("fs");
function prepareData(data) {
const result = [];
const grabFirstValue = (container) => {
if (!container) return 0;
for (const mapName of Object.keys(container)) {
return container[mapName];
}
return 0;
}
const grabMapName = (container) => {
if (!container) return "";
for (const mapName of Object.keys(container)) {
return mapName;
}
return "";
}
//console.log(data)
for (const srvId of Object.keys(data.lastplay)) {
const obj = {}
obj["srv_id"] = srvId;
obj["server_name"] = data.servers[srvId].name;
obj["lastplay"] = grabFirstValue(data.lastplay[srvId])
obj["lastplay_moment"] = moment.unix(obj["lastplay"]).format("DD-MM-YYYY HH:mm")
obj["gametime"] = grabFirstValue(data.gametime[srvId])
obj["map_name"] = grabMapName(data.lastplay[srvId])
result.push(obj);
}
return result;
}
function generateChartData(d) {
const maxGametime = d.sort((a, b) => a.gametime - b.gametime).at(d.length - 1)["gametime"]
console.log(maxGametime)
let gametimeDelimiter = 1;
for (const delimiter of [60, 3600, 3600 * 24]) {
if (maxGametime > delimiter)
gametimeDelimiter = delimiter;
}
console.log(gametimeDelimiter)
const chartData = {
labels: [],
datasets: []
}
const data = []
d.sort((a, b) => b.gametime - a.gametime).forEach(
(val) => {
chartData.labels.push(`${val['server_name']}\n${val['lastplay_moment']}\nНаиграно: ${formatSeconds(val['gametime'])}`)
data.push(val['gametime'] / gametimeDelimiter)
}
)
chartData.datasets.push({
label: "Наиграно времени",
data: data,
})
const chartConfig = {
type: 'radar',
data: chartData,
options: {
responsive: false,
maintainAspectRatio: false,
borderColor: '#bd3b3b',
//backgroundColor: '#FFFFFF',
plugins: {
legend: {
position: 'top',
display: false
},
title: {
display: false,
text: 'Chart.js Polar Area Chart With Centered Point Labels'
}
},
scales: {
r: {
grid: {
color: '#ef9849'
},
angleLines: {
color: '#f08149'
},
pointLabels: {
display: true,
//centerPointLabels: true,
padding: 44,
font: {
size: 16
},
backdropColor: "#395c78",
backdropPadding: "1",
color: "#f5e7de"
},
}
}
},
};
return chartConfig;
}
function createChartOnData(testData) {
// Create a canvas and chart
const width = 800;
const height = 800;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
const d = prepareData(testData);
const chartConfig = generateChartData(d);
new Chart(ctx, chartConfig);
return canvas.createPNGStream();
}
module.exports = {createChartOnData}
function test() {
const stream = createChartOnData(testData);
// Save the chart image as a file
const fs = require('fs');
const out = fs.createWriteStream('chart.png');
stream.pipe(out);
out.on('finish', () => console.log('The chart image was saved.'));
}

38
chartWebServer.js

@ -0,0 +1,38 @@
const express = require('express');
const {createChartOnData} = require("./chartGenerator");
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware для парсинга JSON тела запроса
app.use(express.json({ limit: '10mb' }));
//ai sloooop moment
app.post('/api/chart/:id.png', async (req, res) => {
const { id } = req.params;
const payload = req.body;
// Проверяем, что тело запроса не пустое
if (!payload || (typeof payload === 'object' && Object.keys(payload).length === 0)) {
return res.status(400).send('Bad Request: request body is required');
}
try {
// Получаем PNG поток из пользовательской функции
const pngStream = createChartOnData(payload, id);
// Устанавливаем заголовок ответа
res.setHeader('Content-Type', 'image/png');
// Отправляем поток в ответ
pngStream.pipe(res);
} catch (err) {
console.error('Error generating PNG:', err);
res.status(500).send('Internal Server Error: could not generate image');
}
});
// Запуск сервера
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
console.log(`Endpoint: POST /api/chart/:id.png`);
});

12
docker-compose.yaml

@ -0,0 +1,12 @@
services:
chartjs_ssr:
build: ./
container_name: chartjs_ssr
restart: always
ports:
- 3000:3000
deploy:
resources:
limits:
cpus: 1.0
memory: 512M

11
package.json

@ -0,0 +1,11 @@
{
"name": "ChartJsSSR",
"version": "1.0.0",
"dependencies": {
"chart.js": "^4.4.8",
"chartjs-node-canvas": "^5.0.0",
"express": "^5.2.1",
"moment": "^2.30.1",
"sharp": "^0.34.5"
}
}

76
trash/start.js

@ -0,0 +1,76 @@
let { Buffer } = require("buffer");
const {ChartJSNodeCanvas} = require("chartjs-node-canvas");
const dependencies = {
chartJS: require("chartjs-node-canvas")
};
const constants = {
TYPES: ['PNG', 'SVG'],
smalll: {
height: 400,
width: 400
},
medium: {
height: 500,
width: 500
}
};
(async () => {
console.log("Creating Chart Object");
const { CanvasRenderService } = dependencies.chartJS;
const { height, width } = constants.medium;
const renderService = new ChartJSNodeCanvas({ type: 'svg', width: 800, height: 600 });
/*let renderService = new CanvasRenderService(width, height, (ChartJS) => {
ChartJS.defaults.global.animation = false;
ChartJS.defaults.global.responsive = false;
}, 'SVG'); //Tried SVG and svg*/
try {
let buffer = await renderService.renderToBufferSync({
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
/*scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}*/
}
}, 'image/svg+xml');
//When I run this I dont get anthing below this to print out
console.log("Finished");
console.log();
const fs = require('node:fs');
fs.writeFileSync('image.svg', buffer, 'utf8');
//let base64Str = Buffer.from(buffer).toString("base64");
//console.log(base64Str);
} catch (e) {
console.log(e);
}
})();

37
trash/test1.js

@ -0,0 +1,37 @@
const { createCanvas, registerFont } = require('canvas');
const Chart = require('chart.js/auto');
// Create a canvas and chart
const width = 800;
const height = 400;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
const chartConfig = {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [
{
label: 'My First Dataset',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: ['red', 'blue', 'yellow', 'green', 'purple', 'orange'],
},
],
},
options: {
responsive: false,
maintainAspectRatio: false,
},
};
new Chart(ctx, chartConfig);
// Save the chart image as a file
const fs = require('fs');
const out = fs.createWriteStream('chart.png');
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => console.log('The chart image was saved.'));

2448
trash/testData.js

File diff suppressed because it is too large

46
utils.js

@ -0,0 +1,46 @@
/**
* Преобразует секунды в формат "дни ЧЧ:ММ:СС" (если дни > 0)
* или просто "ЧЧ:ММ:СС" (если дней нет).
* @param {number} totalSeconds - количество секунд (целое неотрицательное число)
* @returns {string} отформатированная строка
*/
function formatSeconds(totalSeconds) {
if (totalSeconds < 0) return "0 00:00:00";
const days = Math.floor(totalSeconds / 86400);
let remainder = totalSeconds % 86400;
const hours = Math.floor(remainder / 3600);
remainder %= 3600;
const minutes = Math.floor(remainder / 60);
const seconds = remainder % 60;
// Форматируем часы, минуты, секунды с ведущими нулями
const timePart = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
if (days === 0) {
return timePart;
}
// Склонение слова "день"
let dayWord;
const lastDigit = days % 10;
const lastTwoDigits = days % 100;
if (lastTwoDigits >= 11 && lastTwoDigits <= 14) {
dayWord = "дней";
} else {
switch (lastDigit) {
case 1: dayWord = "день"; break;
case 2:
case 3:
case 4: dayWord = "дня"; break;
default: dayWord = "дней";
}
}
return `${days} ${dayWord} ${timePart}`;
}
module.exports = {formatSeconds}
Loading…
Cancel
Save