Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added __tests__/fixtures/template-charts.pptx
Binary file not shown.
170 changes: 170 additions & 0 deletions __tests__/template-chart.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
const PPTX = require('../index.js');
const fs = require('fs');
const tmpDir = `${__dirname}/tmp`;
const tmpFile = 'charts-new-add-chart-apply-template.pptx'
const categories1 = ['Category 1', 'Category 2', 'Category 3', 'Category 4']
const barChartData1 = [
{
name: 'Series 1',
labels: categories1,
values: [6.3, 4.5, 2.5, 4.5],
},
{
name: 'Series 2',
labels: categories1,
values: [3.4, 1.4, 1.8, 2.8],
},
{
name: 'Series 3',
labels: categories1,
values: [2.0, 2.0, 1.0, 55.5],
},
];

const pieChartData = [
{
name: 'Series 1',
labels: categories1,
values: [20, 30, 40, 10],
}
];


const pptxTemplateFile = `${__dirname}/fixtures/template-charts.pptx`
describe('Charts Module', () => {
beforeAll(() => {
prepareTmpDir(tmpDir);
});

test('should be able to create a simple chart and apply a bar chart template', async () => {
try {
expect.assertions(1);

let pptx = new PPTX.Composer();
let promise = (await pptx.compose(async pres => {
await pres.layout('LAYOUT_4x3').addSlide(async slide => {
await slide.addChart(chart => {
chart
.template({
pptxFile: pptxTemplateFile,
xmlFile: 'ppt/charts/chart1.xml'
})
.data(barChartData1)
.x(100)
.y(100)
.cx(400)
.cy(300);
});
});

})).save(`${tmpDir}/${tmpFile}`);

await promise;

expect(fs.existsSync(`${tmpDir}/${tmpFile}`)).toBe(true);
} catch (err) {
console.warn(err);
throw err;
}
});

test('should be able to create a chart and apply a pie chart template', async () => {
try {
expect.assertions(1);

let pptx = new PPTX.Composer();
let promise = (await pptx.compose(async pres => {
await pres.layout('LAYOUT_4x3').addSlide(async slide => {
await slide.addChart(chart => {
chart
.template({
pptxFile: pptxTemplateFile,
xmlFile: 'ppt/charts/chart2.xml'
})
.data(pieChartData)
.x(100)
.y(100)
.cx(400)
.cy(300);
});
});

})).save(`${tmpDir}/${tmpFile}`);

await promise;

expect(fs.existsSync(`${tmpDir}/${tmpFile}`)).toBe(true);
} catch (err) {
console.warn(err);
throw err;
}
});

test('should be able to create a chart and apply a combo chart template', async () => {
try {
expect.assertions(1);

let pptx = new PPTX.Composer();
let promise = (await pptx.compose(async pres => {
await pres.layout('LAYOUT_4x3').addSlide(async slide => {
await slide.addChart(chart => {
chart
.template({
pptxFile: pptxTemplateFile,
xmlFile: 'ppt/charts/chart3.xml',
callback: (template, { newChartSpaceBlock, TemplateHelper, chart }) => {
// First we replace chartSpace by our template
TemplateHelper.applyChartSpace(template, newChartSpaceBlock)

// We need to extract the different series templates using their chart type names
let seriesTemplateBar = TemplateHelper.getSeriesTemplate(template, 'barChart')
let seriesTemplateLine = TemplateHelper.getSeriesTemplate(template, 'lineChart')

// Combined, the templates will be passed to the creator
seriesTemplate = [
seriesTemplateBar[0], seriesTemplateBar[1], seriesTemplateLine[0]
]

// The series have to be calculated based on the passed series templates
let series = TemplateHelper.createSeriesFromTemplate(chart.chartData, seriesTemplate)

// We will override the template series with our created ones
TemplateHelper.applySeriesToChart([series[0], series[1]], newChartSpaceBlock, 'barChart')
TemplateHelper.applySeriesToChart([series[2]], newChartSpaceBlock, 'lineChart')
}
})
.data(barChartData1)
.x(100)
.y(100)
.cx(400)
.cy(300)
});
});

})).save(`${tmpDir}/${tmpFile}`);

await promise;

expect(fs.existsSync(`${tmpDir}/${tmpFile}`)).toBe(true);
} catch (err) {
console.warn(err);
throw err;
}
});
});

function prepareTmpDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
} else {
emptyDir(dir);
}
}

function emptyDir(dir) {
if (fs.existsSync(`${__dirname}/tmp/${tmpFile}`)) {
fs.unlink(`${__dirname}/tmp/${tmpFile}`, err => {
if (err) throw err;
});
}
}
6 changes: 6 additions & 0 deletions lib/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class Chart extends ElementProperties {
this.content = content;
super.setPropertyContent(this.content['p:graphicFrame'][0]['p:xfrm'][0]);
}

template(templateParams) {
this.templateParams = templateParams;

return this;
}
}

module.exports.Chart = Chart;
2 changes: 1 addition & 1 deletion lib/factories/ppt/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class PptFactory {
}

async addChart(slide, chart) {
this.slideFactory.addChartToSlideRelationship(slide, chart.name);
this.slideFactory.addChartToSlideRelationship(slide, chart);

let workbookJSZip = await ExcelHelper.createWorkbook(chart.chartData);
let workbookContentZipBinary = await workbookJSZip.generateAsync({ type: 'arraybuffer' });
Expand Down
30 changes: 21 additions & 9 deletions lib/factories/ppt/slides.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

let { PptFactoryHelper } = require('../../helpers/ppt-factory-helper');
let { PptxContentHelper } = require('../../helpers/pptx-content-helper');
let { TemplateHelper } = require('../../helpers/template-helper');

class SlideFactory {
constructor(parentFactory, args) {
Expand Down Expand Up @@ -43,7 +44,6 @@ class SlideFactory {
};

slideContent = JSON.parse(JSON.stringify(slideContent));

this.content[slideKey] = slideContent;

return slideContent;
Expand Down Expand Up @@ -128,7 +128,8 @@ class SlideFactory {
return rId;
}

addChartToSlideRelationship(slide, chartName) {
addChartToSlideRelationship(slide, chart) {
let chartName = chart.name
let relsKey = `ppt/slides/_rels/${slide.name}.xml.rels`;
let rId = `rId${this.content[relsKey]['Relationships']['Relationship'].length + 1}`;

Expand All @@ -140,7 +141,7 @@ class SlideFactory {
},
});

return rId;
chart.rId = rId
}

addImage(slide, image, imageObjectName, rId) {
Expand Down Expand Up @@ -306,19 +307,30 @@ class SlideFactory {
return newShapeBlock;
}

addChart(slide, chart) {
async addChart(slide, chart) {
let slideKey = `ppt/slides/${slide.name}.xml`;
let chartKey = `ppt/charts/${chart.name}.xml`;

let newGraphicFrameBlock = PptFactoryHelper.createBaseChartFrameBlock(chart.x(), chart.y(), chart.cx(), chart.cy()); // goes onto the slide
let newGraphicFrameBlock = PptFactoryHelper.createBaseChartFrameBlock(chart); // goes onto the slide
let newChartSpaceBlock = PptFactoryHelper.createBaseChartSpaceBlock(); // goes into the chart XML
let seriesDataBlock = PptFactoryHelper.createSeriesDataBlock(chart.chartData);

newChartSpaceBlock['c:chartSpace']['c:chart'][0]['c:plotArea'][0]['c:barChart'][0]['c:ser'] = seriesDataBlock['c:ser'];

if(chart.hasOwnProperty('templateParams')) {
// If templateParams are set, a chart template will be loaded and applied to newChartSpaceBlock.
await TemplateHelper.applyTemplateXml(chart.templateParams, { newChartSpaceBlock, chart })
} else {
let seriesDataBlock = PptFactoryHelper.createSeriesDataBlock(chart.chartData);
newChartSpaceBlock['c:chartSpace']['c:chart'][0]['c:plotArea'][0]['c:barChart'][0]['c:ser'] = seriesDataBlock['c:ser'];
}

this.content[chartKey] = newChartSpaceBlock;
this.content[slideKey]['p:sld']['p:cSld'][0]['p:spTree'][0]['p:graphicFrame'] = newGraphicFrameBlock['p:graphicFrame'];

let tree = this.content[slideKey]['p:sld']['p:cSld'][0]['p:spTree'][0]
if(!tree.hasOwnProperty('p:graphicFrame')) {
tree['p:graphicFrame'] = new Array
}

tree['p:graphicFrame'].push(newGraphicFrameBlock['p:graphicFrame'][0])

return newGraphicFrameBlock;
}

Expand Down
26 changes: 19 additions & 7 deletions lib/helpers/ppt-factory-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const XmlNode = require('../xmlnode');

let { ExcelHelper } = require('./excel-helper');
let { PptxUnitHelper } = require('./unit-helper');
let { TemplateHelper } = require('./template-helper');

const HyperlinkType = {
TEXT: 'text',
Expand Down Expand Up @@ -85,7 +86,7 @@ class PptFactoryHelper {
// TODO: this block is taken straight from won21 (except I had to change some objects to an array of objects to support our existing
// block structure); I don't like the defaults it's using and there are some slight differences from an actual PowerPoint-generated
// p:graphicFrame block. Once basic charts are done, revisit this and see if this block can be made better.
static createBaseChartFrameBlock(x, y, cx, cy) {
static createBaseChartFrameBlock(chart) {
return {
'p:graphicFrame': [
{
Expand Down Expand Up @@ -118,8 +119,8 @@ class PptFactoryHelper {
],
'p:xfrm': [
{
'a:off': [{ $: { x: x, y: y } }],
'a:ext': [{ $: { cx: cx, cy: cy } }],
'a:off': [{ $: { x: chart.x(), y: chart.y() } }],
'a:ext': [{ $: { cx: chart.cx(), cy: chart.cy() } }],
},
],
'a:graphic': [
Expand All @@ -134,7 +135,7 @@ class PptFactoryHelper {
$: {
'xmlns:c': 'http://schemas.openxmlformats.org/drawingml/2006/chart',
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
'r:id': 'rId2',
'r:id': chart.rId,
},
},
],
Expand Down Expand Up @@ -625,6 +626,7 @@ class PptFactoryHelper {
}

// this will return all the child nodes that belong under a <c:ser> node (it will NOT contain the <c:ser> root)
// Passing an array in this.template will return series created by TemplateHelper.
static createSingleSeriesDataNode(series, i) {
let rc2a = ExcelHelper.rowColToSheetAddress;
let strRef = PptFactoryHelper.createStrRefNode;
Expand All @@ -634,13 +636,23 @@ class PptFactoryHelper {
let sheetCellRangeForCategories = `Sheet1!${rc2a(2, 1, true, true)}:${rc2a(2 + series.labels.length - 1, 1, true, true)}`;
let sheetCellAddressForSeriesName = `Sheet1!${rc2a(1, 2 + i, true, true)}`;

let tx = strRef(sheetCellAddressForSeriesName, [series.name])
let cat = strRef(sheetCellRangeForCategories, series.labels)
let val = numRef(sheetCellRangeForValues, series.values, 'General')

if(typeof this.template !== 'undefined' && typeof this.template[i] !== 'undefined') {
let series = TemplateHelper.setTemplateSeriesDefault(this.template[i])
TemplateHelper.setTemplateSeriesData(series, {i, tx, cat, val})
return series
}

let serChildNodes = XmlNode()
.addChild('c:idx', XmlNode().attr('val', i))
.addChild('c:order', XmlNode().attr('val', i))
.addChild('c:tx', strRef(sheetCellAddressForSeriesName, [series.name]))
.addChild('c:invertIfNegative', XmlNode().attr('val', 0))
.addChild('c:cat', strRef(sheetCellRangeForCategories, series.labels))
.addChild('c:val', numRef(sheetCellRangeForValues, series.values, 'General'));
.addChild('c:tx', tx)
.addChild('c:cat', cat)
.addChild('c:val', val);

if (series.color) {
let colorBlock = PptFactoryHelper.createColorBlock(series.color);
Expand Down
Loading