# Simulate a simple spinner

I'm teaching some simple ideas in probability to students in grade 7.

The "spinner" below works, but I'm wondering how I could make it just a bit more realistic by having it actually "spin" around a few times, slow down, and then end up where ever it has to be? I.E. randomly do a few complete circles and then slow down and stop.....

Any suggestions are appreciated.

 Manipulate[
Column[{

Show[
PieChart[{1, 1, 1, 1, 1}, ChartStyle -> "DarkRainbow",
ChartLabels ->
Placed[{2, 4, 6, 8, 10}, "RadialCenter", Style[#, 24] &],
LabelingFunction -> None],

Graphics[{

{PointSize[0.05], Point[{0, 0}]},

{Thick, Arrow[{{0, 0}, 0.7 { Cos[angle], Sin[angle]}}]}
}], ImageSize -> 600],
Button["Spin!", angle = RandomReal[{0, 6.28}]]
}
]

, {{angle, 0, "Spin"}, 0, 2 \[Pi]}]

-

I suggest a different user interface, for fun. The user clicks down the mouse and drags. An arrow will be drawn that indicates the magnitude and direction of a "flick" of the spinner. When the mouse is released ("MouseUp"), a random destination is computed and the index of the spinner spins to it, decelerating as if under constant deceleration. The number of revolutions depends on the magnitude of the flick. It is not exactly a physical model, since the index will stop at the random destination independent of the magnitude of the flick.

I wrote three helper functions, one for the background and two for the index (pointer). The background is composed of sectors instead of a PieChart because the elements of a PieChart respond to mouse clicks. One of the index functions draws the index at a specified angle and the other dynamically moves the index from a specified angle0 to another angle1. When the motion is finished, the variable spinningQ is set to False, which ultimately stops the dynamic updating. The motion is regulated by Clock[], so that the motion will be accurate in time and not depend on system delays; however, system speed will determine the jumps in the movements (the intervals between renderings) and the apparent continuity of motion. There are embedded constant parameters that control the performance, which are pointed out in comments in the code.

spinnerBG[labels_] :=(* background *)
With[{nSectors = Length@labels},
Table[{ColorData["DarkRainbow"][Rescale[i, {1, nSectors}]],
Disk[{0, 0}, 1, π - 2 π (i - 1)/nSectors + 2 π/nSectors {-1, 0}], Black,
Text[Style[labels[[i]], 24], 0.7 {Cos[#], Sin[#]} &[π - 2 π (i - 0.5)/nSectors]]},
{i, nSectors}]];
stillIDX[angle_] := (* non-moving index *)
{{PointSize[0.05], Point[{0, 0}]}, {Thick, Arrow[{{0, 0}, 0.7 {Cos[angle], Sin[angle]}}]}};
spinningIDX[angle0_, angle1_, time0_] := (* animated spinning index *)
DynamicModule[{scaledTime, v0, a0, dt},
dt = 0.5 Sqrt[Abs[angle1 - angle0]];(* alter 0.5 to adjust speed *)
v0 = a0 = 2 (angle1 - angle0);
Dynamic[If[(scaledTime = Rescale[Clock[Infinity] - time0, {0, dt}]) >= 1,
spinningQ = False;(* stop spinning *)
scaledTime = 1];
stillIDX[-0.5 a0 scaledTime^2 + v0 scaledTime + angle0]]
];


Here is my version of the Manipulate.

spinningQ = False;
Manipulate[EventHandler[
Dynamic@Graphics[{
spinnerBG[{2, 4, 6, 8, 10}],
If[spinningQ,
Dynamic@spinningIDX[angle0, angle1, Refresh[Clock[Infinity], None]],
stillIDX[angle1]],
Red, Thick, Dynamic@If[ptDown != ptUp, Arrow[{ptUp, ptDown}], {}]
}, PlotRange -> 1.2, ImageSize -> 600],
{"MouseDown" :> (ptDown = ptUp = MousePosition["Graphics"];
angle1 = ArcTan @@ ptDown; spinningQ = False),
"MouseDragged" :> (ptUp = MousePosition["Graphics"]),
"MouseUp" :> (ptUp = MousePosition["Graphics"];
If[ptDown != ptUp,
angle0 = ArcTan @@ ptDown;
angle1 = angle0 + RandomReal[{-π, π}] +
2 π Round[Sign[#] 0.5 (* to make sure the rotation is not in the reverse \
direction  of the arrow *)
+ # &[4 (ptDown - ptUp).{-Sin[angle1], Cos[angle1]}]];
(* the factor of 4 affects how many times it goes around before stopping *)
spinningQ = True; (* start spinning *)
ptDown = ptUp; (* reset: makes arrow disappear *)
])
}
],

{{ptDown, {1, 0}}, None}, {{ptUp, {1, 0}}, None},
{{angle1, 0}, None}, {{angle0, 0}, None},
TrackedSymbols :> {angle1, spinningQ}]


Edit

In case you want a button:

Manipulate[
Dynamic@Graphics[{
spinnerBG[{2, 4, 6, 8, 10}],
If[spinningQ,
Dynamic@spinningIDX[angle0, angle1, Refresh[Clock[Infinity], None]],
stillIDX[angle1]],
Red, Thick, Dynamic@If[ptDown != ptUp, Arrow[{ptUp, ptDown}], {}]
}, PlotRange -> 1.2, ImageSize -> 300],
{{ptDown, {1, 0}}, None}, {{ptUp, {1, 0}}, None},
{{angle1, 0}, None}, {{angle0, 0}, None},
Button["Spin", angle0 = angle1;
angle1 += RandomReal[{0, 2 π}] + 2 π RandomInteger[{1, 4}];
spinningQ = True; ptUp = {1, 0}; ptDown = {1, 0}],
TrackedSymbols :> {angle1, spinningQ},
Initialization :> (spinningQ = False)
]


The code for the Button can be added to the first Manipulate, if you want both interfaces.

-
+1, nice. You could also solve the ODE I solve with the initial angular velocity proportional to the magnitude of the flick; then it would be closer to a simulation. I'd do it but I don't want to steal your code! –  acl Apr 19 '13 at 13:44
Thanks, wow, that's a great spinner. All the answers were so helpful, I chose this as "the answer", but they all taught me some new Mathematica skills, and they all will help with my teaching... –  Tom De Vries Apr 22 '13 at 14:47
I'm getting An unknown box name (List) was sent as the BoxForm for the expression. Check the format rules for the expression. in version 7; any idea where that might be coming from before I start digging? –  Mr.Wizard Jul 30 '13 at 13:51
@Mr.Wizard It would be something as unhelpful as List. First or second Manipulate, or both? Right after execution, I suppose (before a spin)? Works in v8.0.4, the closest thing I have to v7. Works if I localize and initialize all the functions inside. (Lots of declarations like {{spinnerBG, spinnerBG}, None}, plus Initialization :> (..) code.) I can't see anything that's different from v7 in v8. Did you try restarting with a fresh FrontEnd? If I think of something, I'll ping. Sorry :( –  Michael E2 Jul 30 '13 at 15:08

You could have the velocity at which it spins decay randomly in time.

For example, here I have the angle at time $t$ satisfy $\theta''(t)=-f(t)\theta'(t)$ with $f(t)$ a random(ish) function (see later). If $\theta(0)=1$ and $\theta'(0)=0$ then $\theta'(t)\rightarrow 0$ for large $t$.

So now we just need the $f(t)$, which I construct by interpolating a list of random numbers. This results in a smooth function. If maxF is the maximum value and upT the upper time limit,

maxF = .1;
upT = 100;
f = Interpolation[
{{0, 0}}~Join~Transpose@{Range[1., upT], RandomReal[{0, maxF}, upT]}
];


So we solve for $\theta$,

soln = First@
NDSolve[{th''[t] == -f[t] th'[t], th[0] == 1, th'[0] == 1},
th, {t, 0, upT}]


which does the right thing:

Here's how to make it so that you click on a button and it rotates. Warning, I am clueless about this dynamical functionality, UIs and so on. I'm making this up as I go along. Use at your own risk!

solveEqn[maxF_, upT_] := Module[{th, f},
f = Interpolation[{{0, 0}}~Join~
Transpose@{Range[1., upT], RandomReal[{0, maxF}, upT]}];
th /. First@
NDSolve[{th''[t] == -f[t] th'[t], th[0] == 1, th'[0] == 1},
th, {t, 0, upT}]

]


And

pchart = PieChart[{1, 1, 1, 1, 1}, ChartStyle -> "DarkRainbow",
ChartLabels ->
Placed[{2, 4, 6, 8, 10}, "RadialCenter", Style[#, 24] &],
LabelingFunction -> None];
upT = 50;
th = solveEqn[.1, 100];
Column[{Dynamic[
ListAnimate[
Table[
Show[pchart,
Graphics[{{PointSize[0.05], Point[{0, 0}]}, {Thick,
Arrow[{{0, 0}, 0.7 {Cos[th[t]], Sin[th[t]]}}]}}],
ImageSize -> 300],
{t, 1, upT}
],
AnimationRepetitions -> 1
],
TrackedSymbols :> {th}
], Button["Spin!", Dynamic[th = solveEqn[.1, upT]]]}]


(you may want to increase maxF and/or $\theta(0)$ and $\theta'(0)$ to get more variability in the final position).

You click, it spins.

If anybody who knows what they're doing with Dynamics has any suggestions, please let me know.

-
Very nice. Don't forget his Spin button... –  cormullion Apr 17 '13 at 10:17
For random wiggly functions, you might want to use Perlin noise. –  Ｊ. Ｍ. Apr 17 '13 at 10:33
@J.M. I only know of Perlin noise from your answers... –  acl Apr 17 '13 at 10:38
@cormullion thanks. I figured that he has the UI in place and was asking about the rotation, so I addressed that. –  acl Apr 17 '13 at 10:39
Thanks so much for responding. Some great stuff there! I will admit that I don't in fact know how to incorporate that into my existing spinner. What you've done looks very realistic! Just not sure how to make it work.... –  Tom De Vries Apr 17 '13 at 15:21

Here's a way to animate the pointer:

Manipulate[ang = 0.9*ang + dest;
Column[{Show[PieChart[{1, 1, 1, 1, 1}, ChartStyle -> "DarkRainbow",
ChartLabels -> Placed[{2, 4, 6, 8, 10}, "RadialCenter", Style[#, 24] &],
LabelingFunction -> None],
Graphics[{{PointSize[0.05], Point[{0, 0}]}, {Thick,
Arrow[{{0, 0}, 0.7 {Cos[ang], Sin[ang]}}]}}], ImageSize -> 600],
Button["Spin!", {dest = RandomReal[{0, 2 Pi}]; ang = RandomReal[{0, 2 Pi}];}]}],
{ang, {0, 2 Pi}, ControlType -> None}, {dest, {0, 2 Pi}, ControlType -> None}]


The initial location of the pointer is set to ang and the final location is proportional to dest. The speed of the pointer is controlled by the decay factor (set to 0.9 here), which causes it to slow down exponentially at this rate.

-