pptxjavascriptverified
pptx combo chart skill
pptxgenjs helper addComboChartSlide(pres, options): column plus line series on a secondary value axis with both valAxes and catAxes declared so PowerPoint keeps the chart.
rec_8c4e82c887c54fa18776571a9c6df034 · banked 2026-09-04 · by neruva
Certificate
A program checked this on cases it had never seen. You can run it.
Model alone
13%
With this skill
100%
Near-misses it rejects
6/6
Signed
ed25519
What was checked
- Output built to the specification is accepted.
- Each of these deliberate breaks is rejected:
unbold_header,wrong_header_fill,rect_wrong_width,rect_wrong_color,wrong_header_text,row_value_changed. - An empty file is rejected.
What was not
- Anything a person would call taste: layout, tone, whether it looks good. No program can check that, and this one does not claim to.
- Behaviour outside the specification's clauses.
- Inputs the checker was never given. See the evidence line for the corpus.
Scorecard
A slide holding one native combo chart: a column series plus a line series on a secondary axis.
The model on its own
13%16 unseen cases
The same model with this skill
100%deepseek-chat
A frontier model on its own
100%same cases
Cost per task with the skill
0.317 cents14.7x cheaper than the frontier model
Smallest model measured
Qwen3-0.6B, on a laptop, no API16/16
Cost to build it once
$1.98
Checked by
hand-written, opens the deck and reads the chart XML back
Where it does not apply
- Chart types beyond column plus line are untested.
- PowerPoint silently discards a chart whose axes are not declared, which is the failure this rung exists to prevent.
Every figure comes from one run, kept in the repository as probe6b_pptx_combo_result.json.
Evidence
- 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
Use it
# in Claude Code (MCP tools from neruva-mcp)
rung_search(q="pptx_combo_chart_rung")
rung_install(id="rec_8c4e82c887c54fa18776571a9c6df034", dir="~/.claude/skills")
# the skill folder is now loaded like any other skillUsage guide
What the model reads to call the skill. This is the whole interface.
Show the guide
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 });
Code
102 lines of javascript, hashed and signed below.
Show the code
function addComboChartSlide(pres, options) {
if (!pres) throw new Error('addComboChartSlide: pres (the pptxgenjs instance) is required as the first argument');
const {
x = 0.5,
y = 0.5,
w = 9,
h = 4.5,
chartTitle,
categories,
barSeriesName,
barValues,
lineSeriesName,
lineValues,
barColor = '1E2761',
lineColor = 'F96167',
showBarDataLabels = true,
barDataLabelColor = '363636',
barDataLabelFontSize = 10,
showLegend = true,
legendPos = 'b',
catAxisTitle = '',
valAxisTitle = '',
valAxis2Title = '',
axisLabelColor = '363636'
} = options || {};
if (!chartTitle) throw new Error('addComboChartSlide: chartTitle is required');
if (!Array.isArray(categories) || categories.length === 0) throw new Error('addComboChartSlide: categories must be a non-empty array');
if (!barSeriesName || !Array.isArray(barValues)) throw new Error('addComboChartSlide: barSeriesName and barValues (array) are required');
if (!lineSeriesName || !Array.isArray(lineValues)) throw new Error('addComboChartSlide: lineSeriesName and lineValues (array) are required');
if (barValues.length !== categories.length || lineValues.length !== categories.length) {
throw new Error('addComboChartSlide: barValues and lineValues must each have the same length as categories');
}
const slide = pres.addSlide();
const barData = [{
name: barSeriesName,
labels: categories,
values: barValues
}];
const lineData = [{
name: lineSeriesName,
labels: categories,
values: lineValues
}];
slide.addChart(
[
{
type: pres.charts.BAR,
data: barData,
options: {
chartColors: [barColor],
showValue: showBarDataLabels,
dataLabelPosition: 'outEnd',
dataLabelColor: barDataLabelColor,
dataLabelFontSize: barDataLabelFontSize
}
},
{
type: pres.charts.LINE,
data: lineData,
options: {
chartColors: [lineColor],
secondaryValAxis: true,
secondaryCatAxis: true,
lineSize: 2,
lineDataSymbol: 'circle',
showValue: false
}
}
],
{
x, y, w, h,
showTitle: true,
title: chartTitle,
showLegend: showLegend,
legendPos: legendPos,
catAxes: [
{ catAxisTitle: catAxisTitle, showCatAxisTitle: !!catAxisTitle, catAxisLabelColor: axisLabelColor },
{ catAxisHidden: true }
],
valAxes: [
{
showValAxisTitle: !!valAxisTitle,
valAxisTitle: valAxisTitle,
valAxisLabelColor: axisLabelColor
},
{
showValAxisTitle: !!valAxis2Title,
valAxisTitle: valAxis2Title,
valAxisLabelColor: axisLabelColor,
valGridLine: { style: 'none' }
}
]
}
);
return slide;
}Certificate
Code sha256 4b2b3ddda8a79a6fdd736cf4495da0aab0e6a8af02b29c006b9ed81b93ea6fa6
Signature ed25519, present
Last re-checked
passing
Re-checked today. The signature was recomputed from the code served by the API, and the certificate matched.
A certificate proves the code has not changed. Re-running the check proves it still works today, on today's libraries.