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
SKILL.md
Certificate

A program checked this on cases it had never seen. You can run it.

Download the checker
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 skill

Usage 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.