Nodes/ComfyUI CV/CV Chart Series
ComfyUI Node

CV Chart Series

One score per detected face? CV Chart Series is the chart the numbers always needed

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
CV Chart Series
  • values
  • spec
◄chartbar►
◄title►
◄x_labels►
◄series_names►
◄reference_lines►
◄color_byseries color►
◄orientationvertical (bars go up)►
◄show_valuesfalse►
◄value_rangestart at zero►
◄min_value0.00►
◄max_value1.00►
◄x_axis_name►
◄y_axis_name►
◄palettecolorblind-safe (Okabe-Ito)►
◄gradient_paletteviridis►
◄themedark►
◄decimals3►
◄chart_height320►

What this is for

Half the arrays in a vision workflow aren't pictures at all - they're a measurement per thing. One similarity score per detected face, a histogram, a metric per method, a score against a parameter sweep. Preview CV Array renders those as a few anonymous pixels and min-max normalization deletes the absolute scale, which is exactly the part you cared about: is this score above the 0.363 threshold your face-recognition model uses, or not?

CV Chart Series draws them as labelled bars (or lines, areas, steps, scatter) with the values readable, in the node. Hover gives exact numbers, box-zoom works, and the toolbar saves a PNG - so the picture you paste into a write-up carries the numbers, not just the shape.

How it works

The full Apache ECharts option object is built in Python - palettes, legend, reference lines, value formatting - and passed to web/cv_charts.js, which does nothing but instantiate it. All the chart logic is server-side and comes back out as the spec STRING, so you can read and diff the plot as JSON. Values are capped at 200,000 points; past that the node tells you you're charting an image by mistake rather than freezing the tab.

The inputs that matter

values is required. A 1-D array is one series of N samples; a 2-D array is samples × series, one column per series. Both (N, 1) and (1, N) count as a single series of N samples, so you don't have to reshape before feeding it.

chart is the second required input and the one that decides honesty of read: bar for independent measurements (one per object, one per method), line/area/step when consecutive samples form a signal (a histogram, score versus a swept parameter), scatter when samples have no order at all.

From the optional set, the three worth wiring:

  • x_labels - name per sample, ;/newline/comma or JSON. Missing names fall back to #0, #1, … Feed the same string that labels the corresponding image tiles and bar i provably refers to tile i. That is a real difference from eyeballing a legend.
  • reference_lines - value or value=label, separated like the label lists. 0.363=SFace threshold paints your decision threshold into the picture instead of leaving it in a widget, and color_by → above/below first reference line then splits every mark into pass and fail colours with a legend saying which is which. That's the whole point: a chart where you can see the threshold crossing without hovering anything.
  • orientation - horizontal when the sample names are file names, person names or anything else longer than three characters. Vertical bars with rotated labels are where charts go to die.

series_names names the columns for the legend. color_by also takes series color (default) or value (gradient) with gradient_palette. value_range defaults to start at zero, which is correct for bars - auto fits the data and exaggerates small differences, manual (with min_value/max_value) is what makes two runs comparable. show_values prints each value next to its mark so the chart survives being saved as a PNG. Plus x_axis_name/y_axis_name (y wants units), palette, theme, decimals, chart_height.

Output is spec. The node is an output node, so it draws on Queue; Preview as Text reads the charted values.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
# restart ComfyUI

Or search ComfyUI CV in ComfyUI Manager (registry publisher bmad4ever). Python ≥ 3.12 and a V3-node-API ComfyUI; opencv-contrib-python-headless~=5.0.0.93 comes from the pack's requirements. ECharts is vendored - no model or library downloads for this node.

Common issues

  • Labels one short. Not an error. Missing names get numbered placeholders by design; a chart with a short label list is still a readable chart, and the author decided a label typo shouldn't kill a run.
  • Bars all look identical, or wildly different across runs. Check value_range. auto rescales to whatever is in front of it, so small differences shout. Pin it.
  • Nothing appears in the node. Reload the page - the chart is rendered by a frontend module, and a tab that was already open when you installed won't have it.
  • Contrib-backed nodes vanished pack-wide. A non-contrib OpenCV wheel clobbered the shared site-packages/cv2. python tools/repair_opencv_contrib.py --check then --apply.
Categoryimage/CV/plots

Inputs (19)

NameTypeDefaultDescription
valuesNPARRAY1-D array (one series), or 2-D (samples x series) with one COLUMN per series. (N, 1) and (1, N) both count as one series of N samples.
chartCOMBObarMark type. 'bar' for independent measurements (one per detected object/method); 'line'/'area'/'step' when consecutive samples form a signal or a curve (histogram, score vs parameter); 'scatter' to show samples without implying continuity.
titleSTRINGHeading drawn above the chart. Empty draws none.
x_labelsoptSTRINGName per sample, as a JSON list or ';' / newline / ',' separated text (e.g. 'face 0;face 1;face 2'). Missing names fall back to '#0', '#1', ... - a short list never breaks the chart. Feed the same string that labels the corresponding images.
series_namesoptSTRINGName per series (per COLUMN of values), same formats as x_labels. Shown in the legend and the tooltip; defaults to 'series 0', 'series 1', ...
reference_linesoptSTRINGHorizontal marker lines: 'value' or 'value=label', separated like the label lists - e.g. '0.363=SFace threshold'. This is how a decision threshold becomes visible in the picture instead of living only in a widget.
color_byoptCOMBOseries color'series color' gives each series one color. 'value (gradient)' colors every mark by its own value and adds a labelled scale. 'above/below first reference line' splits the marks into two colors at the threshold - the pass/fail read, with the legend saying which side is which.
orientationoptCOMBOvertical (bars go up)Horizontal keeps long sample names readable - the right choice when the labels are file or person names rather than indices.
show_valuesoptBOOLEANfalsePrint each value next to its mark, so the chart reads without hovering (and survives being saved as a PNG).
value_rangeoptCOMBOstart at zero'auto' fits the values, which EXAGGERATES small differences; 'start at zero' keeps bar lengths proportional to the values; 'manual' pins the axis so separate runs are comparable.
min_valueoptFLOAT0.00-1000000000–1000000000Value-axis minimum ('manual' range only).
max_valueoptFLOAT1.00-1000000000–1000000000Value-axis maximum ('manual' range only).
x_axis_nameoptSTRINGCaption under the sample axis (what the samples ARE: 'detected face', 'bin', 'method').
y_axis_nameoptSTRINGCaption beside the value axis, with units ('cosine similarity', 'pixels', 'dB').
paletteoptCOMBOcolorblind-safe (Okabe-Ito)Series colors. The default stays readable for a colour-blind reader and in greyscale; 'bright' is punchier but puts a red and a green next to each other; the sampled ramps suit ORDERED series (sweep steps).
gradient_paletteoptCOMBOviridisRamp used by the 'value (gradient)' coloring; 'viridis' matches 'Preview CV Array' heatmaps.
themeoptCOMBOdarkChart colors. 'light' is for a PNG that will be pasted into a document.
decimalsoptINT30–8Decimals shown in the tooltips and the printed values. Trailing zeros are dropped.
chart_heightoptINT320160–2048Height of the chart area inside the node, in pixels.

Outputs (1)

NameTypeDescription
specSTRINGThe chart definition as JSON - the exact option the frontend renders. Wire into 'Preview as Text' to read the values that were charted.