officegen-dbb
Version:
Modification of Ziv Barber's officegen, to provide a reasonable way to have line breaks in table cells.
537 lines (466 loc) • 16.2 kB
JavaScript
/// @author vtloc
/// @date 2014Jan09
/// @author GradualStudent
/// @date 2015jan06
/// This module's purpose is to transform
///
var _ = require('lodash'); // replacing underscore to get merge function
var xmlBuilder = require('xmlbuilder');
var fs = require('fs')
var _chartSpecs = require('./charts');
function OfficeChart(chartInfo) {
if (chartInfo instanceof OfficeChart) {
return chartInfo;
}
return {
chartSpec: null, // Javascript object that represents the XML tree for the PowerPoint chart
toXML: function () {
return xmlBuilder.create(this.chartSpec, {version: '1.0', encoding: 'UTF-8', standalone: true}).end({ pretty: true, indent: ' ', newline: '\n' });
},
toJSON: function () {
return this.chartSpec;
},
getClass: function() { return "OfficeChart"; },
///
/// @brief Create XML representation of chart object
/// @param[chartInfo] object
/// {
/// title: 'eSurvey chart',
/// data: [ // array of series
/// {
/// name: 'Income',
/// labels: ['2005', '2006', '2007', '2008', '2009'],
/// values: [23.5, 26.2, 30.1, 29.5, 24.6],
/// color: 'ff0000'
/// }
/// ],
/// overlap: "0",
/// gapWidth: "150"
///
/// }
initialize: function (chartInfo) {
if (chartInfo.getClass && chartInfo.getClass()== 'OfficeChart') {
return chartInfo;
}
// overlap ["50"] is handled as an option within the chartbase
// gapWidth ["150"] is handled as an option within the chartbase
// valAxisCrossAtMaxCategory [true|false] is handled as an option within the chart base
// catAxisReverseOrder [true|false] is handled as an option within the chart base
this.chartSpec = OfficeChart.getChartBase(chartInfo); // get foundation XML for the chart type
// Below are methods for handling options with more complex XML to mix in
this.setData(chartInfo['data']);
this.setTitle(chartInfo.title || chartInfo.name);
this.setValAxisTitle(chartInfo.valAxisTitle);
this.setCatAxisTitle(chartInfo.catAxisTitle);
this.setValAxisNumFmt(chartInfo.valAxisNumFmt);
this.setValAxisScale(chartInfo.valAxisMinValue, chartInfo.valAxisMaxValue);
this.setTextSize(chartInfo.fontSize);
this.mergeChartXml(chartInfo.xml);
this.setValAxisMajorGridlines(chartInfo.valAxisMajorGridlines);
this.setValAxisMinorGridlines(chartInfo.valAxisMinorGridlines);
return this;
},
setTextSize: function(textSize) {
if (textSize !== undefined) {
var textRef = this._text(textSize);
_.merge(this.chartSpec['c:chartSpace'], textRef);
}
},
setTitle: function (title) {
if (title !== undefined) {
var titleRef = this._title(chartInfo.title || chartInfo.name);
_.merge(this.chartSpec['c:chartSpace']['c:chart'], titleRef)
}
},
setValAxisTitle: function (title) {
if (title) {
var titleRef = this._title(title);
_.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx'], titleRef);
}
},
setCatAxisTitle: function (title) {
if (title) {
var titleRef = this._title(title);
_.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:catAx'], titleRef);
}
},
setValAxisNumFmt: function (format) {
if (format !== undefined) {
var numFmtRef = this._numFmt(format);
_.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx'], numFmtRef)
}
},
setValAxisScale: function (min, max) {
if (min!==undefined || max!== undefined) {
var scalingRef = this._scaling(min, max);
_.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx'], scalingRef);
}
},
mergeChartXml: function (xml) {
if (xml !== undefined) {
_.merge(this.chartSpec['c:chartSpace'], xml);
}
},
setValAxisMajorGridlines: function(boolean) {
if (boolean) {
this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx']['c:majorGridlines'] = {};
}
},
setValAxisMinorGridlines: function(boolean) {
if (boolean) {
this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx']['c:minorGridlines'] = {};
}
},
setData: function (data) {
if (data) {
this.data = data;
// Mix in data series
if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:lineChart']) {
seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:lineChart']['#list'];
} else
if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:areaChart']) {
seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:areaChart']['#list'];
} else
if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:barChart']) {
seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:barChart']['#list'];
}
else if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:doughnutChart']) {
seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:doughnutChart']['#list'];
}
else if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:lineChart']) {
seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:lineChart']['#list'];
}
else if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:pieChart']) {
seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:pieChart']['#list'];
}
else throw new Error("Can't add data to chart that is not initialized or not a recognized type");
// the barChart/pieChart objects has other attributes too, so we push the series data on, not replace it
if (chartInfo['data']) {
var seriesData = this.getSeriesRef(chartInfo['data']);
for (var i = 0; i < seriesData.length; i++) {
seriesDataRef.push(seriesData[i])
}
}
}
return this;
},
///
/// @brief Transform an array of string into an office's compliance structure
///
/// @param[in] region String
/// The reference cell of the string, for example: $A$1
/// @param[in] stringArr
/// An array of string, for example: ['foo', 'bar']
///
_strRef: function (region, stringArr) {
var obj = {
'c:strRef': {
'c:f': region,
'c:strCache': function () {
var result = {};
result['c:ptCount'] = { '@val': stringArr.length };
result['#list'] = [];
for (var i = 0; i < stringArr.length; i++) {
result['#list'].push({'c:pt': { '@idx': i, 'c:v': stringArr[i] }});
}
return result;
}
}
};
return obj;
},
///
/// @brief Transform an array of numbers into an office's compliance structure
///
/// @param[in] region String
/// The reference cell of the string, for example: $A$1
/// @param[in] numArr
/// An array of numArr, for example: [4, 7, 8]
/// @param[in] formatCode
/// A string describe the number's format. Example: General
///
_numRef: function (region, numArr, formatCode) {
var obj = {
'c:numRef': {
'c:f': region,
'c:numCache': {
'c:formatCode': formatCode,
'c:ptCount': {'@val': '' + numArr.length},
'#list': function () {
result = [];
for (var i = 0; i < numArr.length; i++) {
result.push({'c:pt': { '@idx': i, 'c:v': numArr[i].toString() }});
}
return result;
}
}
}
};
return obj;
},
_numFmt: function (formatCode) {
return {
"c:numFmt": {
"@formatCode": formatCode ? formatCode : "General",
"@sourceLinked": formatCode ? "0" : "1"
}
}
},
///
/// @brief Transform an array of string into an office's compliance structure
///
/// @param[in] colorArr
/// An array of colorArr, for example: ['ff0000', '00ff00', '0000ff']
///
_colorRef: function (colorArr) {
var arr = [];
for (var i = 0; i < colorArr.length; i++) {
arr.push({
'c:dPt': {
'c:idx': {'@val': i},
'c:bubble3D': {'@val': 0},
'c:spPr': {
'a:solidFill': {
'a:srgbClr': {'@val': colorArr[i].toString()}
}
}
}
});
}
return arr;
},
///
/// @brief Transform an array of string into an office's compliance structure
///
/// @param[in] row int
/// Row index.
/// @param[in] col int
/// Col index.
/// @param[in] isRowAbsolute boolean
/// Will add $ into cell's address if this parameter is true.
/// @param[in] isColAbsolute boolean
/// Will add $ into cell's address if this parameter is true.
///
_rowColToSheetAddress: function (row, col, isRowAbsolute, isColAbsolute) {
var address = "";
if (isColAbsolute)
address += '$';
// these lines of code will transform the number 1-26 into A->Z
// used in excel's cell's coordination
while (col > 0) {
var num = col % 26;
col = (col - num ) / 26;
address += String.fromCharCode(65 + num - 1);
}
if (isRowAbsolute)
address += '$';
address += row;
return address;
},
/// @brief returns XML snippet for a chart dataseries
_ser: function (serie, i) {
var rc2a = this._rowColToSheetAddress; // shortcut
var serieData = {
'c:ser': {
'c:idx': {'@val': i},
'c:order': {'@val': i},
'c:tx': this._strRef('Sheet1!' + rc2a(1, 2 + i, true, true), [serie.name]), // serie's value
'c:invertIfNegative': {'@val': '0'},
'c:cat': this._strRef('Sheet1!' + rc2a(2, 1, true, true) + ':' + rc2a(2 + serie.labels.length - 1, 1, true, true), serie.labels),
'c:val': this._numRef('Sheet1!' + rc2a(2, 2 + i, true, true) + ':' + rc2a(2 + serie.labels.length - 1, 2 + i, true, true), serie.values, "General")
}
};
if (serie.color) {
serieData['c:ser']['c:spPr'] = {
'a:solidFill': {
'a:srgbClr': {'@val': serie.color}
}
};
}
else if (serie.schemeColor) {
serieData['c:ser']['c:spPr'] = {
'a:solidFill': {
'a:schemeClr': {'@val': serie.schemeColor}
}
};
}
if (serie.xml) {
serieData['c:ser'] = _.merge(serieData['c:ser'], serie.xml)
}
// for pie charts
if (serie.colors) {
serieData['c:ser']['#list'] = this._colorRef(serie.colors);
}
return serieData;
},
/// @brief returns XML snippet for a chart dataseries
getSeriesRef: function (data) {
return data.map(this._ser, this);
},
/// @brief returns XML snippet for axis number format
/// e.g. "$0" for US currency, "0%" for percentages
xmlValAxisFormat: function (formatCode) {
return {
"c:chartSpace": {
"c:chart": {
"c:plotArea": {
"c:valAx": {
"c:majorGridlines": {},
"c:numFmt": {
"@formatCode": formatCode,
"@sourceLinked": "0"
}
}
}
}
}
}
},
/// @brief returns XML snippet for an axis scale
/// currently just min/max are supported
/*
<c:scaling><c:orientation val="minMax"/>
<c:max val="24.0"/>
<c:min val="24.0"/>
</c:scaling>
*/
_scaling: function (minValue, maxValue) {
var ref = {
"c:scaling": {
"c:orientation": {
"@val": "minMax"
}
}
};
if (maxValue != undefined) {
ref["c:scaling"]["c:max"] = {
"@val": "" + (maxValue || "")
};
}
if (minValue != undefined) {
ref["c:scaling"]["c:min"] = {
"@val": "" + (minValue || "")
};
}
return ref;
},
_text: function(textSize) {
return {
"c:txPr": {
"a:bodyPr": {},
"a:listStyle": {},
"a:p": {
"a:pPr": {
"a:defRPr": {
"@sz": textSize
}
},
"a:endParaRPr": {
"@lang": "en-US"
}
}
}
};
},
/// @brief returns XML snippet for an axis title
_title: function (title) {
if (typeof title == 'object') return title; // assume it's an XML representations
return {
"c:title": {
"c:tx": {
"c:rich": {
"a:bodyPr": {},
"a:lstStyle": {},
"a:p": {
"a:pPr": {
"a:defRPr": {}
},
"a:r": {
"a:rPr": {
"@lang": "en-US",
"@dirty": "0",
"@smtClean": "0"
},
"a:t": title
},
"a:endParaRPr": {
"@lang": "en-US",
"@dirty": "0"
}
}
}
},
"c:layout": {},
"c:overlay": {
"@val": "0"
}
}
}
}
}.initialize(chartInfo);
}
OfficeChart.getChartBase = function (chartInfo) {
var chartBase;
if (typeof chartInfo == 'string') {
chartBase = _chartSpecs[chartInfo]();
}
else if (typeof chartInfo.renderType == 'string') {
chartBase = _chartSpecs[chartInfo.renderType](chartInfo);
}
else if (chartInfo.xml) {
chartBase = chartInfo.xml
}
else {
throw new Error("invalid chart type")
}
// return deep copy so can create multiple charts from same base within one PowerPoint document
return JSON.parse(JSON.stringify(chartBase));
}
module.exports = OfficeChart;
/***********************************************************************************************************************
// Column chart
new OfficeChart({
title: 'eSurvey chart',
renderType: 'column',
data: [ // array of series
{
name: 'Income',
labels: ['2005', '2006', '2007', '2008', '2009'],
values: [23.5, 26.2, 30.1, 29.5, 24.6],
colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional
},
{
name: 'Expense',
labels: ['2005', '2006', '2007', '2008', '2009'],
values: [18.1, 22.8, 23.9, 25.1, 25],
colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional
}
]
});
// Pie chart
new OfficeChart({
title: 'eSurvey chart',
renderType: 'pie',
data: [ // array of series
{
name: 'Income',
labels: ['2005', '2006', '2007', '2008', '2009'],
values: [23.5, 26.2, 30.1, 29.5, 24.6],
colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional
}
]
});
// Clustered bar chat
new OfficeChart({
title: 'eSurvey chart',
renderType: 'group-bar',
data: [ // array of series
{
name: 'Income',
labels: ['2005', '2006', '2007', '2008', '2009'],
values: [23.5, 26.2, 30.1, 29.5, 24.6],
colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional
}
]
});
***********************************************************************************************************************/