Edit
I found this a very interesting question, and I developed a solution at the Graphics
level rather that the Plot
level. My implementation approach is classic divide-and-conquer, building up the main function from a number of small ones. This made development straightforward, but also makes the code rather long for answers posted on this site.
Component code
Binary signals
Binary signals (bit streams) are represented by lists of ones and zeros. Signal data are expected to be string representations of binary numbers, e.g., "01100100001101000100".
bitStringToDigits[bitstr_String] := ToExpression @ Characters@ bitstr
The clock's bit stream is standardized as alternating ones and zeros beginning with a one. It only needs to be given the length of the stream.
clockDigits[tMax_Integer /; tMax > 0] := Table[Mod[i, 2], {i, tMax}]
Graphics items
What is drawn on the screen to represent the bit at time t
depends on both the value of the bit and the value of the bit that precedes it. The first argument to bit distinguishes the four possible cases. The second argument is the bit's position, t
.
{0, 0}
$\ \ \ \ \ $zero-bit preceded by a zero-bit
{1, 0}
$\ \ \ \ \ $zero-bit preceded by a one-bit
{1, 1}
$\ \ \ \ \ $one-bit preceded by a one-bit
{0, 1}
$\ \ \ \ \ $one-bit preceded by a zero-bit
Note that bit
draws zeros at $y=0$ and ones at $y=1$. In a chart with multiple bit streams, the bits in any given stream will be translated vertically to their proper height in the chart.
bit[{0, 0}, t_Integer] := {Line[{{t, 0}, {t + 1, 0}}]}
bit[{1, 0}, t_Integer] := {Line[{{t, 1}, {t, 0}}], Line[{{t, 0}, {t + 1, 0}}]}
bit[{1, 1}, t_Integer] := {Line[{{t, 1}, {t + 1, 1}}]}
bit[{0, 1}, t_Integer] := {Line[{{t, 0}, {t, 1}}], Line[{{t, 1}, {t + 1, 1}}]}
Given a list where every element is either zero or one, signalGraphicsItem
returns a list of Line
expressions which will draw the bit stream represented by the input list.
signalGraphicsItem[bits_List] :=
Module[{pairs},
pairs = Partition[Join[{bits[[1]]}, bits], 2, 1];
MapIndexed[bit[#1, #2[[1]] - 1] &, pairs]]
Given an integer, tMax
, clockGraphicsItem
returns a list of graphics primitives that draws the bit train of a reference over the interval 0 to tMax
time units. The bit train is labeled CLOCK
to its left.
clockGraphicsItem[tMax_Integer /; tMax > 0] :=
{Text[Style["CLOCK", 14], {-.25, .5}, {1, 0}], signalGraphicsItem[clockDigits[tMax]]}
Given a string representing a valid binary signal, bitsGraphicsItem
returns a list of graphics primitives that draws the bit train of the signal. Otherwise, bitsGraphicsItem
issues an error message and returns an empty list.
bitsGraphicsItem::badbits = "`1` does not represent a valid binary signal";
With[{textHt = 14},
bitsGraphicsItem[bitstr_String] /; StringFreeQ[bitstr, Except["0" | "1"]] :=
{Text[Style[bitstr, textHt], {-.25, .5}, {1, 0}],
signalGraphicsItem[bitStringToDigits[bitstr]]}]
bitsGraphicsItem[args___] := (Message[bitsGraphicsItem::badbits, Defer[args]]; {})
Put vertical dashed lines in the background, marking off the time steps.
With[{color = Red},
backgroundLines[tMax_Integer, nSignals_Integer, spacer_ /; spacer >= 0] :=
{color, Dashed,
Table[Line[{{i, 0}, {i, (1 + spacer) nSignals - spacer}}], {i, 0, tMax}]}]
Label the background lines with time indicators, top and bottom
timeLabel[tMax_Integer] := Table[Text[i, {i, 0}], {i, 0, tMax}]
Binary signal chart
This is the main function. Given a list of strings representing valid binary signals, binarySignalChart draws charts like
binarySignalChart
takes two options.
clock
$\ \ \ \ \ \ \ \ \ \ \ \ $default is True
; if False
, suppresses drawing of the clock bit train
ImageSize
$\ \ \ \ \ $passed on to Graphics
for controlling chart size
Protect[clock];
Options[binarySignalChart] = {clock -> True, ImageSize -> Medium};
With[{spacer = .5},
binarySignalChart[signals : {_String ..}, OptionsPattern[]] :=
Module[{n, items, tMax, displacements},
items = bitsGraphicsItem /@ signals;
tMax = Max[StringLength /@ signals];
If[OptionValue[clock],
PrependTo[items, clockGraphicsItem[tMax]]];
n = Length@items;
displacements = {0, 1 + spacer} # & /@ Range[0, n - 1];
Graphics[{
backgroundLines[tMax, n, spacer],
Thread[Translate[items, displacements]],
Translate[timeLabel[tMax], {0, -.25}],
Translate[timeLabel[tMax], displacements[[-1]] + {0, 1.2}]},
ImageSize -> OptionValue[ImageSize]]]]
binarySignalChart[signal_String, opt : OptionsPattern[]] :=
binarySignalChart[{signal}, opt]
Examples
This is a minimal chart. The clock display is suppressed and because there is only one signal to display, there is no need to wrap it in a list.
With[{signal = "10110101011001"},
binarySignalChart[signal, clock -> False, ImageSize -> 500]]
Any number of signals can be given. They do not have to be the same length. The vertical timing lines and the clock train are automatically extended to cover the longest signal train.
With[{signals = {"11100110", "0101100101100", "11011011"}},
binarySignalChart[signals, ImageSize -> Large]]
An invalid input string is rejected, but the valid ones are drawn. An error message is printed.
With[{signals = {"11100110", "invalid", "11011011"}}, binarySignalChart[signals]]
bitsGraphicsItem::badbits: invalid does not represent a valid binary signal
ListLinePlot[IntegerDigits@11011011, InterpolationOrder -> 0, AspectRatio -> 1/10]
is a good start :) $\endgroup$