9
$\begingroup$

What is the most elegant way to draw a timing diagram specified by a list of zeros and ones?

In the following code I attempt to draw the clock pulse diagram, and I'd like to continue drawing the timing diagram for a given input of 0's and 1's, e.g., {11011011,11100110}.

clockWidth = 0.2;
pulseHgh = 0.1;
rowHgh = pulseHgh + 0.1;
pNum = 8;
diagWidth = 2;
yb = 0;
clock = Table[
   Line[{ {xb, 
      yb} , {xb, yb} + {0, pulseHgh}, {xb, yb} + {clockWidth/2, 
       pulseHgh}, {xb, yb} + {clockWidth/2, 0}, {xb, 
       yb} + {clockWidth, 0}}], {xb, 0, clockWidth*pNum, clockWidth}];
txt = Table[
   Text[Style[n, 12, Italic], {n*clockWidth + clockWidth/2, 
     yb + 0.13}], {n, 0, pNum}];
Graphics[{clock}, Epilog -> {Text["Clock", {2, yb}], txt}, 
 PlotRange -> {-0.2, 0.2} ]

I'd like the result to look somewhat like the figure shown below.

enter image description here

$\endgroup$
2
  • 5
    $\begingroup$ ListLinePlot[IntegerDigits@11011011, InterpolationOrder -> 0, AspectRatio -> 1/10] is a good start :) $\endgroup$
    – rm -rf
    Commented Jul 14, 2014 at 2:06
  • $\begingroup$ @rm-rf I believe the way I created the clock pulse is clumsy enough. $\endgroup$
    – Putterboy
    Commented Jul 14, 2014 at 2:07

3 Answers 3

12
$\begingroup$

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

chart

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]]

min-chart

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]]

max-chart

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

invalid

$\endgroup$
6
  • $\begingroup$ Alternatives are always good in my opinion. Even as a terse code fanatic I'd like to see it. $\endgroup$
    – Mr.Wizard
    Commented Jul 17, 2014 at 12:02
  • $\begingroup$ Believe me, it's about as far from your style as one can get in Mathematica ;D $\endgroup$
    – m_goldberg
    Commented Jul 17, 2014 at 17:43
  • $\begingroup$ @m_goldberg I would like take a step further to ask, how to implement a program that can show a continuously flowing in inputs of random "0 and 1" strings. Just like the screen shown on an oscilloscope. $\endgroup$
    – Putterboy
    Commented Jul 17, 2014 at 21:31
  • $\begingroup$ That would be a nice feature, and it's certainly doable. Would have to give it some thought, though. Biggest challenge is keeping the left-hand labels static while the bits scroll by. I can see the use of what you ask for displaying long but given signals. However, don't see the worth of displaying random, dynamically generated bit streams. Could you explain? $\endgroup$
    – m_goldberg
    Commented Jul 17, 2014 at 21:55
  • $\begingroup$ @m_goldberg There are much to learn in MM, the most important is to grasp the algorithms from the solutions made by the gurus, I believe. Random signals can mean anything, so it can be replaced by anything meaningful if necessary. $\endgroup$
    – Putterboy
    Commented Jul 17, 2014 at 22:17
6
$\begingroup$

Perhaps (as per rm-rf):

str = {11011011, 11100110}
sig = {#1, #2 + 
      1.5} & @@ ((IntegerDigits /@ str) /. {1 -> Sequence[1, 1], 
      0 -> Sequence[0, 0]});
clock = Join @@ Table[{4, 3}, {Length[First@sig]/2}];
plt = ListLinePlot[Join[sig, {clock}], InterpolationOrder -> 0, 
  PlotRange -> {{0, 17}, Automatic}, 
  FrameTicks -> {Thread[{Range[1.5, 15.5, 2], Range[8]}], 
    Thread[{{0.5, 2, 3.5}, {str[[1]], str[[2]], "clock"}}], None, 
    None}, Frame -> True, FrameLabel -> {"Ticks", "Signals"}, 
  ImageSize -> 500]

enter image description here

UPDATE

As m_goldberg observes the above is an approach only for the test strings. To deal with other binary strings including starting zeros and to end on full clock cycle:

fun[str_] := Module[{dig, fs, clock, n, timd},
  dig = (IntegerDigits[ToExpression@#, 10, StringLength[#]] & /@ 
      str) /. {1 -> Sequence[1, 1], 0 -> Sequence[0, 0]};
  fs = MapThread[Join[#1, {#2}] &, {dig, Last /@ dig}];
  n = Length[First@dig]/2;
  clock = Append[Join @@ Table[{1, 0}, {n}], 0];
  timd = Join[fs, {clock}];
  ListLinePlot[MapIndexed[#1 + 2 First@#2 - 2 &, timd], 
   InterpolationOrder -> 0, Frame -> True, 
   FrameTicks -> {Thread[{Range[1.5, 2 n - 0.5, 2], Range[n]}], 
     Thread[{Range[0.5, 2 Length@timd, 2], Join[str, {"clock"}]}], 
     None, None}, PlotRange -> {{0, 2 n + 2}, Automatic}]
  ]

This is not as nice as I would like, e.g.(i) I have not accounted for variable string lengths but this could be easily dealt with by just scaling to maximal string length or I could just truncate to shortest, or just only accept equal length...but I leave to needs/aims user (ii)have not programmed different clock rates relative to signal pulses (have just kept original).

An example:

fun[{"001101", "110111", "010101", "100101"}]

enter image description here


Mr.Wizard here, for a bit of refactoring:

fun[str : {__String}] :=
 Module[{fs, n, timd},
  fs = Riffle[#, #] ~Append~ Last[#] & /@ ToExpression @ Characters @ str;
  n = Length @ First @ fs;
  timd = fs ~Append~ PadLeft[{0}, n, {0, 1}];

  ListLinePlot[
   MapIndexed[#1 + 2 First@#2 - 2 &, timd],
   InterpolationOrder -> 0,
   Frame -> True, 
   FrameTicks -> {
     Thread[{Range[1.5, n - 1.5, 2], Range[n/2]}], 
     Thread[{Range[0.5, 2 Length@timd, 2], Join[str, {"clock"}]}],
     None,
     None
    },
   PlotRange -> {{0, n + 1}, Automatic}
  ]
 ]
$\endgroup$
5
  • $\begingroup$ @rm-rf, ubpdqn Beautiful, Thanks a lot. $\endgroup$
    – Putterboy
    Commented Jul 14, 2014 at 3:29
  • $\begingroup$ @user16069 glad it was helpful... i merely applied window dressing to rm-rf 's answer...you can adapt and modify to your stylistic preferences $\endgroup$
    – ubpdqn
    Commented Jul 14, 2014 at 3:38
  • $\begingroup$ My only problem with this answer is that won't work for cases where the signal stream starts with one or more zeros. $\endgroup$
    – m_goldberg
    Commented Jul 16, 2014 at 14:08
  • $\begingroup$ @m_goldberg see edit $\endgroup$
    – ubpdqn
    Commented Jul 17, 2014 at 10:24
  • $\begingroup$ @Mr.Wizard thank you very much for the improvements...I should learn to appreciate and use Riffle more..btw sorry i missed chance to help in your mma 10 quest...would have been more than happy to help $\endgroup$
    – ubpdqn
    Commented Jul 17, 2014 at 12:26
2
$\begingroup$

I'm not going to do the red lines and the numbers but here's a way to generate the coordinates of the horizontal and vertical lines that make up the graphs and to draw these:

drawGraph[str_, width_, height_] := 
 Module[{values, vertical, horizontal},
  values = Join[{First@#}, #, {Last@#}] &[ToExpression /@ Characters@str];
  vertical = MapIndexed[
    Transpose[{width {First@#2, First@#2}, height #}] &,
    Partition[values, 2, 1]
    ];
  horizontal = Partition[Rest@Flatten[vertical, 1], 2];
  Graphics[{
    Line[vertical],
    Line[horizontal]
    }, ImageSize -> 500]
  ]

Example:

Grid@Transpose[{
   {"Clock", "11011011", "11100110"},
   drawGraph[#, 1, 1] & /@ {"10101010", "11011011", "11100110"}
   }]

example

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.