This was the first version of Convoy, before it grew into a Python/notebook codegen tool: a canvas-based node graph for building data pipelines, where dragging in sources, filters, and transforms produces a chart you can push straight to Datawrapper instead of a static export. The pipeline runs entirely as an in-memory DataFrame, and a chart node's job is just to describe an axis mapping and a chart type against whatever the graph produces upstream.
The Datawrapper handoff needed two conversions: the internal chart type had to map onto Datawrapper's own vocabulary (bar → d3-bars, scatter → d3-scatter-plot, and so on), and the DataFrame itself had to become a CSV payload for their upload API. The CSV serialization is the fiddlier of the two, since it has to quote any cell that would otherwise break the format:
export function dataFrameToCSV(data: DataFrame): string {
const columnNames = data.columns.map((c) => c.name);
const escapeCell = (value: unknown): string => {
if (value === null || value === undefined) return '';
const str = String(value);
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
};
const header = columnNames.map(escapeCell).join(',');
const rows = data.rows.map((row) =>
columnNames.map((col) => escapeCell(row[col])).join(',')
);
return [header, ...rows].join('\n');
}