Data Presentation Using Dials

Daniel G. Martinez

Science Applications International Corporation

Programming Foundation

Transformations of geometric-to-geodetic angles ( function κ, below ) and polar-to-rectangular coordinates ( function λ, below ) are central to the technique presented herein for using virtual rotatable dials to express quantitative or qualitative data.  In the examples that follow, the independent variable ( elapsed time ) of a time-series data set is indicated by a control device, such as a slider, and the dependent variable ( magnitude related to elapsed time ) is given by a rotating dial's position on a graduated scale.

κ[Θ_]:=(90+360 UnitStep[Θ-90]-Θ) °  (*  geometric to geodetic angle ( ° ) conversion, Rob Knapp's modification  *)

λ[Θ_,r_,as_]:=With[{u=r Exp[I κ[Θ]]},Graphics[{Red,Arrowheads[as],Arrow[{{0,0},{Re[u],Im[u]}}]}]]  (*  arrow at angle Θ ° from origin to dial's edge ( radius r ) with arrowhead size 'as', adapted from Rob Knapp's work *)

Sidebar: Isolation of a dynamic variable

Supporting Functions and Calculations

κ[Θ_]:=(90+360 UnitStep[Θ-90]-Θ) °  (*  geometric to geodetic angle ( ° ) conversion, Rob Knapp's version  *)

λ[Θ_,r_,as_]:=With[{u=r Exp[I κ[Θ]]},Graphics[{Red,Arrowheads[as],Arrow[{{0,0},{Re[u],Im[u]}}]}]]  (*  arrow from origin to dial's edge ( radius r ) at angle Θ ° with arrowhead size 'as', adapted from Rob Knapp's work *)

ar[α_]:={Blue,Arrowheads[Large],Arrow[{{0,0},{Re[0.95 Exp[I κ[α]]],Im[0.95 Exp[I κ[α]]]}}]}  (*  arrow pointing to geodetic angle α  *)

λt[Θ_,tic_]:=With[{u=Exp[I κ[Θ]]},Graphics[{Blue,Line[{{tic Re[u],tic Im[u]},{Re[u],Im[u]}}]}]]  (*  blue tick of relative length 'tic' from dial's edge at geodetic angle Θ °   *)

τm[v_]:=Table[λt[j,0.95],{j,0,360,v}];  (*   tick marks  *)

Application

Row[{Manipulate[DynamicModule[{α=0},
Column[{Slider[Dynamic[α],{0,360 5},ImageSize->{275,15}],
Dynamic@Show[ListPlot[{{0,0}},PlotRange->{{-1.1,1.1},{-1.1,1.1}},Axes->False,Frame->False,AspectRatio->1,ImageSize->250,Epilog->ar[α]],Graphics[{Blue,Circle[{0,0},1]}],τm[v]]}]],{{v,1,"tick density"},1,10,1, Appearance->{"Labeled",Tiny},AppearanceElements->None},AppearanceElements->None],Spacer[150],Manipulate[DynamicModule[{α=0},
Column[{Slider[Dynamic[α],{0,360 5},ImageSize->{275,15}],
Show[ListPlot[{{0,0}},PlotRange->{{-1.1,1.1},{-1.1,1.1}},Axes->False,Frame->False,AspectRatio->1,ImageSize->250,Epilog->Dynamic[ar[α]]],Graphics[{Blue,Circle[{0,0},1]}],τm[v]]}]],{{v,1,"tick density"},1,10,1, Appearance->{"Labeled",Tiny},AppearanceElements->None},AppearanceElements->None]}]

Working Application

Supporting Functions and Calculations

κ[Θ_]:=(90+360 UnitStep[Θ-90]-Θ) °  (*  geometric to geodetic angle ( ° ) conversion  *)

ar1[α_,c_]:={c,Arrowheads[Large],Arrow[{{0,0},{Re[0.95 Exp[I κ[α]]],Im[0.95 Exp[I κ[α]]]}}]}  (*  arrow of color c pointing to geodetic angle α  *)

cι[x_]:=Graphics[{Lighter[Blue,0.7],Circle[{0,0},x]}]   (*  circle whose radius is 'x'  *)

t[x_,c_,r_,Θ_]:=With[{u=r Exp[I κ[Θ]]},Graphics[{c,Text[Style[x,Plain,FontSize->12],{Re[u],Im[u]},Background->GrayLevel[0.99]]}]]   (*  plot text 'x' in color 'c' at radius 'r' and angle 'Θ' from circle's center  *)

dat={{1,60},{7,35},{9,20},{15,80},{25,5},{30,10},{40,60}};  (*  time series data set ( red line )  *)

dat1={{1,30},{5,25},{11,40},{17,50},{23,15},{32,15},{40,25}};  (*  time series data set ( black line )  *)

dat2={{1,18},{3,29},{14,34},{20,5},{27,45},{35,50},{40,15}};  (*  time series data set ( green line )  *)

Syntax Note on scaling and offset factor

The scaling factor, (320/100), in the ' f ' functions is based on dividing a sweep of 320 degrees ( 200 to 360 on the left side and 0 to 160 on the right ) into 100 gradations.  The offset factor, -160 , is based on having the zero mark 160 degrees to the "left" of vertical.

f[j_]:=Interpolation[dat,InterpolationOrder->1][j]×(320/100)-160  (*  time series interpolation with scaling and offset factors FOR RED DIAL  *)

f1[j_]:=Interpolation[dat1,InterpolationOrder->1][j]×(320/100)-160  (*  time series interpolation with scaling and offset factors FOR BLACK DIAL  *)

f2[j_]:=Interpolation[dat2,InterpolationOrder->1][j]×(320/100)-160  (*  time series interpolation with scaling and offset factors FOR GREEN DIAL  *)

rd=Graphics[{Red,Disk[{0,0},0.02]}];  (*  red disk at center of circle  *)

Syntax Note on tick mark tables

In both tables τm and τq, 200 represents the canonical 200 degree angle and 520 represents the ( 360 + 160 = 520 ) 160 degree angle.  The τm marks are drawn every 3.2 degrees, and the τq are drawn every fifth degree.

λt[Θ_,tic_]:=With[{u=Exp[I κ[Θ]]},Graphics[{Blue,Line[{{tic Re[u],tic Im[u]},{Re[u],Im[u]}}]}]]  (*  blue tick of relative length 'tic' from dial's edge at angle Θ °   *)

τm=Table[λt[j,0.95],{j,200,520,3.2}];  (* 1-minute tick marks  *)

τq=Table[λt[j,0.9],{j,200,520,3.2×5}];  (* 5-minute tick marks  *)

Application

Framed[DynamicModule[{α=10},GraphicsRow[{
Column[{Dynamic[α],Slider[Dynamic[α],{(Subscript[dat, [[1,1]]]),(Subscript[dat, [[-1,1]]])},Appearance->Tiny,ImageSize->{250,15}],
Framed[Show[ListPlot[{{0,0}},PlotStyle->White,PlotRange->{{-1.3,1.3},{-1.3,1.3}},Axes->False,Frame->False,AspectRatio->1,Epilog->{Dynamic[ar1[f[α],Red]],Dynamic[ar1[f1[α],Black]],Dynamic[ar1[f2[α],Green]]},ImageSize->200],t[0,Blue,1.15,200],t[10,Blue,1.15,232],t[20,Blue,1.15,264],t[30,Blue,1.15,296],t[40,Blue,1.15,328],t[50,Blue,1.15,0],t[60,Blue,1.15,32],t[70,Blue,1.15,64],t[80,Blue,1.15,96],t[90,Blue,1.15,128],t[100,Blue,1.15,160],rd,τm,τq,cι[1]],FrameMargins->35]},Center],
ListLinePlot[{dat,dat1,dat2},PlotRange->{0,100},AspectRatio->1,Axes->False,Frame->True,PlotStyle->{Red,Black,Green},ImageSize->300,Epilog->Dynamic[{Blue,Thickness[0.0025],Line[{{α,0},{α,90}}]}]]}]]]

Oval Dial

κ[Θ_]:=(90+360 UnitStep[Θ-90]-Θ) °  (*  geometric to geodetic angle ( ° ) conversion, Rob Knapp's modification  *)

λ[Θ_,r_,as_]:=With[{u=r Exp[I κ[Θ]]},Graphics[{Red,Arrowheads[as],Arrow[{{0,0},{Re[u],Im[u]}}]}]]  (*  arrow at angle Θ ° from origin to dial's edge ( radius r ) with arrowhead size 'as', adapted from Rob Knapp's work *)

re[θ_,a_,b_]:=(Sqrt[(((a^2) (b^2))/((a^2) ((Sin[θ])^2)+(b^2) ((Cos[θ])^2)))])  (*  ellipse radius length as a function of direction and axes coefficients  *)

eλ[x_,y_,θ_,as_]:=Show[ListPlot[{{0,0}},PlotRange->{{-7,7},{-7,7}},PlotStyle->White,AspectRatio->Automatic,Axes->False,Frame->True,ImageSize->200],Graphics[Circle[{0,0},{x,y}]],λ[θ,re[κ[θ],x,y],as]]  (*  ellipse with respective axes lengths of x and y, line from center at angle θ °, and arrow size of 'as'   *)

t[x_,c_,r_,θ_,fs_]:=With[{u=r Exp[I κ[θ]]},Graphics[{c,Text[Style[x,Plain,FontSize->fs],{Re[u],Im[u]},Background->GrayLevel[0.99]]}]]   (*  plot text 'x' in color 'c' at radius 'r' and angle 'θ' from circle's center  *)

Manipulate[Show[eλ[x,y,θ,as],t[0,Blue,tdy re[κ[0],x,y],0,fs],t[90,Blue,tdx re[κ[90],x,y],90,fs],t["180",Blue,tdy re[κ[180],x,y],180,fs],t[270,Blue,tdx re[κ[270],x,y],270,fs]],{{x,1,"x semiaxis "},1,5,AppearanceElements->None,Appearance->{"Labeled",Tiny}},{{y,1,"y semiaxis "},1,5,AppearanceElements->None,Appearance->{"Labeled",Tiny}},{{fs,5,"font size "},5,12,AppearanceElements->None,Appearance->{"Labeled",Tiny}},{{tdx,1,"x-label distance "},1,2,AppearanceElements->None,Appearance->{"Labeled",Tiny}},{{tdy,1,"y-label distance "},1,2,AppearanceElements->None,Appearance->{"Labeled",Tiny}},{{as,0.01,"arrowhead size "},0.01,0.2,AppearanceElements->None,Appearance->{"Labeled",Tiny}},{{θ,0,"dial angle ( ° ) "},0,360,AppearanceElements->None,Appearance->{"Labeled",Tiny}},ControlPlacement->Left,AppearanceElements->None]

eλ[x semiaxix, y semiaxis, angle, arrowhead size]
t[text, color, v re[κ[angle], x semiaxix, y semiaxis], angle, fontsize]

Manipulate[Show[eλ[3.135,2.205,a,0.05],t["Empty",Blue,1.35re[κ[230],3.135,2.205],230,10],t["Half",Blue,1.25re[κ[0],3.135,2.205],0,10],t["Full",Blue,1.35re[κ[130],3.135,2.205],130,10]
],
{a,230,360+130,Appearance->Tiny,AppearanceElements->None},AppearanceElements->None]

Function gg puts the line ( eλ ) and dial marker ( t ) data that were devised in the exercise above into a form that can be used to supply time-dependent data into the dial face, as shown below.

gg[a_]:=Show[eλ[3.135,2.205,a,0.05],t["Empty",Blue,1.35re[κ[230],3.135,2.205],230,10],t["Half",Blue,1.25re[κ[0],3.135,2.205],0,10],t["Full",Blue,1.35re[κ[130],3.135,2.205],130,10]]

In the example below, a full inventory is assumed to be 80 items.  The objective is to relate elapsed time ( day of year ) to the relative quantity of items on hand assuming a linear time dependence between data points.

dat={{5,60},{24,28},{56,12},{69,40},{80,64},{93,50},{122,71},{139,27}};  (* { day of year, inventory quantity obtained at inflection points }  *)

d1=Interpolation[Table[{(Subscript[dat, [[j,1]]]),((Subscript[dat, [[j,2]]])/80)},{j,Length[dat]}],InterpolationOrder->1];

Plot[d1[j],{j,(Subscript[dat, [[1,1]]]),(Subscript[dat, [[-1,1]]])},ImageSize->500,Axes->False,Frame->True,PlotRange->{0,1},AspectRatio->0.3,FrameLabel->{"Day of Year","Inventory"}]

Function gλ evaluates at point x the linear equation between points v1 and v2.

gλ[x_,v1_,v2_]:=((v1[[2]]-v2[[2]])/(v1[[1]]-v2[[1]])) (x-v1[[1]])+v1[[2]]

Plot[gλ[x,{0,230},{1,490}],{x,0,1},ImageSize->150,Axes->False,Frame->True]

Manipulate[Row[{gg[gλ[d1[j],{0,230},{1,490}]],Plot[d1[k],{k,(Subscript[dat, [[1,1]]]),(Subscript[dat, [[-1,1]]])},ImageSize->500,Axes->False,Frame->True,PlotRange->{0,1},AspectRatio->0.3,FrameLabel->{"Day of Year","Inventory"},Epilog->Dynamic[{Red,Thickness[0.0025],Line[{{j,0},{j,90}}]}]]}],{j,(Subscript[dat, [[1,1]]]),(Subscript[dat, [[-1,1]]]),Appearance->"Labeled",AppearanceElements->None},AppearanceElements->None]

δλ[x_]:=Style[DateString[DatePlus[{2007,12,31},x],{"Day"," ","MonthName"," ","Year"}],Blue]

Manipulate[Row[{gg[gλ[d1[j],{0,230},{1,490}]],Plot[d1[k],{k,(Subscript[dat, [[1,1]]]),(Subscript[dat, [[-1,1]]])},ImageSize->500,Axes->False,Frame->True,PlotRange->{0,1},AspectRatio->0.3,FrameLabel->{"Day of Year","Inventory"},Epilog->Dynamic[{Red,Thickness[0.0025],Line[{{j,0},{j,90}}]}]]}],{j,(Subscript[dat, [[1,1]]]),(Subscript[dat, [[-1,1]]]),ControlType->None},Row[{"date   ",Slider[Dynamic[j],{dat[[1,1]],dat[[-1,1]],1},Appearance->Tiny],"  ",Item[Dynamic[δλ[j]]]}],AppearanceElements->None]