From 1c5fc6dd40b905e57fa60e3ed7390873f456a7e1 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 6 Jul 2021 18:34:43 +0300 Subject: [PATCH 01/23] replace Cyrillic diagram name with Latin --- chart/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/base.js b/chart/base.js index c780587..2a064bc 100644 --- a/chart/base.js +++ b/chart/base.js @@ -767,7 +767,7 @@ var Chart = Backbone.Model.extend ({ "xdr:cNvPr": { "$": { "id": `${n + 1}`, - "name": `Диаграмма ${n}` + "name": `Diagram ${n}` } }, "xdr:cNvGraphicFramePr": {} From bc3a4b92af043ae662eacaad28a11dc1e95fa7ec Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 6 Jul 2021 18:44:09 +0300 Subject: [PATCH 02/23] allow to pass the chart position as a fields of chart --- chart/base.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/chart/base.js b/chart/base.js index 2a064bc..126d20b 100644 --- a/chart/base.js +++ b/chart/base.js @@ -748,16 +748,16 @@ var Chart = Backbone.Model.extend ({ } o ["xdr:wsDr"]["xdr:twoCellAnchor"].push ({ "xdr:from": { - "xdr:col": 0, - "xdr:colOff": 0, - "xdr:row": (n - 1) * 20, - "xdr:rowOff": 0 + "xdr:col": chart.xdr_from_col ?? 0, + "xdr:colOff": chart.xdr_from_colOff ?? 0, + "xdr:row": chart.xdr_from_row ?? (n - 1) * 20, + "xdr:rowOff": chart.xdr_from_rowOff ?? 0 }, "xdr:to": { - "xdr:col": 10, - "xdr:colOff": 0, - "xdr:row": n * 20, - "xdr:rowOff": 0 + "xdr:col": chart.xdr_to_col ?? 10, + "xdr:colOff": chart.xdr_to_colOff ?? 0, + "xdr:row": chart.xdr_to_row ?? n * 20, + "xdr:rowOff": chart.xdr_to_rowOff ??0 }, "xdr:graphicFrame": { "$": { From 8a59b3993a08f91942f9b6139561c76c0164a91f Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 6 Jul 2021 20:15:59 +0300 Subject: [PATCH 03/23] use chart.position object to set chart position on page --- chart/base.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/chart/base.js b/chart/base.js index 126d20b..9a18c61 100644 --- a/chart/base.js +++ b/chart/base.js @@ -688,6 +688,19 @@ var Chart = Backbone.Model.extend ({ async.eachSeries (me.charts, (chart, cb) => { ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me [a] = chart [a]); + + const position = Object.assign({ + fromColumn: 0, + fromColumnOffset: 0, + fromRow: n * 20, + fromRowOffset: 0, + toColumn: 10, + toColumnOffset: 0, + toRow: (n + 1) * 20, + toRowOffset: 0, + }, + chart.position + ); async.series ([ function (cb) { @@ -748,16 +761,16 @@ var Chart = Backbone.Model.extend ({ } o ["xdr:wsDr"]["xdr:twoCellAnchor"].push ({ "xdr:from": { - "xdr:col": chart.xdr_from_col ?? 0, - "xdr:colOff": chart.xdr_from_colOff ?? 0, - "xdr:row": chart.xdr_from_row ?? (n - 1) * 20, - "xdr:rowOff": chart.xdr_from_rowOff ?? 0 + "xdr:col": position.fromColumn, + "xdr:colOff": position.fromColumnOffset, + "xdr:row": position.fromRow, + "xdr:rowOff": position.fromRowOffset }, "xdr:to": { - "xdr:col": chart.xdr_to_col ?? 10, - "xdr:colOff": chart.xdr_to_colOff ?? 0, - "xdr:row": chart.xdr_to_row ?? n * 20, - "xdr:rowOff": chart.xdr_to_rowOff ??0 + "xdr:col": position.toColumn, + "xdr:colOff": position.toColumnOffset, + "xdr:row": position.toRow, + "xdr:rowOff": position.toRowOffset }, "xdr:graphicFrame": { "$": { From 4220d146c5959b852fc4c53cf0f174761dc1bf95 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 9 Jul 2021 04:58:25 +0300 Subject: [PATCH 04/23] set coordinates for the first chart --- chart/base.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/chart/base.js b/chart/base.js index 9a18c61..e93353b 100644 --- a/chart/base.js +++ b/chart/base.js @@ -749,13 +749,22 @@ var Chart = Backbone.Model.extend ({ }); }, function (cb) { - if (n == 1) { - return cb (); - } me.read ({file: "xl/drawings/drawing1.xml"}, function (err, o) { if (err) { return cb (new VError (err, "generateMult")); } + if (n == 1) { + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:col"] = position.fromColumn; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:colOff"] = position.fromColumnOffset; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:row"] = position.fromRow; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:from"]["xdr:rowOff"] = position.fromRowOffset; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:col"] = position.toColumn; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:colOff"] = position.toColumnOffset; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:row"] = position.toRow; + o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:rowOff"] = position.toRowOffset; + me.write ({file: `xl/drawings/drawing1.xml`, object: o}); + return cb (); + } if (n == 2) { o ["xdr:wsDr"]["xdr:twoCellAnchor"] = [o ["xdr:wsDr"]["xdr:twoCellAnchor"]]; } From 01cdcdc4cf7c37db5f8f17dfcb1017e703ddec36 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Jul 2021 20:12:43 +0300 Subject: [PATCH 05/23] update README --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index 42d07f7..3153a04 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,45 @@ xlsxChart.generate (opts, function (err, data) { }); ``` +## Available chart options +```js +var opts = { + file: "chart.xlsx", // exported file + type: "nodebuffer", // optional: used by JSZip library + charts: [ + { + chart: "column", // pie, doughnut, line, area, bar + titles: [ + "title1", // list of chart titles + ], + fields: [ + "field1", // list of chart fields + ], + data: { + "title1": { + "field1": 123, // structured data + }, + }, + position: { // optional: chart position + fromColumn: 0, // chart top left x coordinate in columns + fromColumnOffset: 0, // chart top left x coordinate in pixels + fromRow: n * 20, // chart top left y coordinate in columns + fromRowOffset: 0, // chart top left y coordinate in pixels + toColumn: 10, // chart bottom right x coordinate in columns + toColumnOffset: 0, // chart bottom right x coordinate in pixels + toRow: (n + 1) * 20, // chart bottom right y coordinate in columns + toRowOffset: 0, // chart bottom right y coordinate in pixels + }, + } + ] +}; + +xlsxChart.writeFile (opts, function (err) { + console.log ("File: ", opts.file); +}); + +``` + ## Examples column.js From 00d930e39a11c4ae889945c449d0c6f3e82abdac Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 9 Jul 2021 06:07:38 +0300 Subject: [PATCH 06/23] add custom point color for bar chart --- chart/base.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/chart/base.js b/chart/base.js index e93353b..990ff50 100644 --- a/chart/base.js +++ b/chart/base.js @@ -402,6 +402,43 @@ var Chart = Backbone.Model.extend ({ var ser = {}; _.each (me.titles, function (t, i) { var chart = me.data [t].chart || me.chart; + var customColorsPoints = { + "c:dPt": [], + }; + if (me.charts [chartN - 1].customColors) { + console.warn(me) + customColorsPoints ["c:dPt"] = Object.values(me.charts [chartN - 1].customColors[t] || {}).map(function(color, i) { + if (!color) { + return null; + } + return { + "c:idx": { + $: { + val: i, + }, + }, + "c:spPr": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: color, + }, + }, + }, + "a:ln": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: color, + }, + }, + }, + }, + }, + } + }).filter(Boolean); + } + console.warn(customColorsPoints) var r = { "c:idx": { $: { @@ -431,6 +468,7 @@ var Chart = Backbone.Model.extend ({ } } }, + ...customColorsPoints, "c:cat": { "c:strRef": { "c:f": "Table!$A$" + (row + 1) + ":$A$" + (me.fields.length + row), From a3a82dcefa982c4cb648961b4c997f19f09b1a07 Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 12 Jul 2021 04:43:17 +0300 Subject: [PATCH 07/23] add custom series color; add custom fill and line colors for point --- chart/base.js | 96 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/chart/base.js b/chart/base.js index 990ff50..f4dd524 100644 --- a/chart/base.js +++ b/chart/base.js @@ -400,45 +400,98 @@ var Chart = Backbone.Model.extend ({ return cb (new VError (err, "writeChart")); } var ser = {}; + const chartOpts = me.charts [chartN - 1]; _.each (me.titles, function (t, i) { var chart = me.data [t].chart || me.chart; var customColorsPoints = { "c:dPt": [], }; - if (me.charts [chartN - 1].customColors) { - console.warn(me) - customColorsPoints ["c:dPt"] = Object.values(me.charts [chartN - 1].customColors[t] || {}).map(function(color, i) { - if (!color) { - return null; - } - return { - "c:idx": { - $: { - val: i, - }, - }, - "c:spPr": { - "a:solidFill": { - "a:srgbClr": { + var customColorsSeries = {}; + + if (chartOpts.customColors) { + const customColors = chartOpts.customColors; + + if (customColors.points) { + customColorsPoints ["c:dPt"] = chartOpts.fields.map(function(field, i) { + const color = _.chain(customColors).get('points').get(t).get(field, null).value(); + + if (!color) { + return null; + } + if (color === 'noFill') { + return { + "c:idx": { $: { - val: color, + val: i, }, }, + "c:spPr": { + 'a:noFill': '' + } + } + } + let fillColor = color; + let lineColor = color; + if (typeof color === 'object') { + fillColor = color.fill; + lineColor = color.line; + } + return { + "c:idx": { + $: { + val: i, + }, }, - "a:ln": { + "c:spPr": { "a:solidFill": { "a:srgbClr": { $: { - val: color, + val: fillColor, + }, + }, + }, + "a:ln": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: lineColor, + }, }, }, }, }, - }, + } + }).filter(Boolean); + } + + if (customColors.series && customColors.series[t]) { + let fillColor = customColors.series[t]; + let lineColor = customColors.series[t]; + if (typeof customColors.series === 'object') { + fillColor = customColors.series[t].fill; + lineColor = customColors.series[t].line; } - }).filter(Boolean); + customColorsSeries ["c:spPr"] = { + "a:solidFill": { + "a:srgbClr": { + $: { + val: fillColor, + }, + }, + }, + "a:ln": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: lineColor, + }, + }, + }, + }, + }; + } } - console.warn(customColorsPoints) + var r = { "c:idx": { $: { @@ -469,6 +522,7 @@ var Chart = Backbone.Model.extend ({ } }, ...customColorsPoints, + ...customColorsSeries, "c:cat": { "c:strRef": { "c:f": "Table!$A$" + (row + 1) + ":$A$" + (me.fields.length + row), From b10392b3639423ddc4ee8ac381e5b6aab4792e16 Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 12 Jul 2021 04:08:47 +0300 Subject: [PATCH 08/23] fix me.charts mixing (used as list of charts and list of types) in some cases it was array of chart definitions in some cases it was object: { "bar": true, "radar": true, ... } --- chart/base.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/chart/base.js b/chart/base.js index f4dd524..26c759b 100644 --- a/chart/base.js +++ b/chart/base.js @@ -225,20 +225,20 @@ var Chart = Backbone.Model.extend ({ }); }; _.each (["line", "radar", "area", "scatter", "pie"], function (chart) { - if (!me.charts [chart]) { + if (!me.chartTypes [chart]) { delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]; } else { addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]); }; }); - if (!me.charts ["column"] && !me.charts ["bar"]) { + if (!me.chartTypes ["column"] && !me.chartTypes ["bar"]) { delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; } else - if (me.charts ["column"] && !me.charts ["bar"]) { + if (me.chartTypes ["column"] && !me.chartTypes ["bar"]) { o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]; addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); } else - if (!me.charts ["column"] && me.charts ["bar"]) { + if (!me.chartTypes ["column"] && me.chartTypes ["bar"]) { o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]; addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); } else { @@ -650,27 +650,28 @@ var Chart = Backbone.Model.extend ({ /* Set template name */ - setTemplateName: function () { + setTemplateName: function (opts) { var me = this; - var charts = {}; + var chartTypes = {}; _.each (me.data, function (o) { - charts [o.chart || me.chart] = true; + chartTypes [o.chart || me.chart] = true; }); - me.charts = charts; - if (charts ["radar"]) { + me.charts = [opts]; + me.chartTypes = chartTypes; + if (chartTypes ["radar"]) { me.tplName = "radar"; return; }; - if (charts ["scatter"]) { + if (chartTypes ["scatter"]) { me.tplName = "scatter"; return; }; - if (charts ["pie"]) { + if (chartTypes ["pie"]) { me.tplName = "pie"; return; }; - if (_.keys (charts).length == 1) { - me.tplName = _.keys (charts) [0]; + if (_.keys (chartTypes).length == 1) { + me.tplName = _.keys (chartTypes) [0]; return; }; me.tplName = "charts"; @@ -689,7 +690,7 @@ var Chart = Backbone.Model.extend ({ async.series ([ function (cb) { me.zip = new JSZip (); - me.setTemplateName (); + me.setTemplateName (opts); let path = me.templatePath ? me.templatePath : (__dirname + "/../template/" + me.tplName + ".xlsx"); fs.readFile(path, function (err, data) { if (err) { From ce6a01c025617948952c3798998d12cebd6a5718 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Jul 2021 19:51:13 +0300 Subject: [PATCH 09/23] add custom markers color --- chart/base.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/chart/base.js b/chart/base.js index 26c759b..55758ff 100644 --- a/chart/base.js +++ b/chart/base.js @@ -467,9 +467,11 @@ var Chart = Backbone.Model.extend ({ if (customColors.series && customColors.series[t]) { let fillColor = customColors.series[t]; let lineColor = customColors.series[t]; + let markerColor = customColors.series[t]; if (typeof customColors.series === 'object') { fillColor = customColors.series[t].fill; lineColor = customColors.series[t].line; + markerColor = customColors.series[t].marker; } customColorsSeries ["c:spPr"] = { "a:solidFill": { @@ -488,6 +490,17 @@ var Chart = Backbone.Model.extend ({ }, }, }, + "c:marker": { + "c:spPr": { + "a:solidFill": { + "a:srgbClr": { + $: { + val: markerColor, + }, + }, + }, + }, + }, }; } } From d67ba9564546e1c733c5bacf0b9b2b8e811f3db1 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Jul 2021 19:52:38 +0300 Subject: [PATCH 10/23] Add custom charts in multi-chart config --- chart/base.js | 78 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/chart/base.js b/chart/base.js index 55758ff..6be81ce 100644 --- a/chart/base.js +++ b/chart/base.js @@ -602,15 +602,89 @@ var Chart = Backbone.Model.extend ({ } else { o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; }; - } else - if (chart == "bar") { + } else if (chart == "bar") { if (me.tplName == "charts") { o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]["c:ser"] = ser; } else { o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; }; } else { + if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"][`c:${chart}Chart`]) { + // minimal chart config + o ["c:chartSpace"]["c:chart"] = { + "c:autoTitleDeleted": { + $: { + val: 0, + }, + }, + "c:plotArea": { + [`c:${chart}Chart`]: { + "c:varyColors": { + $: { + val: 1, + }, + }, + }, + }, + }; + } + // set series data o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:ser"] = ser; + if (chartOpts.firstSliceAng) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:firstSliceAng"] = { + $: { + val: chartOpts.firstSliceAng, + }, + }; + } + if (chartOpts.holeSize) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:holeSize"] = { + $: { + val: chartOpts.holeSize, + }, + }; + } + // if (chartOpts.showLabels) { + // o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:dLbls"] = { + // "c:showLegendKey": { + // $: { + // val: 1, + // }, + // }, + // "c:showVal": { + // $: { + // val: 0, + // }, + // }, + // "c:showCatName": { + // $: { + // val: 0, + // }, + // }, + // "c:showSerName": { + // $: { + // val: 0, + // }, + // }, + // "c:showPercent": { + // $: { + // val: 0, + // }, + // }, + // "c:showBubbleSize": { + // $: { + // val: 0, + // }, + // }, + // "c:showLeaderLines": { + // $: { + // val: 1, + // }, + // }, + // }; + // } + // o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:grouping"] = { $: { val: 'standard' } }; + }; }); me.removeUnusedCharts (o); From d2e49bc3eaf9f911b60348de72a990dba4d2acb1 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Jul 2021 19:53:37 +0300 Subject: [PATCH 11/23] add custom legend position ('b', 'r', etc); null to hide legend --- chart/base.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/chart/base.js b/chart/base.js index 6be81ce..223ddd2 100644 --- a/chart/base.js +++ b/chart/base.js @@ -686,6 +686,18 @@ var Chart = Backbone.Model.extend ({ // o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:grouping"] = { $: { val: 'standard' } }; }; + + if (chartOpts.legendPos === undefined || chartOpts.legendPos) { + o ["c:chartSpace"]["c:chart"]["c:legend"] = { + "c:legendPos": { + $: { + val: chartOpts.legendPos || 'r', + }, + }, + }; + } else if (chartOpts.legendPos === null) { + delete o ["c:chartSpace"]["c:chart"]["c:legend"]; + } }); me.removeUnusedCharts (o); From 654ee4d58695a76a67b7e8dc4c9456997cd2a3ec Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Jul 2021 20:22:59 +0300 Subject: [PATCH 12/23] Update README.md --- README.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3153a04..b37f68a 100644 --- a/README.md +++ b/README.md @@ -208,15 +208,23 @@ var opts = { chart: "column", // pie, doughnut, line, area, bar titles: [ "title1", // list of chart titles + "title2", // list of chart titles ], fields: [ "field1", // list of chart fields + "field2", ], data: { "title1": { "field1": 123, // structured data + "field2": 321, + }, + "title2": { + "field1": 456, + "field2": 654, }, }, + chartTitle: "Title", position: { // optional: chart position fromColumn: 0, // chart top left x coordinate in columns fromColumnOffset: 0, // chart top left x coordinate in pixels @@ -227,8 +235,31 @@ var opts = { toRow: (n + 1) * 20, // chart bottom right y coordinate in columns toRowOffset: 0, // chart bottom right y coordinate in pixels }, - } - ] + customColors: { // optional: chart colors + points: { + "title1": { + "field1": "FF0000", // colors in the same structure as data; not all points required + "field1": { + fill: "FF0000", // separate colors for fill and border could be set + line: "FF0000", + }, + }, + }, + series: { + "title1": "FF0000", // whole series color could be also set + "title2": { + fill: "FF0000", // separate colors for fill and border could be set + line: "FF0000", + markerColor: "FF0000", // different marker color also could be set for line chart + }, + }, + }, + firstSliceAng: 270, // optional: first slice angle for pie and doughnut chart in degrees + holeSize: 50, // optional: hole size of doughnut chart in percent + legendPos: "r", // optional: 'l', 't', 'r', 'b', null - legend position; null to hide; right is default + deleteEmptyCells: false, // optional: allow to will empty values with 0 or leave as gaps + }, + ], }; xlsxChart.writeFile (opts, function (err) { From 94ad33866ff08166a38c111a47c6523726a3b8ad Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Jul 2021 20:44:54 +0300 Subject: [PATCH 13/23] add ability to move and resize chart and title over the chart canvas --- chart/base.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/chart/base.js b/chart/base.js index 223ddd2..2fe32e4 100644 --- a/chart/base.js +++ b/chart/base.js @@ -698,11 +698,47 @@ var Chart = Backbone.Model.extend ({ } else if (chartOpts.legendPos === null) { delete o ["c:chartSpace"]["c:chart"]["c:legend"]; } + + if (chartOpts.manualLayout && chartOpts.manualLayout.plotArea) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:layout"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:layout"] || {}; + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:layout"]["c:manualLayout"] = { + "c:xMode": { + $: { + val: 'edge', + }, + }, + "c:yMode": { + $: { + val: 'edge', + }, + }, + "c:x": { + $: { + val: chartOpts.manualLayout.plotArea.x, + }, + }, + "c:y": { + $: { + val: chartOpts.manualLayout.plotArea.y, + }, + }, + "c:w": { + $: { + val: chartOpts.manualLayout.plotArea.w, + }, + }, + "c:h": { + $: { + val: chartOpts.manualLayout.plotArea.h, + }, + }, + }; + } }); me.removeUnusedCharts (o); - + if (me.chartTitle) { - me.writeTitle (o, me.chartTitle); + me.writeTitle (o, me.chartTitle, chartOpts); }; me.write ({file: `xl/charts/chart${chartN}.xml`, object: o}); cb (); @@ -711,9 +747,36 @@ var Chart = Backbone.Model.extend ({ /* Chart title */ - writeTitle: function (chart, title) { + writeTitle: function (o, title, chartOpts = {}) { var me = this; - chart ["c:chartSpace"]["c:chart"]["c:title"] = { + const layout = {}; + + if (chartOpts.manualLayout && chartOpts.manualLayout.title) { + layout["c:manualLayout"] = { + "c:xMode": { + $: { + val: 'edge', + }, + }, + "c:yMode": { + $: { + val: 'edge', + }, + }, + "c:x": { + $: { + val: chartOpts.manualLayout.title.x, + }, + }, + "c:y": { + $: { + val: chartOpts.manualLayout.title.y, + }, + }, + }; + } + + o ["c:chartSpace"]["c:chart"]["c:title"] = { "c:tx": { "c:rich": { "a:bodyPr": {}, @@ -733,14 +796,14 @@ var Chart = Backbone.Model.extend ({ } } }, - "c:layout": {}, + "c:layout": layout, "c:overlay": { $: { val: "0" } } }; - chart ["c:chartSpace"]["c:chart"]["c:autoTitleDeleted"] = { + o ["c:chartSpace"]["c:chart"]["c:autoTitleDeleted"] = { $: { val: "0" } From e96f071a9e2c2472ab31bf810792ba1ba2d82028 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Jul 2021 20:45:04 +0300 Subject: [PATCH 14/23] update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index b37f68a..231f7d8 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,18 @@ var opts = { }, }, }, + manualLayout: { // optional blocks position on the chart canvas + plotArea: { // chart position + x: 0.5, // x and y of top left corner in proportion value 0..1 + y: 0.5, + w: 0.5, // width and height of block point in proportion value 0..1 + h: 0.5, + }, + title: { + x: 0.5, // x and y of top left corner in proportion value 0..1 + y: 0.5, + }, + }, firstSliceAng: 270, // optional: first slice angle for pie and doughnut chart in degrees holeSize: 50, // optional: hole size of doughnut chart in percent legendPos: "r", // optional: 'l', 't', 'r', 'b', null - legend position; null to hide; right is default From 00bf5cadd06a8e25051dbefeeda73cd1cd873bea Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 15 Jul 2021 17:28:00 +0300 Subject: [PATCH 15/23] fix adding Line chart for multi-chart config --- chart/base.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/chart/base.js b/chart/base.js index 2fe32e4..0c0c079 100644 --- a/chart/base.js +++ b/chart/base.js @@ -608,6 +608,16 @@ var Chart = Backbone.Model.extend ({ } else { o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; }; + } else if (chart == "line") { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"] || o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; + delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; + delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:barDir"]; + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:grouping"] = { $: { val: 'standard' } }; + if (me.tplName == "charts") { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"][1]["c:ser"] = ser; + } else { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:ser"] = ser; + }; } else { if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"][`c:${chart}Chart`]) { // minimal chart config From 0cbda62ab954c71cc1307c187df69ff267b3376f Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 21 Jul 2021 11:25:20 +0300 Subject: [PATCH 16/23] add eslint for easier code formatting --- .eslintrc.json | 18 + chart/base.js | 144 +++---- package-lock.json | 1056 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 8 +- 4 files changed, 1152 insertions(+), 74 deletions(-) create mode 100644 .eslintrc.json create mode 100644 package-lock.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2418563 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "env": { + "commonjs": true, + "es2020": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 12 + }, + "rules": { + "space-before-function-paren": ["warn", "always"], + "space-before-blocks": ["warn", "always"], + "func-call-spacing": ["warn", "always"], + "object-curly-spacing": ["warn", "never"], + "quotes": ["warn", "double"], + "indent": ["warn", "tab"] + } +} \ No newline at end of file diff --git a/chart/base.js b/chart/base.js index 0c0c079..acaf4eb 100644 --- a/chart/base.js +++ b/chart/base.js @@ -18,7 +18,7 @@ var Chart = Backbone.Model.extend ({ return new VError (err, "getXML"); } cb (err, o); - }); + }); }, /* Build XML from object and write to zip @@ -34,11 +34,11 @@ var Chart = Backbone.Model.extend ({ */ getColName: function (n) { var abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - n --; + n--; if (n < 26) { - return abc [n]; + return abc[n]; } else { - return abc [(n / 26 - 1) | 0] + abc [n % 26]; + return abc[(n / 26 - 1) | 0] + abc[n % 26]; } }, /* @@ -49,7 +49,7 @@ var Chart = Backbone.Model.extend ({ if (!me.str.hasOwnProperty (s)) { throw new VError ("getStr: Unknown string: " + s); } - return me.str [s]; + return me.str[s]; }, /* Write table @@ -95,7 +95,7 @@ var Chart = Backbone.Model.extend ({ $: { r: me.getColName (x + 2) + (y + 2) }, - v: me.data [t][f] + v: me.data[t][f] }); }); r.c = c; @@ -111,13 +111,13 @@ var Chart = Backbone.Model.extend ({ */ writeMultTable: function (row, cb) { let me = this; - + me.read ({file: "xl/worksheets/sheet2.xml"}, function (err, o) { if (err) { return cb (new VError (err, "writeMultTable")); } o.worksheet.dimension.$.ref = `A${row}:${me.getColName (me.titles.length + 1)}${me.fields.length + row + 1}`; - + let rows = [{ $: { r: row, @@ -164,7 +164,7 @@ var Chart = Backbone.Model.extend ({ $: { r: me.getColName (x + 2) + (y + 2 + row) }, - v: me.data [t][f] + v: me.data[t][f] }); }); r.c = c; @@ -184,16 +184,16 @@ var Chart = Backbone.Model.extend ({ */ writeStrings: function (cb) { let me = this; - + me.read ({file: "xl/sharedStrings.xml"}, function (err, o) { if (err) { return cb (new VError (err, "writeStrings")); } o.sst.$.count = me.titles.length + me.fields.length; o.sst.$.uniqueCount = o.sst.$.count; - + let si = []; - + _.each (me.titles, function (t) { si.push ({t: t}); }); @@ -201,9 +201,9 @@ var Chart = Backbone.Model.extend ({ si.push ({t: t}); }); me.str = {}; - + _.each (si, function (o, i) { - me.str [o.t] = i; + me.str[o.t] = i; }); o.sst.si = si; me.write ({file: "xl/sharedStrings.xml", object: o}); @@ -265,7 +265,7 @@ var Chart = Backbone.Model.extend ({ /* Write chart */ -/* + /* writeChart: function (chartN, cb) { var me = this; @@ -391,18 +391,18 @@ var Chart = Backbone.Model.extend ({ cb (); }); }, -*/ + */ writeChart: function (chartN, row, cb) { var me = this; - + me.read ({file: "xl/charts/chart1.xml"}, function (err, o) { if (err) { return cb (new VError (err, "writeChart")); } var ser = {}; - const chartOpts = me.charts [chartN - 1]; + const chartOpts = me.charts[chartN - 1]; _.each (me.titles, function (t, i) { - var chart = me.data [t].chart || me.chart; + var chart = me.data[t].chart || me.chart; var customColorsPoints = { "c:dPt": [], }; @@ -412,13 +412,13 @@ var Chart = Backbone.Model.extend ({ const customColors = chartOpts.customColors; if (customColors.points) { - customColorsPoints ["c:dPt"] = chartOpts.fields.map(function(field, i) { - const color = _.chain(customColors).get('points').get(t).get(field, null).value(); + customColorsPoints["c:dPt"] = chartOpts.fields.map (function (field, i) { + const color = _.chain (customColors).get ("points").get (t).get (field, null).value (); if (!color) { return null; } - if (color === 'noFill') { + if (color === "noFill") { return { "c:idx": { $: { @@ -426,13 +426,13 @@ var Chart = Backbone.Model.extend ({ }, }, "c:spPr": { - 'a:noFill': '' + "a:noFill": "" } } } let fillColor = color; let lineColor = color; - if (typeof color === 'object') { + if (typeof color === "object") { fillColor = color.fill; lineColor = color.line; } @@ -461,19 +461,19 @@ var Chart = Backbone.Model.extend ({ }, }, } - }).filter(Boolean); + }).filter (Boolean); } if (customColors.series && customColors.series[t]) { let fillColor = customColors.series[t]; let lineColor = customColors.series[t]; let markerColor = customColors.series[t]; - if (typeof customColors.series === 'object') { + if (typeof customColors.series === "object") { fillColor = customColors.series[t].fill; lineColor = customColors.series[t].line; markerColor = customColors.series[t].marker; } - customColorsSeries ["c:spPr"] = { + customColorsSeries["c:spPr"] = { "a:solidFill": { "a:srgbClr": { $: { @@ -571,7 +571,7 @@ var Chart = Backbone.Model.extend ({ $: { idx: j }, - "c:v": me.data [t][f] + "c:v": me.data[t][f] }; }) } @@ -579,11 +579,11 @@ var Chart = Backbone.Model.extend ({ } }; if (chart == "scatter") { - r ["c:xVal"] = r ["c:cat"]; - delete r ["c:cat"]; - r ["c:yVal"] = r ["c:val"]; - delete r ["c:val"]; - r ["c:spPr"] = { + r["c:xVal"] = r["c:cat"]; + delete r["c:cat"]; + r["c:yVal"] = r["c:val"]; + delete r["c:val"]; + r["c:spPr"] = { "a:ln": { $: { w: 28575 @@ -592,8 +592,8 @@ var Chart = Backbone.Model.extend ({ } }; }; - ser [chart] = ser [chart] || []; - ser [chart].push (r); + ser[chart] = ser[chart] || []; + ser[chart].push (r); }); _.each (ser, function (ser, chart) { if (chart == "column") { @@ -612,7 +612,7 @@ var Chart = Backbone.Model.extend ({ o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"] || o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:barDir"]; - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:grouping"] = { $: { val: 'standard' } }; + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:grouping"] = {$: {val: "standard"}}; if (me.tplName == "charts") { o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"][1]["c:ser"] = ser; } else { @@ -694,14 +694,14 @@ var Chart = Backbone.Model.extend ({ // }; // } // o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:grouping"] = { $: { val: 'standard' } }; - + }; if (chartOpts.legendPos === undefined || chartOpts.legendPos) { o ["c:chartSpace"]["c:chart"]["c:legend"] = { "c:legendPos": { $: { - val: chartOpts.legendPos || 'r', + val: chartOpts.legendPos || "r", }, }, }; @@ -714,12 +714,12 @@ var Chart = Backbone.Model.extend ({ o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:layout"]["c:manualLayout"] = { "c:xMode": { $: { - val: 'edge', + val: "edge", }, }, "c:yMode": { $: { - val: 'edge', + val: "edge", }, }, "c:x": { @@ -765,12 +765,12 @@ var Chart = Backbone.Model.extend ({ layout["c:manualLayout"] = { "c:xMode": { $: { - val: 'edge', + val: "edge", }, }, "c:yMode": { $: { - val: 'edge', + val: "edge", }, }, "c:x": { @@ -834,16 +834,16 @@ var Chart = Backbone.Model.extend ({ me.tplName = "radar"; return; }; - if (chartTypes ["scatter"]) { + if (chartTypes["scatter"]) { me.tplName = "scatter"; return; }; - if (chartTypes ["pie"]) { + if (chartTypes["pie"]) { me.tplName = "pie"; return; }; if (_.keys (chartTypes).length == 1) { - me.tplName = _.keys (chartTypes) [0]; + me.tplName = _.keys (chartTypes)[0]; return; }; me.tplName = "charts"; @@ -864,9 +864,9 @@ var Chart = Backbone.Model.extend ({ me.zip = new JSZip (); me.setTemplateName (opts); let path = me.templatePath ? me.templatePath : (__dirname + "/../template/" + me.tplName + ".xlsx"); - fs.readFile(path, function (err, data) { + fs.readFile (path, function (err, data) { if (err) { - console.error(`Template ${path} not read: ${err}`); + console.error (`Template ${path} not read: ${err}`); return cb (err); }; me.zip.load (data); @@ -878,9 +878,9 @@ var Chart = Backbone.Model.extend ({ }, function (cb) { _.each (me.titles, function (t) { - me.data [t] = me.data [t] || {}; + me.data[t] = me.data[t] || {}; _.each (me.fields, function (f) { - me.data [t][f] = me.data [t][f] || (me.deleteEmptyCells ? '' : 0); //deleteEmptyCells - don't display missing values as 0 + me.data[t][f] = me.data[t][f] || (me.deleteEmptyCells ? "" : 0); //deleteEmptyCells - don't display missing values as 0 }); }); me.writeTable (cb); @@ -898,19 +898,19 @@ var Chart = Backbone.Model.extend ({ }, generateMult: function (opts, cb) { let me = this; - + opts.type = opts.type || "nodebuffer"; _.extend (me, opts); async.series ([ function (cb) { me.zip = new JSZip (); - + let path = me.templatePath || (__dirname + "/../template/mult.xlsx"); - + fs.readFile (path, function (err, data) { if (err) { - console.error(`Template ${path} not read: ${err}`); + console.error (`Template ${path} not read: ${err}`); return cb (err); } me.zip.load (data); @@ -920,7 +920,7 @@ var Chart = Backbone.Model.extend ({ function (cb) { me.titles = []; me.fields = []; - + me.charts.forEach (chart => { me.titles = [...me.titles, ...chart.titles]; me.fields = [...me.fields, ...chart.fields]; @@ -930,15 +930,15 @@ var Chart = Backbone.Model.extend ({ }, function (cb) { let row = 1; - + async.eachSeries (me.charts, (chart, cb) => { - ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me [a] = chart [a]); - + ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me[a] = chart[a]); + _.each (me.titles, function (t) { - me.data [t] = me.data [t] || {}; - + me.data[t] = me.data[t] || {}; + _.each (me.fields, function (f) { - me.data [t][f] = me.data [t][f] || 0; + me.data[t][f] = me.data[t][f] || 0; }); }); me.writeMultTable (row, () => { @@ -950,11 +950,11 @@ var Chart = Backbone.Model.extend ({ function (cb) { let n = 0; let row = 2; - + async.eachSeries (me.charts, (chart, cb) => { - ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me [a] = chart [a]); + ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me[a] = chart[a]); - const position = Object.assign({ + const position = Object.assign ({ fromColumn: 0, fromColumnOffset: 0, fromRow: n * 20, @@ -964,16 +964,16 @@ var Chart = Backbone.Model.extend ({ toRow: (n + 1) * 20, toRowOffset: 0, }, - chart.position + chart.position ); - + async.series ([ function (cb) { - me.writeChart (++ n, row, cb); + me.writeChart (++n, row, cb); }, function (cb) { row += 3 + me.fields.length; - + if (n == 1) { return cb (); } @@ -987,7 +987,7 @@ var Chart = Backbone.Model.extend ({ "PartName": `/xl/charts/chart${n}.xml` } }); - me.write ({file: `[Content_Types].xml`, object: o}); + me.write ({file: "[Content_Types].xml", object: o}); cb (); }); }, @@ -1009,7 +1009,7 @@ var Chart = Backbone.Model.extend ({ "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" } }); - me.write ({file: `xl/drawings/_rels/drawing1.xml.rels`, object: o}); + me.write ({file: "xl/drawings/_rels/drawing1.xml.rels", object: o}); cb (); }); }, @@ -1027,7 +1027,7 @@ var Chart = Backbone.Model.extend ({ o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:colOff"] = position.toColumnOffset; o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:row"] = position.toRow; o ["xdr:wsDr"]["xdr:twoCellAnchor"]["xdr:to"]["xdr:rowOff"] = position.toRowOffset; - me.write ({file: `xl/drawings/drawing1.xml`, object: o}); + me.write ({file: "xl/drawings/drawing1.xml", object: o}); return cb (); } if (n == 2) { @@ -1090,7 +1090,7 @@ var Chart = Backbone.Model.extend ({ }, "xdr:clientData": {} }); - me.write ({file: `xl/drawings/drawing1.xml`, object: o}); + me.write ({file: "xl/drawings/drawing1.xml", object: o}); cb (); }); } @@ -1102,7 +1102,7 @@ var Chart = Backbone.Model.extend ({ return cb (new VError (err, "build")); } let result = me.zip.generate ({type: me.type}); - + cb (null, result); }); } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..210704c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1056 @@ +{ + "name": "xlsx-chart", + "version": "0.4.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "backbone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", + "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", + "requires": { + "underscore": ">=1.8.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.30.0.tgz", + "integrity": "sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "extsprintf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.1.tgz", + "integrity": "sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jszip": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.6.1.tgz", + "integrity": "sha1-uI86ey5noqBIFSmCx6N1bZxIKPA=", + "requires": { + "pako": "~1.0.2" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.1.tgz", + "integrity": "sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "underscore": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 159cdbb..61f9d15 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,13 @@ "verror": "^1.6.0", "xml2js": "^0.4.13" }, - "devDependencies": {}, + "devDependencies": { + "eslint": "^7.30.0" + }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint ./", + "lint:fix": "eslint ./ --fix" }, "repository": { "type": "git", From 5340a963427f4a382199722c1abbc2ceb5814564 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 21 Jul 2021 11:30:02 +0300 Subject: [PATCH 17/23] fix "new Buffer" warning --- chart/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/base.js b/chart/base.js index acaf4eb..c2ef770 100644 --- a/chart/base.js +++ b/chart/base.js @@ -27,7 +27,7 @@ var Chart = Backbone.Model.extend ({ var me = this; var builder = new xml2js.Builder (); var xml = builder.buildObject (opts.object); - me.zip.file (opts.file, new Buffer (xml), {base64: true}); + me.zip.file (opts.file, Buffer.from (xml), {base64: true}); }, /* Get column name From e88917ce020d1078ad31dc4451e331549e54ca8d Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 21 Jul 2021 11:32:54 +0300 Subject: [PATCH 18/23] Add muli-layer chart support --- chart/base.js | 144 +++++++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 59 deletions(-) diff --git a/chart/base.js b/chart/base.js index c2ef770..919839a 100644 --- a/chart/base.js +++ b/chart/base.js @@ -213,54 +213,65 @@ var Chart = Backbone.Model.extend ({ /* Remove unused charts */ - removeUnusedCharts: function (o) { + removeUnusedCharts: function (o, chart) { var me = this; - if (me.tplName != "charts") { - return; - }; + + // axis ids that are used in chart layers var axId = []; function addId (o) { _.each (o ["c:axId"], function (o) { axId.push (o.$.val); }); }; - _.each (["line", "radar", "area", "scatter", "pie"], function (chart) { - if (!me.chartTypes [chart]) { + + const chartTypes = getChartTypes (chart); + + // remove chart layers that are not used in chart + _.each (["line", "radar", "area", "scatter", "pie", "doughnut"], function (chart) { + if (!chartTypes [chart]) { delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]; } else { addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]); }; }); - if (!me.chartTypes ["column"] && !me.chartTypes ["bar"]) { + + if (!chartTypes ["column"] && !chartTypes ["bar"]) { delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; - } else - if (me.chartTypes ["column"] && !me.chartTypes ["bar"]) { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]; - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); - } else - if (!me.chartTypes ["column"] && me.chartTypes ["bar"]) { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]; - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); } else { - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]); - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]); - }; + addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); + } - var catAx = []; - _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"], function (o) { - if (axId.indexOf (o ["c:axId"].$.val) > -1) { - catAx.push (o); - }; - }); - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"] = catAx; + if (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"]) { + var catAx = []; + // if category axes is single - it's an object, otherwise it's an array of objects + if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"].length) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"] = [o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"]]; + } - var valAx = []; - _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"], function (o) { - if (axId.indexOf (o ["c:axId"].$.val) > -1) { - valAx.push (o); - }; - }); - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"] = valAx; + // leave only category axes that are used in chart + _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"], function (o) { + if (axId.indexOf (o ["c:axId"].$.val) > -1) { + catAx.push (o); + }; + }); + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"] = catAx; + } + + if (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"]) { + var valAx = []; + // if value axes is single - it's an object, otherwise it's an array of objects + if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"].length) { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"] = [o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"]]; + } + + // leave only value axes that are used in chart + _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"], function (o) { + if (axId.indexOf (o ["c:axId"].$.val) > -1) { + valAx.push (o); + }; + }); + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"] = valAx; + } }, /* Write chart @@ -595,29 +606,16 @@ var Chart = Backbone.Model.extend ({ ser[chart] = ser[chart] || []; ser[chart].push (r); }); + _.each (ser, function (ser, chart) { - if (chart == "column") { - if (me.tplName == "charts") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]["c:ser"] = ser; - } else { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; - }; - } else if (chart == "bar") { - if (me.tplName == "charts") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]["c:ser"] = ser; - } else { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; - }; - } else if (chart == "line") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"] || o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; - delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; - delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:barDir"]; - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:grouping"] = {$: {val: "standard"}}; - if (me.tplName == "charts") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"][1]["c:ser"] = ser; - } else { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:lineChart"]["c:ser"] = ser; - }; + if (chart == "column" || chart == "bar") { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; + } else if (chart == "line" || chart == "area") { + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] = _.clone (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] || o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); + + delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:barDir"]; + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:grouping"] = {$: {val: "standard"}}; + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:ser"] = ser; } else { if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"][`c:${chart}Chart`]) { // minimal chart config @@ -745,7 +743,7 @@ var Chart = Backbone.Model.extend ({ }; } }); - me.removeUnusedCharts (o); + me.removeUnusedCharts (o, chartOpts); if (me.chartTitle) { me.writeTitle (o, me.chartTitle, chartOpts); @@ -824,12 +822,20 @@ var Chart = Backbone.Model.extend ({ */ setTemplateName: function (opts) { var me = this; - var chartTypes = {}; - _.each (me.data, function (o) { - chartTypes [o.chart || me.chart] = true; + var chartTypes = { + ...getChartTypes (me), + }; + + _.each (opts.charts, function (chart) { + chartTypes = { + ...chartTypes, + ...getChartTypes (chart), + }; }); - me.charts = [opts]; + me.charts = opts.charts || [opts]; + me.chartTypes = chartTypes; + if (chartTypes ["radar"]) { me.tplName = "radar"; return; @@ -901,6 +907,8 @@ var Chart = Backbone.Model.extend ({ opts.type = opts.type || "nodebuffer"; _.extend (me, opts); + + me.setTemplateName (opts); async.series ([ function (cb) { @@ -1108,3 +1116,21 @@ var Chart = Backbone.Model.extend ({ } }); module.exports = Chart; + +/** + * + * @param {Object} chart + * @returns {Object.} + */ +const getChartTypes = (chart) => { + const chartTypes = {}; + if (chart.chart) { + chartTypes[chart.chart] = true; + } + _.each (chart.data, function (series) { + if (series.chart) { + chartTypes[series.chart] = true; + } + }); + return chartTypes; +} From d80ba18e832a4c7c45c2f087534eeeddb2ee5985 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 21 Jul 2021 12:07:39 +0300 Subject: [PATCH 19/23] add support multi-layer chart with several layers of the same type --- chart/base.js | 118 +++++++++++++++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/chart/base.js b/chart/base.js index 919839a..61ccc47 100644 --- a/chart/base.js +++ b/chart/base.js @@ -5,6 +5,9 @@ var xml2js = require ("xml2js"); var VError = require ("verror"); var fs = require ("fs"); var async = require ("async"); + +const CHART_TYPES = ["bar", "column", "line", "radar", "area", "scatter", "pie", "doughnut"]; + var Chart = Backbone.Model.extend ({ /* Read XML file from xlsx as object @@ -213,34 +216,37 @@ var Chart = Backbone.Model.extend ({ /* Remove unused charts */ - removeUnusedCharts: function (o, chart) { + removeUnusedCharts: function (o) { var me = this; // axis ids that are used in chart layers var axId = []; function addId (o) { + if (!o ["c:axId"]) { + return; + } _.each (o ["c:axId"], function (o) { axId.push (o.$.val); }); }; - const chartTypes = getChartTypes (chart); - // remove chart layers that are not used in chart - _.each (["line", "radar", "area", "scatter", "pie", "doughnut"], function (chart) { - if (!chartTypes [chart]) { - delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]; + _.each (CHART_TYPES, function (chart) { + if (chart == "column") { + chart = "bar"; + } + + if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]) { + return; + } + + if (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"].length) { + _.each (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"], addId); } else { addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]); - }; + } }); - if (!chartTypes ["column"] && !chartTypes ["bar"]) { - delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]; - } else { - addId (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); - } - if (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"]) { var catAx = []; // if category axes is single - it's an object, otherwise it's an array of objects @@ -607,53 +613,52 @@ var Chart = Backbone.Model.extend ({ ser[chart].push (r); }); + const templateChartName = "c:" + CHART_TYPES.find ((chartType) => me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][`c:${chartType}Chart`]) + "Chart"; + + // remove template barChart from the XML object; + o ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName] = []; + _.each (ser, function (ser, chart) { - if (chart == "column" || chart == "bar") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser; - } else if (chart == "line" || chart == "area") { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] = _.clone (o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] || o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]); + if (chart == "column") { + chart = "bar"; + } + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] || []; + // minimal chart config + let newChart = {}; - delete o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:barDir"]; - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:grouping"] = {$: {val: "standard"}}; - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:ser"] = ser; + if (chart == "bar") { + // clone barChart from template + newChart = _.clone (me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName]); + + } else if (chart == "line" || chart == "area" || chart == "radar" || chart == "scatter") { + newChart = _.clone (me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName]); + delete newChart["c:barDir"]; + delete newChart["c:grouping"]; } else { - if (!o ["c:chartSpace"]["c:chart"]["c:plotArea"][`c:${chart}Chart`]) { - // minimal chart config - o ["c:chartSpace"]["c:chart"] = { - "c:autoTitleDeleted": { - $: { - val: 0, - }, - }, - "c:plotArea": { - [`c:${chart}Chart`]: { - "c:varyColors": { - $: { - val: 1, - }, - }, - }, - }, - }; - } // set series data - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:ser"] = ser; + newChart["c:varyColors"] = { + $: { + val: 1, + }, + }; + + newChart["c:ser"] = ser; if (chartOpts.firstSliceAng) { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:firstSliceAng"] = { + newChart["c:firstSliceAng"] = { $: { val: chartOpts.firstSliceAng, }, }; } if (chartOpts.holeSize) { - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:holeSize"] = { + newChart["c:holeSize"] = { $: { val: chartOpts.holeSize, }, }; } // if (chartOpts.showLabels) { - // o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:dLbls"] = { + // newChart["c:dLbls"] = { // "c:showLegendKey": { // $: { // val: 1, @@ -691,10 +696,13 @@ var Chart = Backbone.Model.extend ({ // }, // }; // } - // o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"]["c:grouping"] = { $: { val: 'standard' } }; + // newChart["c:grouping"] = { $: { val: 'standard' } }; }; + newChart["c:ser"] = ser; + o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"].push (newChart); + if (chartOpts.legendPos === undefined || chartOpts.legendPos) { o ["c:chartSpace"]["c:chart"]["c:legend"] = { "c:legendPos": { @@ -743,7 +751,7 @@ var Chart = Backbone.Model.extend ({ }; } }); - me.removeUnusedCharts (o, chartOpts); + me.removeUnusedCharts (o); if (me.chartTitle) { me.writeTitle (o, me.chartTitle, chartOpts); @@ -879,6 +887,16 @@ var Chart = Backbone.Model.extend ({ cb (); }); }, + function (cb) { + // save template chart so it could be easily removed before adding charts according to config + me.read ({file: "xl/charts/chart1.xml"}, function (err, o) { + if (err) { + return cb (new VError (err, "writeChart")); + } + me.chartTemplate = o; + cb (); + }); + }, function (cb) { me.writeStrings (cb); }, @@ -976,6 +994,16 @@ var Chart = Backbone.Model.extend ({ ); async.series ([ + function (cb) { + // save template chart so it could be easily removed before adding charts according to config + me.read ({file: "xl/charts/chart1.xml"}, function (err, o) { + if (err) { + return cb (new VError (err, "writeChart")); + } + me.chartTemplate = o; + cb (); + }); + }, function (cb) { me.writeChart (++n, row, cb); }, From 2b24ec761f01a9db0cba9ff2120504f1e7c37178 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 21 Jul 2021 12:15:27 +0300 Subject: [PATCH 20/23] add grouping option to be able to create staked charts --- chart/base.js | 86 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 19 deletions(-) diff --git a/chart/base.js b/chart/base.js index 61ccc47..2e0deac 100644 --- a/chart/base.js +++ b/chart/base.js @@ -6,6 +6,28 @@ var VError = require ("verror"); var fs = require ("fs"); var async = require ("async"); +const CHART_TAG_BY_CHART_NAME = { + bar: "c:barChart", + column: "c:barChart", + line: "c:lineChart", + radar: "c:radarChart", + area: "c:areaChart", + scatter: "c:scatterChart", + pie: "c:pieChart", + doughnut: "c:doughnutChart", +}; + +const CHART_GROUPING_BY_CHART_NAME = { + bar: "clustered", + column: "clustered", + line: "standard", + radar: undefined, // radar, scatter, pie and doughnut charts should not have "c:grouping" tag, otherwise rest of xml is ignored + area: "standard", + scatter: undefined, + pie: undefined, + doughnut: undefined, +}; + const CHART_TYPES = ["bar", "column", "line", "radar", "area", "scatter", "pie", "doughnut"]; var Chart = Backbone.Model.extend ({ @@ -416,10 +438,12 @@ var Chart = Backbone.Model.extend ({ if (err) { return cb (new VError (err, "writeChart")); } - var ser = {}; + var seriesByChart = {}; const chartOpts = me.charts[chartN - 1]; _.each (me.titles, function (t, i) { var chart = me.data[t].chart || me.chart; + var grouping = me.data[t].grouping || me.grouping || CHART_GROUPING_BY_CHART_NAME[chart]; + var customColorsPoints = { "c:dPt": [], }; @@ -522,7 +546,7 @@ var Chart = Backbone.Model.extend ({ } } - var r = { + var ser = { "c:idx": { $: { val: i @@ -596,11 +620,11 @@ var Chart = Backbone.Model.extend ({ } }; if (chart == "scatter") { - r["c:xVal"] = r["c:cat"]; - delete r["c:cat"]; - r["c:yVal"] = r["c:val"]; - delete r["c:val"]; - r["c:spPr"] = { + ser["c:xVal"] = ser["c:cat"]; + delete ser["c:cat"]; + ser["c:yVal"] = ser["c:val"]; + delete ser["c:val"]; + ser["c:spPr"] = { "a:ln": { $: { w: 28575 @@ -609,8 +633,9 @@ var Chart = Backbone.Model.extend ({ } }; }; - ser[chart] = ser[chart] || []; - ser[chart].push (r); + const seriesKey = `${chart}\r\r${grouping}`; + seriesByChart[seriesKey] = seriesByChart[seriesKey] || []; + seriesByChart[seriesKey].push (ser); }); const templateChartName = "c:" + CHART_TYPES.find ((chartType) => me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][`c:${chartType}Chart`]) + "Chart"; @@ -618,24 +643,33 @@ var Chart = Backbone.Model.extend ({ // remove template barChart from the XML object; o ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName] = []; - _.each (ser, function (ser, chart) { - if (chart == "column") { - chart = "bar"; + _.each (seriesByChart, function (ser, chart) { + + var [chart, grouping] = chart.split ("\r\r"); + + const chartTagName = CHART_TAG_BY_CHART_NAME[chart]; + + if (!chartTagName) { + return cb (new VError (new Error (`Chart type '${chart}' is not supported`), "writeChart")); } - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] = o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"] || []; + + o ["c:chartSpace"]["c:chart"]["c:plotArea"][chartTagName] = o ["c:chartSpace"]["c:chart"]["c:plotArea"][chartTagName] || []; // minimal chart config let newChart = {}; - if (chart == "bar") { + if (chart == "column" || chart == "bar") { // clone barChart from template newChart = _.clone (me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName]); + newChart["c:barDir"] = { + $: { + val: chart.substr (0, 3), + }, + }; } else if (chart == "line" || chart == "area" || chart == "radar" || chart == "scatter") { newChart = _.clone (me.chartTemplate ["c:chartSpace"]["c:chart"]["c:plotArea"][templateChartName]); delete newChart["c:barDir"]; - delete newChart["c:grouping"]; } else { - // set series data newChart["c:varyColors"] = { $: { val: 1, @@ -696,12 +730,26 @@ var Chart = Backbone.Model.extend ({ // }, // }; // } - // newChart["c:grouping"] = { $: { val: 'standard' } }; - }; newChart["c:ser"] = ser; - o ["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart + "Chart"].push (newChart); + + if (grouping != "undefined") { + newChart["c:grouping"] = { + $: { + val: grouping || CHART_GROUPING_BY_CHART_NAME[chart], + }, + }; + if (grouping == "stacked") { + newChart["c:overlap"] = { + $: { + val: 100, // usually stacked expected to be seen with overlap 100% + }, + }; + } + } + + o ["c:chartSpace"]["c:chart"]["c:plotArea"][chartTagName].push (newChart); if (chartOpts.legendPos === undefined || chartOpts.legendPos) { o ["c:chartSpace"]["c:chart"]["c:legend"] = { From 2044996d010c37885bf8340895c64b1d7776620b Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 20 Jul 2021 20:47:23 +0300 Subject: [PATCH 21/23] update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 231f7d8..976f33b 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,7 @@ var opts = { charts: [ { chart: "column", // pie, doughnut, line, area, bar + grouping: "clustered", // optional: stacked, standard - allow to create stacked bar or line chart titles: [ "title1", // list of chart titles "title2", // list of chart titles @@ -215,6 +216,8 @@ var opts = { "field2", ], data: { + // chart: "column", // could be used to create multi-layer charts with e.g. column + bar + line + area chart in the same object + // grouping: "clustered", // same as common grouping option, but only for current layer; "title1": { "field1": 123, // structured data "field2": 321, From 0eb22813543c1708031565d9c00c146a9e4b34d6 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 21 Jul 2021 12:49:52 +0300 Subject: [PATCH 22/23] Use "bar.xlsx" as default template "charts.xslx" template is absent in "templates" folder. Current implementation allow to use any template, that have axes, so bar is good enough --- chart/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/base.js b/chart/base.js index 2e0deac..afbc33c 100644 --- a/chart/base.js +++ b/chart/base.js @@ -908,7 +908,7 @@ var Chart = Backbone.Model.extend ({ me.tplName = _.keys (chartTypes)[0]; return; }; - me.tplName = "charts"; + me.tplName = "bar"; }, /* Generate XLSX with chart From 3b9270590ffa07222873502317c8bd2990b87a1a Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 29 Jul 2021 17:17:01 +0300 Subject: [PATCH 23/23] fix deleteEmptyCells for zero values case before fix zero values also set to empty string, same as values not passed at all --- chart/base.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/chart/base.js b/chart/base.js index afbc33c..52d0aaf 100644 --- a/chart/base.js +++ b/chart/base.js @@ -30,6 +30,22 @@ const CHART_GROUPING_BY_CHART_NAME = { const CHART_TYPES = ["bar", "column", "line", "radar", "area", "scatter", "pie", "doughnut"]; +/** + * + * @param {number | null | undefined} value + * @param {{ deleteEmptyCells?: boolean }} opts + * @returns + */ +function normalizeValue (value, opts) { + if (!opts.deleteEmptyCells) { + return value || 0; + } + if (value || value === 0) { + return value; + } + return ""; +} + var Chart = Backbone.Model.extend ({ /* Read XML file from xlsx as object @@ -952,7 +968,8 @@ var Chart = Backbone.Model.extend ({ _.each (me.titles, function (t) { me.data[t] = me.data[t] || {}; _.each (me.fields, function (f) { - me.data[t][f] = me.data[t][f] || (me.deleteEmptyCells ? "" : 0); //deleteEmptyCells - don't display missing values as 0 + const value = normalizeValue (me.data[t][f], me); + me.data[t][f] = value; }); }); me.writeTable (cb); @@ -1008,11 +1025,14 @@ var Chart = Backbone.Model.extend ({ async.eachSeries (me.charts, (chart, cb) => { ["chart", "titles", "fields", "data", "chartTitle"].forEach (a => me[a] = chart[a]); + const deleteEmptyCells = me.deleteEmptyCells || chart.deleteEmptyCells; + _.each (me.titles, function (t) { me.data[t] = me.data[t] || {}; _.each (me.fields, function (f) { - me.data[t][f] = me.data[t][f] || 0; + const value = normalizeValue (me.data[t][f], {deleteEmptyCells}); + me.data[t][f] = value; }); }); me.writeMultTable (row, () => {