Reading results#
A run produces signals. This page covers the blocks that show them, the chart window, the values the command window keeps, and what a run's messages mean.
The blocks that show a signal#
Sinks are the blocks that consume a signal rather than producing one. The ones you will reach for most:
| Block | What it gives you |
|---|---|
| Scope | A plot against time, filling in as the run advances. The usual way to watch a signal. |
| Floating Scope | A scope with no input link — it is pointed at a signal instead, which is useful for a quick look without redrawing anything. |
| Display | The current value as a number, rather than a curve. |
| XY Graph | One signal against another instead of against time. |
| Signal Recorder | Saves what it receives as a time series in the variables space, so the run's numbers can be worked with in the command window afterwards. |
| To File | Writes the signal to a CSV file on disk, for use elsewhere. |
| Record | A scope that also keeps what it plots: the traces are drawn live, and the same samples are saved as time series in the variables space. |
| Stop Simulation | Ends the run when its input says so, rather than at the stop time. |
| Terminator | Deliberately discards a signal, for an output you do not want. |
Each of these has its own page under Block catalog — every library block, with a sample of it running.
The chart window#
A scope's plot can be opened as a chart of its own, and the chart can be
exported: its Export Chart button writes PNG, JPEG, PDF, SVG or EPS, and
from the command window a chart handle's exportDataCSV(path) writes the
plotted numbers in CSV form, when what you want is the data rather than the
picture.
While a run is going, the chart keeps refitting its view to follow the new samples. After you zoom or pan (the wheel, a left-drag box, a middle-drag, or the zoom buttons), it stops refitting and leaves the view where you put it. New samples are still drawn. Reset Zoom, or a middle-button double click, fits the view to the data again and turns following back on. Starting the next run does the same.
A scope also keeps faded traces of the runs before the current one, so you can see what an edit to the model changed. The run just before is drawn at 25% of its colour, older runs fainter, and all of them under the current run. They are not data: they are not in the legend or the line list, not in the CSV export, and not part of what the view fits. Reset Zoom and the refit while a run is going always frame the current run, whether the old ones are shown or not. The Chart menu in the chart window's title bar sets them:
| Menu row | What it does |
|---|---|
| Show Previous Runs | A switch that shows or hides the faded traces. Hidden runs are still recorded, so turning this back on shows the runs made meanwhile. On by default. |
| Previous Runs Kept | How many earlier runs are kept, 1 to 10. 3 by default. |
| Previous Runs: Clear | Forgets the runs kept so far. |
The same menu's Buffer Size (Sample) field sets how many samples the plot keeps. It takes a positive whole number, applied when you press Return, leave the field or close the menu, and refuses anything else. The axis titles are typed into the X Axis Label and Y Axis Label fields at the top of the X Label and Y Label menus, and the plot follows as you type — the titles on the plot itself cannot be typed into.
X Label, Y Label and Chart work as a menu bar: click one to open it and again to close it, and while one is open, moving the pointer onto another opens that one instead.
Both settings are saved with the scope. The traces themselves are not: a project opens with none. An undo or redo rebuilds the diagram, which clears a scope's plot, and its previous runs go with it.
You can also plot without involving the diagram at all. plot() in the command
window opens a chart of its own — it does not touch the block diagram.
plot speaks MATLAB#
plot takes the arguments MATLAB's plot takes, and reads them the same way:
| You write | You get |
|---|---|
plot(y) | one line per column of y, against the row index 1..n |
plot(x, y) | y against x |
plot(x, y, 'r--o') | with a line spec: colour, line style and marker |
plot(x1, y1, x2, y2) | several pairs on one chart, each with its own spec |
plot(..., 'LineWidth', 2) | trailing 'Name', value pairs, applied to every line |
plot(s) | a recorded time series against its own time base |
A line spec is one colour (r g b c m y k w), one line style (- -- :
-.) and one marker (o s d x + * .), in any order, in one quoted word. A
marker with no line style draws the markers alone, which is MATLAB's rule too.
The five properties this chart carries are LineWidth, LineStyle, Color,
Marker and MarkerSize.
semilogx, semilogy and loglog are the same call with one or both axes on
a base-10 logarithmic scale.
A chart handle lets you look at what was drawn — c.path(i) binds line i and
.info prints its point count and bounds, which is how you check that a matrix
was read the way you meant:
$ ICoreBlocks --console "c = plot([1 2; 3 4; 5 6]); p = c.path(1); p.info"
p: name='', points=3, bounds=[x: 1..3, y: 2..6]
That is the second column — [2 4 6] — against 1..3, and there is a
first line beside it. A vector plots the same way:
$ ICoreBlocks --console "c = plot(0:0.5:2, [0 0.5 1 1.5 2].^2, 'r--o'); p = c.path(0); p.info"
p: name='', points=5, bounds=[x: 0..2, y: 0..4]
Three things differ from MATLAB, and each says so when you hit it:
Four marker shapes, not twelve: circle, square, diamond and cross (
x,+and*all draw the cross;.is a small circle). MATLAB's^ v > < p hare refused by name rather than rounded to the nearest shape you did not ask for.``
$ ICoreBlocks --console "plot([1 2 3], 'Marker', '^')" plot(): Marker must be one of o s d x + * . or 'none' (this chart draws four shapes), got '^'``MarkerSizeis in pixels, where MATLAB's is in points — this chart has no point size to give it.plot([])is refused rather than opening empty axes. At a prompt a blank window is a puzzle and a sentence is not.
c.holdOn(m) adds another matrix to an existing chart, and it reads a matrix
the chart's way — column 0 is x, every later column a line, which is the shape
a recorded signal lands in. plot deliberately no longer does; that is the one
place the two disagree, and it is why a series still plots against its own time.
The current figure#
plot draws into the current figure, the way MATLAB's does: the first draw
opens a window ("Figure 1"), and every draw after it replaces what that window
holds unless hold on is set. figure opens the next window and makes it
current; close ends the current one (close all ends every one); clf
empties it and keeps the window. The verbs that dress a figure act on the
current one and take both of MATLAB's spellings — the call, xlabel("t"), and
the command syntax, hold on — and each is also a member on a chart handle,
c.title("x").
$ ICoreBlocks --console "f = figure, plot([1 2 3]), hold on, plot([3 2 1]), c = gcf, c.path(1), hold off, plot([1 2 3]), c.path(1)"
f = Figure 1
1 line(s) plotted
hold on
1 line(s) plotted onto the current chart
c = Figure 1
line path c[1]
hold off
chart redrawn, 1 line(s) plotted
getLinePathByIndex(): c has no line path at index 1
A hold-off draw resets what MATLAB's newplot resets: the lines, any placed
text, the title, the limits and the log scales. A hold-on draw keeps them, and
re-applies a limit set by hand:
$ ICoreBlocks --console "plot([1 2 3], [10 20 30]), xlim, xlim([0 5]), hold on, plot([1 2 3], [15 15 15]), xlim, axis tight, axis"
chart created, 1 line(s) plotted
[1, 3]
xlim set to [0, 5]
hold on
1 line(s) plotted onto the current chart
[0, 5]
axis tight
[1, 3, 10, 30]
The furniture, with what each answers:
| You write | You get |
|---|---|
xlabel(t) ylabel(t) title(t) | the label or title set; a hold-off draw clears the title |
xlim([a b]) ylim([a b]) axis([x0 x1 y0 y1]) | the range set by hand; bare xlim / axis print the current one |
xlim auto axis auto | back to autoscaling (which pads y by 20%, x not at all) |
axis tight xlim tight | ranges fitted to the data |
axis equal | one data unit the same number of pixels on both rulers |
axis off / axis on | rulers hidden or shown |
grid on / grid off / grid minor | the grid, which this chart draws by default |
legend("a", "b") / legend / legend off | lines named in drawing order; unnamed lines are data1, data2, … |
text(x, y, str) | a string placed at the data point, following zoom and pan |
subplot(m, n, p) / subplot(mnp) | the window tiled m by n; tile p (row by row) is the current axes |
tiledlayout(m, n) / nexttile | the same grid, filled in order |
gcf / gca | the current figure / current axes, bindable: a = gca |
saveas(gcf, path) exportgraphics(gcf, path) print("-dpng", path) | the figure written to a file, png/jpg/bmp/pdf/svg by extension |
$ ICoreBlocks --console "subplot(2, 1, 1), bar([1 2 3]), title(\"bars\"), a = subplot(2, 1, 2), stem([1 3 2]), a.path(1), saveas(gcf, \"fig.png\")"
Figure 1 tile 1 of 2 x 1
1 line(s) plotted
title set to 'bars'
a = Figure 1 tile 2 of 2 x 1
2 line(s) plotted
line path a[1]
Figure 1 written to fig.png
Two things differ from MATLAB here, and each says so at the prompt: axis
square, axis image and their kin shape the plot box, which on this chart
follows its window, so they are refused by name (axis equal and axis tight
are the two shapes it has); and legend's 'Location' is refused, because the
legend sits where the toolkit places it. annotation is refused too — it places
in figure-normalised coordinates this chart does not expose — and text is the
form it has.
The model response verbs draw there too#
step, impulse, ramp, pzmap, polezero, rootlocus and the console's
four-argument bode/nyquist sweeps address the current figure like every
other verb on this page — so hold on adds a response to what the axes already
hold, a second call redraws rather than opening a second window, and close,
clf, title and xlim all reach them. They opened a window of their own
until 2026-09-06, and the difference a reader notices is the plot furniture:
$ ICoreBlocks --console "G = tf([1 2],[1 3 2]); pzmap(G); sgrid; c = gca; p = c.path(0); p.info"
p: name='Poles (2)', points=2, bounds=[x: -2..-1, y: 0..0]
sgrid and zgrid draw constant-damping and constant-frequency loci over
whatever the current axes already hold, which is what MATLAB's do. Before
the change the map went to a window and the grid to the figure, so the two
never met; now pzmap(G); sgrid grids the map, and c.path(2) onward are the
loci that were added to it.
The other chart kinds#
bar, barh, stem, stairs, scatter, histogram, area, errorbar and
fill take MATLAB's arguments — one data group, a line spec, 'Name', value
pairs — and draw into the current figure like plot. This chart has one
series kind, the line path, so each of them is MATLAB's geometry drawn as line
paths: a bar is its outline, a stem is a stem path plus a marker-only tip path,
an area is the closed outline of its band. The numbers are exactly MATLAB's —
the same x positions, widths and heights — without the fill, and c.path(i)
counts what a call drew:
$ ICoreBlocks --console "c = bar([1 2; 3 4]); p = c.path(1); p.info"
p: name='', points=8, bounds=[x: 1..2.4, y: 0..4]
$ ICoreBlocks --console "c = stem([1 2 3]); p = c.path(0); p.info, p = c.path(1); p.info"
p: name='', points=9, bounds=[x: 1..3, y: 0..3]
p: name='', points=3, bounds=[x: 1..3, y: 1..3]
$ ICoreBlocks --console "c = histogram([1 2 2 3 3 3]); p = c.path(0); p.info"
p: name='', points=8, bounds=[x: 0.5..3.5, y: 0..3]
| You write | You get |
|---|---|
bar(y) bar(x, y) bar(y, width) bar(..., 'stacked') | one outline per column, grouped side by side (0.8 of the spacing) or stacked |
barh(...) | the same, sideways |
stem(y) stem(x, y) | two paths per column: the stems, then the tips |
stairs(y) stairs(x, y) | one path per column, held flat until the next x |
scatter(x, y) scatter(x, y, sz) scatter(x, y, sz, [r g b]) | markers alone; sz is MATLAB's area in points², scaled so 36 is this chart's 11-pixel circle |
histogram(x) histogram(x, nbins) histogram(x, edges) | the outline of the bins, by MATLAB's own automatic rule; 'BinWidth', 'BinEdges', 'NumBins', 'Normalization' |
area(y) area(x, y) | one closed outline per column, the columns stacked as MATLAB stacks them |
errorbar(y, err) errorbar(x, y, err) errorbar(x, y, neg, pos) | the data line with a whisker at every point |
fill(x, y, c) | a closed polygon's outline in colour c |
scatter draws one size and one colour per call — a path carries one of each —
so a size per point (sz as a vector) is refused rather than drawn at some
average; and pie is refused by name, this chart having neither the rulers nor
the wedges for one (bar(y) draws the same numbers as bars). area() with no
argument, or with a subsystem, is still the canvas decoration of that name: the
two grammars do not overlap, and the argument decides.
Values the command window keeps#
The command window has a variables space: results you name are kept there for the rest of the session, so a value can be computed once and reused.
A recorded signal is a time series — time stamps against one column per channel. It deliberately prints as a summary rather than as its samples, because a run of any length would otherwise fill the window:
$ ICoreBlocks --console "G = tf([1],[1 1]); y = step(G, 5, 6); y"
y = Time Series (6 samples, t = 0 .. 5) # Time Series
To get at the numbers, ask for the two halves — .time() and .values():
$ ICoreBlocks --console "G = tf([1],[1 1]); y = step(G, 5, 6); y.time()"
[[0], [1], [2], [3], [4], [5]] # Matrix of Double
$ ICoreBlocks --console "G = tf([1],[1 1]); y = step(G, 5, 6); y.values()"
[[0], [0.632121], [0.864665], [0.950213], [0.981684], [0.993262]] # Matrix of Double
Those values are 1 − e⁻ᵗ at each second, which is the step response of
1/(s+1) — a result you can check by hand, and a good way to confirm the
application is doing what you think before trusting it on a model you cannot
check.
Asking for something a value does not have tells you what it does have:
$ ICoreBlocks --console "G = tf([1],[1 1]); y = step(G, 5, 6); y.toMatrix()"
error: a time series has no member 'toMatrix' (try .time() or .values())
clearVariablesSpace empties the variables space when you want to start clean.
What a run's messages mean#
The diagnostics panel collects what a run reports. Two messages are worth recognising:
- "Unable to retrieve config …" — a block asked for one of its own parameters and did not get it. In practice this means a parameter the block needs was never set, and the run stops rather than continuing on a value nobody chose. Open that block's configuration dialog and check the parameter named in the message.
- A run that ends immediately — most often a Stop Simulation block whose input was already nonzero at the first step, or a stop time that is not later than the start time. Note that any nonzero entry fires that block, so a vector signal only one of whose elements is nonzero will still stop the run. Both causes are visible in the model configuration panel and on the diagram.
If a result looks wrong rather than absent, the first thing to check is sample rate rather than the block itself — Running a simulation — solver settings, sampling, starting and stopping covers why a signal sampled more slowly than it changes produces a response that looks stepped or delayed.
Real runs#
Every transcript on this page is a real run rather than an illustration. Binary
ICoreBlocks.app, built 2026-09-25 23:58 (source at approximately commit
1dac24c5), re-run 2026-09-26; one process per line,
HOME=<scratch> ICoreBlocks --console "<line>", with the startup lines removed
and paths under that scratch home shortened to …. Re-run any line above to
check this page against the program.