feat: add g2-ssr

This commit is contained in:
xuwei-fit2cloud
2025-07-29 17:43:38 +08:00
parent a1cd1e6ad5
commit 025179cf5e
9 changed files with 524 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
components.d.ts
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json
Binary file not shown.
+81
View File
@@ -0,0 +1,81 @@
const http = require('http');
const url = require("url");
const util = require('util');
const { createChart } = require('@antv/g2-ssr');
const port = 3000;
const { getPieOptions } = require('./charts/pie.js');
const { getLineOptions } = require('./charts/line.js');
const { getColumnOptions } = require('./charts/column.js');
const { getBarOptions } = require('./charts/bar.js');
http.createServer((req, res) => {
res.statusCode = 200,
res.setHeader('Content-Type', 'text/plain;charset=utf-8');
if (req.method === 'GET') {
toGet(req, res);
} else if (req.method === 'POST') {
toPost(req, res);
}
}).listen(port, () => {
console.log(`Server listening on: http://localhost:${port}`);
});
function getOptions(type, axis, data) {
const base_options = {
width: 640,
height: 480,
imageType: 'png',
theme: {
view: {
viewFill: '#FFFFFF',
},
}
}
switch (type) {
case 'bar':
return getBarOptions(base_options, axis, data);
case 'column':
return getColumnOptions(base_options, axis, data);
case 'line':
return getLineOptions(base_options, axis, data);
case 'pie':
return getPieOptions(base_options, axis, data);
}
return base_options
}
// 创建 Chart 和配置
async function GenerateCharts(obj) {
console.log(obj)
const options = getOptions(obj.type, JSON.parse(obj.axis), JSON.parse(obj.data));
console.log(options);
const chart = await createChart(options);
// 导出
chart.exportToFile(obj.path || 'chart');
// -> chart.png
chart.toBuffer();
}
// -> get buffer
//获取GET请求内容
function toGet(req, res) {
let data = 'GET请求内容:\n' + util.inspect(url.parse(req.url));
res.end(data);
console.log(data);
}
//获取POST请求内容、cookie
function toPost(req, res) {
req.on('data', async function (chunk) {
await GenerateCharts(JSON.parse(chunk))
res.end('complete');
});
}
+112
View File
@@ -0,0 +1,112 @@
const {checkIsPercent} = require("./utils");
function getBarOptions(baseOptions, axis, data) {
const x = axis.filter((item) => item.type === 'x')
const y = axis.filter((item) => item.type === 'y')
const series = axis.filter((item) => item.type === 'series')
if (x.length === 0 || y.length === 0) {
return
}
const _data = checkIsPercent(y[0], data)
const options = {
...baseOptions,
type: 'interval',
data: _data.data,
coordinate: {transform: [{type: 'transpose'}]},
encode: {
x: x[0].value,
y: y[0].value,
color: series.length > 0 ? series[0].value : undefined,
},
style: {
radiusTopLeft: (d) => {
if (d[y[0].value] && d[y[0].value] > 0) {
return 4
}
return 0
},
radiusTopRight: (d) => {
if (d[y[0].value] && d[y[0].value] > 0) {
return 4
}
return 0
},
radiusBottomLeft: (d) => {
if (d[y[0].value] && d[y[0].value] < 0) {
return 4
}
return 0
},
radiusBottomRight: (d) => {
if (d[y[0].value] && d[y[0].value] < 0) {
return 4
}
return 0
},
},
axis: {
x: {
title: x[0].name,
labelFontSize: 12,
labelAutoHide: {
type: 'hide',
keepHeader: true,
keepTail: true,
},
labelAutoRotate: false,
labelAutoWrap: true,
labelAutoEllipsis: true,
},
y: {title: y[0].name},
},
scale: {
x: {
nice: true,
},
y: {
nice: true,
},
},
interaction: {
elementHighlight: {background: true},
},
tooltip: (data) => {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
}
} else {
return {name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`}
}
},
labels: [
{
text: (data) => {
const value = data[y[0].value]
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
},
transform: [
{type: 'contrastReverse'},
{type: 'exceedAdjust'},
{type: 'overlapHide'},
],
},
],
}
if (series.length > 0) {
options.transform = [{type: 'stackY'}]
}
return options
}
module.exports = {getBarOptions}
+113
View File
@@ -0,0 +1,113 @@
const {checkIsPercent} = require("./utils");
function getColumnOptions(baseOptions, axis, data) {
const x = axis.filter((item) => item.type === 'x')
const y = axis.filter((item) => item.type === 'y')
const series = axis.filter((item) => item.type === 'series')
if (x.length === 0 || y.length === 0) {
return
}
const _data = checkIsPercent(y[0], data)
const options = {
...baseOptions,
type: 'interval',
data: _data.data,
encode: {
x: x[0].value,
y: y[0].value,
color: series.length > 0 ? series[0].value : undefined,
},
style: {
radiusTopLeft: (d) => {
if (d[y[0].value] && d[y[0].value] > 0) {
return 4
}
return 0
},
radiusTopRight: (d) => {
if (d[y[0].value] && d[y[0].value] > 0) {
return 4
}
return 0
},
radiusBottomLeft: (d) => {
if (d[y[0].value] && d[y[0].value] < 0) {
return 4
}
return 0
},
radiusBottomRight: (d) => {
if (d[y[0].value] && d[y[0].value] < 0) {
return 4
}
return 0
},
},
axis: {
x: {
title: x[0].name,
labelFontSize: 12,
labelAutoHide: {
type: 'hide',
keepHeader: true,
keepTail: true,
},
labelAutoRotate: false,
labelAutoWrap: true,
labelAutoEllipsis: true,
},
y: {title: y[0].name},
},
scale: {
x: {
nice: true,
},
y: {
nice: true,
},
},
interaction: {
elementHighlight: {background: true},
},
tooltip: (data) => {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
}
} else {
return {name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`}
}
},
labels: [
{
text: (data) => {
const value = data[y[0].value]
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
},
position: 'top',
dy: -25,
transform: [
{type: 'contrastReverse'},
{type: 'exceedAdjust'},
{type: 'overlapHide'},
],
},
],
}
if (series.length > 0) {
options.transform = [{type: 'stackY'}]
}
return options
}
module.exports = {getColumnOptions}
+97
View File
@@ -0,0 +1,97 @@
const {checkIsPercent} = require("./utils");
function getLineOptions(baseOptions, axis, data) {
const x = axis.filter((item) => item.type === 'x')
const y = axis.filter((item) => item.type === 'y')
const series = axis.filter((item) => item.type === 'series')
if (x.length === 0 || y.length === 0) {
return
}
const _data = checkIsPercent(y[0], data)
const options = {
...baseOptions,
type: 'view',
data: _data.data,
encode: {
x: x[0].value,
y: y[0].value,
color: series.length > 0 ? series[0].value : undefined,
},
axis: {
x: {
title: x[0].name,
labelFontSize: 12,
labelAutoHide: {
type: 'hide',
keepHeader: true,
keepTail: true,
},
labelAutoRotate: false,
labelAutoWrap: true,
labelAutoEllipsis: true,
},
y: {title: y[0].name},
},
scale: {
x: {
nice: true,
},
y: {
nice: true,
},
},
children: [
{
type: 'line',
encode: {
shape: 'smooth',
},
labels: [
{
text: (data) => {
const value = data[y[0].value]
if (value === undefined || value === null) {
return ''
}
return `${value}${_data.isPercent ? '%' : ''}`
},
style: {
dx: -10,
dy: -12,
},
transform: [
{type: 'contrastReverse'},
{type: 'exceedAdjust'},
{type: 'overlapHide'},
],
},
],
tooltip: (data) => {
if (series.length > 0) {
return {
name: data[series[0].value],
value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
}
} else {
return {name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`}
}
},
},
{
type: 'point',
style: {
fill: 'white',
},
tooltip: false,
},
],
}
return options
}
module.exports = {getLineOptions}
+41
View File
@@ -0,0 +1,41 @@
const {checkIsPercent} = require("./utils");
function getPieOptions(baseOptions, axis, data) {
const y = axis.filter((item) => item.type === 'y')
const series = axis.filter((item) => item.type === 'series')
if (series.length === 0 || y.length === 0) {
return
}
const _data = checkIsPercent(y[0], data)
return {
...baseOptions,
type: 'interval',
coordinate: {type: 'theta', outerRadius: 0.8},
transform: [{type: 'stackY'}],
data: _data.data,
encode: {
y: y[0].value,
color: series[0].value,
},
legend: {
color: {position: 'bottom', layout: {justifyContent: 'center'}},
},
labels: [
{
position: 'outside',
text: (data) =>
`${data[series[0].value]}: ${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
},
],
tooltip: (data) => {
return {name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`}
},
}
}
module.exports = {getPieOptions}
+37
View File
@@ -0,0 +1,37 @@
const {filter, endsWith, replace} = require("lodash-es");
function checkIsPercent(valueAxis, data) {
const result = {
isPercent: false,
data: [],
}
const notEmptyData = filter(
data,
(d) =>
d &&
d[valueAxis.value] !== null &&
d[valueAxis.value] !== undefined &&
d[valueAxis.value] !== 0 &&
d[valueAxis.value] !== '0'
)
if (notEmptyData.length > 0) {
const v = notEmptyData[0][valueAxis.value] + ''
if (endsWith(v.trim(), '%')) {
result.isPercent = true
}
}
for (let i = 0; i < data.length; i++) {
const v = data[i]
const _v = {...v}
if (result.isPercent) {
const formatValue = replace(v[valueAxis.value], '%', '')
_v[valueAxis.value] = Number(formatValue)
}
result.data.push(_v)
}
return result
}
module.exports = {checkIsPercent}
+18
View File
@@ -0,0 +1,18 @@
{
"name": "vite-project",
"private": true,
"version": "0.0.0",
"dependencies": {
"@antv/g2": "^5.3.3",
"@antv/g2-ssr": "^0.1.0",
"@rollup/plugin-json": "^6.1.0",
"lodash-es": "^4.17.21",
"nan": "^2.22.2",
"node-canvas": "^2.9.0",
"nprogress": "^0.2.0"
},
"devDependencies": {
"@types/node": "^18.6.3",
"@types/nprogress": "^0.2.0"
}
}