Skip to content

Commit

Permalink
Add support for PNG graphviz and custom size. #57
Browse files Browse the repository at this point in the history
  • Loading branch information
typpo committed May 26, 2020
1 parent 40dc5bd commit 8d83d16
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 17 deletions.
16 changes: 13 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const { getPdfBufferFromPng, getPdfBufferWithText } = require('./lib/pdf');
const { logger } = require('./logging');
const { renderChart } = require('./lib/charts');
const { renderGraphviz } = require('./lib/graphviz');
const { toChartJs } = require('./lib/google_image_charts');
const { toChartJs, parseSize } = require('./lib/google_image_charts');
const { renderQr, DEFAULT_QR_SIZE } = require('./lib/qr');

const app = express();
Expand Down Expand Up @@ -223,10 +223,19 @@ function doRender(req, res, opts) {
async function handleGChart(req, res) {
if (req.query.cht.startsWith('gv')) {
// Graphviz chart
const format = 'svg'; // Hardcode to svg for now
const format = req.query.chof;
const engine = req.query.cht.indexOf(':') > -1 ? req.query.cht.split(':')[1] : 'dot';
const opts = {
format,
engine,
};
if (req.query.chs) {
const size = parseSize(req.query.chs);
opts.width = size.width;
opts.height = size.height;
}
try {
const buf = await renderGraphviz(req.query.chl, format, engine);
const buf = await renderGraphviz(req.query.chl, opts);
res
.status(200)
.type(format === 'png' ? 'image/png' : 'image/svg+xml')
Expand All @@ -238,6 +247,7 @@ async function handleGChart(req, res) {
failSvg(res, `Graph Error: ${err}`);
}
}
return;
}
const converted = toChartJs(req.query);
if (req.query.format === 'chartjs-config') {
Expand Down
5 changes: 3 additions & 2 deletions lib/google_image_charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ function parseSize(chs) {
}
const size = chs.split('x');
return {
width: parseInt(size[0], 10),
height: parseInt(size[1], 10),
width: Math.min(2048, parseInt(size[0], 10)),
height: Math.min(2048, parseInt(size[1], 10)),
};
}

Expand Down Expand Up @@ -941,4 +941,5 @@ function toChartJs(query) {
module.exports = {
toChartJs,
parseSeriesData,
parseSize,
};
19 changes: 17 additions & 2 deletions lib/graphviz.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
const sharp = require('sharp');
const Viz = require('viz.js');
const { Module, render } = require('viz.js/full.render.js');

async function renderGraphviz(graphStr, format = 'svg', engine = 'dot') {
async function renderGraphviz(graphStr, opts) {
const { format, engine, width, height } = opts || {};
const viz = new Viz({ Module, render });
const result = await viz.renderString(graphStr, {
format,
// Built-in format options don't work great. Hardcode to svg and convert it
// to other supported formats later.
format: 'svg',
engine,
});
if (format === 'png') {
const img = sharp(Buffer.from(result));
if (width && height) {
img.resize({
width,
height,
fit: 'contain',
});
}
return img.png().toBuffer();
}
return result;
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"qs": "^6.7.0",
"rate-limit-redis": "^1.7.0",
"request": "^2.88.0",
"sharp": "^0.25.3",
"text2png": "^2.1.0",
"viz.js": "^2.1.2",
"vm2": "^3.8.4"
Expand Down
39 changes: 37 additions & 2 deletions test/ci/graphviz.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,57 @@
/* eslint-env node, mocha */

const assert = require('assert');
const imageSize = require('image-size');

const { renderGraphviz } = require('../../lib/graphviz');

describe('graphviz rendering', () => {
it('renders a basic graph svg', async () => {
const graphStr =
'digraph{C_0[shape=box];C_0->H_0[type=s];C_0->H_1[type=s];C_0->H_2[type=s];C_0->C_1[type=s];C_1->H_3[type=s];C_1->H_4[type=s];C_1->H_5[color=blue]}';
const buf = await renderGraphviz(graphStr, 'svg', 'dot');
const buf = await renderGraphviz(graphStr);
assert(buf.length > 300);
assert(buf.indexOf('C_1') > -1);
});

it('renders with a different engine', async () => {
const graphStr =
'digraph{C_0[shape=box];C_0->H_0[type=s];C_0->H_1[type=s];C_0->H_2[type=s];C_0->C_1[type=s];C_1->H_3[type=s];C_1->H_4[type=s];C_1->H_5[color=blue]}';
const buf = await renderGraphviz(graphStr, {
engine: 'neato',
});
assert(buf.length > 300);
assert(
buf.indexOf('202.0925,-67.4481 207.4376,-58.3004 197.5546,-62.1183 202.0925,-67.4481') > -1,
);
});

it('handles a malformed graph', async () => {
const graphStr = 'digraoobar}';
assert.rejects(async () => {
await renderGraphviz(graphStr, 'svg', 'dot');
await renderGraphviz(graphStr);
});
});

it('renders to png', async () => {
const graphStr =
'digraph{C_0[shape=box];C_0->H_0[type=s];C_0->H_1[type=s];C_0->H_2[type=s];C_0->C_1[type=s];C_1->H_3[type=s];C_1->H_4[type=s];C_1->H_5[color=blue]}';
const buf = await renderGraphviz(graphStr, {
format: 'png',
});
assert(buf.length > 3000);
});

it('renders to png with size', async () => {
const graphStr =
'digraph{C_0[shape=box];C_0->H_0[type=s];C_0->H_1[type=s];C_0->H_2[type=s];C_0->C_1[type=s];C_1->H_3[type=s];C_1->H_4[type=s];C_1->H_5[color=blue]}';
const buf = await renderGraphviz(graphStr, {
format: 'png',
width: 800,
height: 400,
});
const dimensions = imageSize(buf);
assert.equal(800, dimensions.width);
assert.equal(400, dimensions.height);
});
});
Loading

0 comments on commit 8d83d16

Please sign in to comment.