commit
a68fed7012
16 changed files with 2865 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||
/node_modules/ |
|||
chart.png |
|||
package-lock.json |
|||
image.svg |
|||
@ -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 |
|||
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="JavaScriptLibraryMappings"> |
|||
<includedPredefinedLibrary name="Node.js Core" /> |
|||
</component> |
|||
</project> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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"] |
|||
@ -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.')); |
|||
} |
|||
|
|||
|
|||
@ -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`); |
|||
}); |
|||
@ -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 |
|||
@ -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" |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
})(); |
|||
@ -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.')); |
|||
File diff suppressed because it is too large
@ -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…
Reference in new issue