diff --git a/g2-ssr/.gitignore b/g2-ssr/.gitignore new file mode 100644 index 00000000..5aa0b5f8 --- /dev/null +++ b/g2-ssr/.gitignore @@ -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 diff --git a/g2-ssr/Arial_Unicode.ttf b/g2-ssr/Arial_Unicode.ttf new file mode 100644 index 00000000..1537c5b8 Binary files /dev/null and b/g2-ssr/Arial_Unicode.ttf differ diff --git a/g2-ssr/app.js b/g2-ssr/app.js new file mode 100644 index 00000000..ba646b5d --- /dev/null +++ b/g2-ssr/app.js @@ -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'); + }); +} \ No newline at end of file diff --git a/g2-ssr/charts/bar.js b/g2-ssr/charts/bar.js new file mode 100644 index 00000000..12df3d99 --- /dev/null +++ b/g2-ssr/charts/bar.js @@ -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} \ No newline at end of file diff --git a/g2-ssr/charts/column.js b/g2-ssr/charts/column.js new file mode 100644 index 00000000..124f2b9a --- /dev/null +++ b/g2-ssr/charts/column.js @@ -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} \ No newline at end of file diff --git a/g2-ssr/charts/line.js b/g2-ssr/charts/line.js new file mode 100644 index 00000000..72931edf --- /dev/null +++ b/g2-ssr/charts/line.js @@ -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} diff --git a/g2-ssr/charts/pie.js b/g2-ssr/charts/pie.js new file mode 100644 index 00000000..313bcf70 --- /dev/null +++ b/g2-ssr/charts/pie.js @@ -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} diff --git a/g2-ssr/charts/utils.js b/g2-ssr/charts/utils.js new file mode 100644 index 00000000..f643452f --- /dev/null +++ b/g2-ssr/charts/utils.js @@ -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} \ No newline at end of file diff --git a/g2-ssr/package.json b/g2-ssr/package.json new file mode 100644 index 00000000..a4116796 --- /dev/null +++ b/g2-ssr/package.json @@ -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" + } +}