--- name: pptx_combo_chart_rung description: "Add a slide to your slideshow that shows bars and a line together on the same chart, using two different scales, so you can compare two kinds of numbers side by side without the file breaking when opened in PowerPoint. pptx_combo_chart_rung: pptxgenjs helper addComboChartSlide(pres, options): column" --- # pptx_combo_chart_rung (a Neruva verified skill for `pptx`) Add a slide to your slideshow that shows bars and a line together on the same chart, using two different scales, so you can compare two kinds of numbers side by side without the file breaking when opened in PowerPoint. Verified helper code for `pptx`, javascript. Entry points: `addComboChartSlide`. Code hash sha256 `4b2b3ddda8a79a6fdd736cf4495da0aab0e6a8af02b29c006b9ed81b93ea6fa6`, signed (ed25519). Banked by `legacy-dev`. ## One call Call `build(**args)` with an object matching this schema. You do not have to write code or match the helper signatures below. ```json { "type": "object", "required": [ "outputPath", "chartTitle", "categories", "barSeriesName", "barValues", "lineSeriesName", "lineValues" ], "properties": { "outputPath": { "type": "string", "description": "Filesystem path (including filename, e.g. 'output.pptx') where the .pptx file will be written. Parent directories will be created automatically if missing and possible; if the directory cannot be created or is not writable, build throws a descriptive error. Relative paths are resolved against the current working directory." }, "layout": { "type": "string", "description": "pptxgenjs slide layout to set on the presentation before adding the chart slide, e.g. 'LAYOUT_16x9' or 'LAYOUT_4x3'. Defaults to 'LAYOUT_16x9'.", "default": "LAYOUT_16x9" }, "x": { "type": "number", "description": "Chart left position in inches. Optional; helper default is 0.5." }, "y": { "type": "number", "description": "Chart top position in inches. Optional; helper default is 0.5." }, "w": { "type": "number", "description": "Chart width in inches. Optional; helper default is 9." }, "h": { "type": "number", "description": "Chart height in inches. Optional; helper default is 4.5." }, "chartTitle": { "type": "string", "description": "REQUIRED. Exact text shown as the chart's title." }, "categories": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "REQUIRED, non-empty array of shared category-axis labels for both series, e.g. ['Jan','Feb','Mar']." }, "barSeriesName": { "type": "string", "description": "REQUIRED. Legend name of the column (bar) series." }, "barValues": { "type": "array", "items": { "type": "number" }, "description": "REQUIRED array of numbers for the column series; length MUST equal categories.length." }, "lineSeriesName": { "type": "string", "description": "REQUIRED. Legend name of the line series (plotted on the secondary value axis)." }, "lineValues": { "type": "array", "items": { "type": "number" }, "description": "REQUIRED array of numbers for the line series; length MUST equal categories.length." }, "barColor": { "type": "string", "description": "Hex color string without '#' for the column fill color. Optional; helper default '1E2761'." }, "lineColor": { "type": "string", "description": "Hex color string without '#' for the line color. Optional; helper default 'F96167'." }, "showBarDataLabels": { "type": "boolean", "description": "Whether to show numeric data labels on the column series. Optional; helper default true." }, "barDataLabelColor": { "type": "string", "description": "Hex color string for the bar data label text. Optional; helper default '363636'." }, "barDataLabelFontSize": { "type": "number", "description": "Font size of the bar data labels. Optional; helper default 10." }, "showLegend": { "type": "boolean", "description": "Whether to show the chart legend. Optional; helper default true." }, "legendPos": { "type": "string", "description": "Legend position: 'b' (bottom), 't' (top), 'l' (left), or 'r' (right). Optional; helper default 'b'." }, "catAxisTitle": { "type": "string", "description": "Title for the primary (visible) category axis; shown only if non-empty. Optional; default ''." }, "valAxisTitle": { "type": "string", "description": "Title for the primary (left) value axis; shown only if non-empty. Optional; default ''." }, "valAxis2Title": { "type": "string", "description": "Title for the secondary (right) value axis; shown only if non-empty. Optional; default ''." }, "axisLabelColor": { "type": "string", "description": "Hex color string for tick-label color applied to both value axes and the primary category axis. Optional; helper default '363636'." } } } ``` Example arguments: ```json { "outputPath": "./output/warehouse_chart.pptx", "layout": "LAYOUT_16x9", "chartTitle": "Warehouse Throughput chart", "categories": [ "Jan", "Feb", "Mar", "Apr", "May" ], "barSeriesName": "Units", "barValues": [ 207, 195, 127, 182, 487 ], "lineSeriesName": "Rate", "lineValues": [ 4.4, 3.1, 2.1, 7.3, 6.5 ], "catAxisTitle": "Month", "valAxisTitle": "Units", "valAxis2Title": "Rate" } ``` ```javascript function build(args) { const pptxgen = require('pptxgenjs'); const path = require('path'); const fs = require('fs'); if (!args || typeof args !== 'object') throw new Error('build: args object is required'); const { outputPath, layout = 'LAYOUT_16x9', x, y, w, h, chartTitle, categories, barSeriesName, barValues, lineSeriesName, lineValues, barColor, lineColor, showBarDataLabels, barDataLabelColor, barDataLabelFontSize, showLegend, legendPos, catAxisTitle, valAxisTitle, valAxis2Title, axisLabelColor } = args; if (!outputPath) throw new Error('build: outputPath is required'); const pres = new pptxgen(); pres.layout = layout; const options = { chartTitle, categories, barSeriesName, barValues, lineSeriesName, lineValues }; if (x !== undefined) options.x = x; if (y !== undefined) options.y = y; if (w !== undefined) options.w = w; if (h !== undefined) options.h = h; if (barColor !== undefined) options.barColor = barColor; if (lineColor !== undefined) options.lineColor = lineColor; if (showBarDataLabels !== undefined) options.showBarDataLabels = showBarDataLabels; if (barDataLabelColor !== undefined) options.barDataLabelColor = barDataLabelColor; if (barDataLabelFontSize !== undefined) options.barDataLabelFontSize = barDataLabelFontSize; if (showLegend !== undefined) options.showLegend = showLegend; if (legendPos !== undefined) options.legendPos = legendPos; if (catAxisTitle !== undefined) options.catAxisTitle = catAxisTitle; if (valAxisTitle !== undefined) options.valAxisTitle = valAxisTitle; if (valAxis2Title !== undefined) options.valAxis2Title = valAxis2Title; if (axisLabelColor !== undefined) options.axisLabelColor = axisLabelColor; addComboChartSlide(pres, options); const resolvedPath = path.resolve(outputPath); const dir = path.dirname(resolvedPath); try { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } catch (err) { throw new Error('build: could not create output directory \'' + dir + '\': ' + err.message); } try { fs.accessSync(dir, fs.constants.W_OK); } catch (err) { throw new Error('build: output directory \'' + dir + '\' is not writable: ' + err.message); } return pres.writeFile({ fileName: resolvedPath }).then(() => resolvedPath); } ``` ## How to use it HELPER: addComboChartSlide(pres, options) Purpose: Adds one new slide to an existing pptxgenjs presentation containing ONE native combo chart (column series + line series on a secondary value axis), with the chart title shown and data labels on the column series. Declares both a primary and secondary value axis AND both a primary and secondary category axis, which PowerPoint requires whenever any series uses secondaryValAxis/secondaryCatAxis — omitting either causes PowerPoint to treat the file as corrupt. IMPORTANT — you must create the pptxgenjs instance yourself BEFORE calling this helper, and pass it as the first argument. The helper never creates or references a global `pres`; it only uses the `pres` object you pass in (this includes reading `pres.charts.BAR` / `pres.charts.LINE` off of it). A driver script must look like: const pres = new pptxgen(); pres.layout = 'LAYOUT_16x9'; addComboChartSlide(pres, { ...options... }); pres.writeFile({ fileName: process.env.OUTPUT }); Parameters (options is a single object): - x (number, optional, default 0.5): chart left position in inches. - y (number, optional, default 0.5): chart top position in inches. - w (number, optional, default 9): chart width in inches. - h (number, optional, default 4.5): chart height in inches. - chartTitle (string, REQUIRED): exact text shown as the chart's title (showTitle is forced true). - categories (array of string, REQUIRED, non-empty): shared category-axis labels for both series, e.g. ['Jan','Feb','Mar']. - barSeriesName (string, REQUIRED): legend name of the column series. - barValues (array of number, REQUIRED): values for the column series; length MUST equal categories.length. - lineSeriesName (string, REQUIRED): legend name of the line series (plotted on the secondary value axis). - lineValues (array of number, REQUIRED): values for the line series; length MUST equal categories.length. - barColor (hex string w/o '#', optional, default '1E2761'): column fill color. - lineColor (hex string w/o '#', optional, default 'F96167'): line color. - showBarDataLabels (boolean, optional, default true): show numeric data labels on the column series (outside the bar end). - barDataLabelColor (hex string, optional, default '363636'): color of the bar data labels. - barDataLabelFontSize (number, optional, default 10): font size of the bar data labels. - showLegend (boolean, optional, default true): show/hide the chart legend. - legendPos (string, optional, default 'b'): legend position ('b','t','l','r'). - catAxisTitle (string, optional, default ''): title for the primary (visible) category axis; shown only if non-empty. - valAxisTitle (string, optional, default ''): title for the primary (left) value axis; shown only if non-empty. - valAxis2Title (string, optional, default ''): title for the secondary (right) value axis; shown only if non-empty. - axisLabelColor (hex string, optional, default '363636'): tick-label color applied to both value axes and the primary category axis. Returns: the pptxgenjs Slide object that was created and now holds the chart (useful if you want to add more shapes/notes to the same slide afterward). Behavior notes: - Internally builds one combo chart via slide.addChart([barSeriesDef, lineSeriesDef], mainOptions) — this is the ONLY chart on the slide. - The line series always has secondaryValAxis: true and secondaryCatAxis: true set on it. - mainOptions always declares catAxes as a 2-element array (primary + secondary/hidden) and valAxes as a 2-element array (primary + secondary), which is mandatory once any series uses a secondary axis. - Throws a descriptive Error (before touching pptxgenjs) if chartTitle, categories, barSeriesName/barValues, or lineSeriesName/lineValues are missing, or if the value arrays' lengths don't match categories' length. Example call (driver code): const pres = new pptxgen(); pres.layout = 'LAYOUT_16x9'; addComboChartSlide(pres, { chartTitle: 'Warehouse Throughput chart', categories: ['Jan','Feb','Mar','Apr','May'], barSeriesName: 'Units', barValues: [207,195,127,182,487], lineSeriesName: 'Rate', lineValues: [4.4,3.1,2.1,7.3,6.5] }); pres.writeFile({ fileName: process.env.OUTPUT }); ## Evidence (corpus named) - held-out 16 combo decks: deepseek-chat cold 0.125 -> 1.000 with rung (CI +0.750..+1.000), 0.32c/deck vs Sonnet 4.66c; forged by Sonnet in 2 rounds for $1.98 ## Files - `scripts/skill.js`: the verified code (GET /v1/commons/rungs/rec_8c4e82c887c54fa18776571a9c6df034/code) - verify: POST /v1/commons/verify {"id": "rec_8c4e82c887c54fa18776571a9c6df034"}