21

I've been struggling to create a DotPlot like the one shown in Cleveland's The Elements of Graphing Data.

enter image description here

Using the following dataset

data = Sort[{#, 
 WolframAlpha[
  StringJoin["Number of native speakers ", #], {{"Result", 1}, 
   "ComputableData"}]} & /@ {"Mandarin", "French", "English", 
"Spanish", "German", "Hindi", "Malay", "Arabic", "Portuguese", 
"Russian", "Korean", "Italian", "Cantonese", "Telugu", 
"Urdu"}, #1[[2]] < #2[[2]] &]

What is the best approach to replicate this chart with its two axis, dot, dashed lines , etc?

rm -rf
  • 88,781
  • 21
  • 293
  • 472
Zviovich
  • 9,308
  • 1
  • 30
  • 52

3 Answers3

27

Something like this :

data = Sort[{#, 
  WolframAlpha[
   StringJoin["Number of native speakers ", #], {{"Result", 1}, 
   "ComputableData"}]} & /@ {"Mandarin", "French", "English", 
   "Spanish", "German", "Hindi", "Malay", "Arabic", "Portuguese", 
   "Russian", "Korean", "Italian", "Cantonese", "Telugu", 
   "Urdu"}, #1[[2]] < #2[[2]] &]

sorted = SortBy[data, #[[2]] &] ;
len= Length[sorted] ;

ListPlot[Transpose[{Log[2, sorted[[All, 2]]/10^6], Range[len]}], 
 PlotRange -> {All, All}, Frame -> True,
 FrameTicks -> {{{#, sorted[[#, 1]]} & /@ Range[len], None}, {{#, 2^#} & /@ Range[len], {#, #} & /@ Range[len]}},
 GridLines -> {None, {#, Dotted} & /@ Range[len]},
 FrameLabel -> {"Number of Speakers (millions)", ""}, 
 PlotLabel -> "Log Number of Speakers (\!\(\*SubscriptBox[\(log\), \(2\)]\) \ \ millions)", 
 AxesOrigin -> {0, Log[2, 1.5]}]

data

b.gates.you.know.what
  • 20,103
  • 2
  • 43
  • 84
  • 1
    you just need the top ticks and you're all set. Use a frame with the 4 element list version for the ticks – rm -rf Jul 02 '12 at 18:23
  • @R.M Thanks, I totally overlooked that. – b.gates.you.know.what Jul 02 '12 at 19:28
  • speakers are now stored in "people" units so You need sorted = MapAt[QuantityMagnitude, #, {2}] & /@ SortBy[data, #[[2]] &] but I'm not very familiar with Units so maybe it could be done simpler. – Kuba Jul 10 '13 at 06:42
14

Except for the simplest graphics, you almost always have more flexibility and control if you build the graphics expressions bottom-up, eg try a variation on this:

Graphics[
 {{Opacity[0.3], Dashed, Line[{{0, First@#2}, {900, First@#2}}]}, 
    Text[First@#, {0, First@#2}, {1, 0}],
    Blue, Disk[{Last@#/10^6, First@#2}, 5*{1, 0.03}]} &~ MapIndexed~ 
  data,
 AspectRatio -> 1/2,
 PlotRange -> All,
 ImageSize -> 500,
 Frame -> {True, False, False, False},
 BaseStyle -> FontFamily -> "Helvetica"
 ]

Note there's some hacks here (bad programming practice, but faster to implement):

  • The '900' as parameter to Line instead of extracting the Max coordinate

  • The 5*{1,0.03} as parameter to Disk (effecting ellipses) which must be tuned to AspectRatio, instead of using Epilog and Inset to decouple from AspectRatio. I've bugged WRI about this 'feature'.

enter image description here

alancalvitti
  • 15,143
  • 3
  • 27
  • 92
  • 1
    If you use Point (with either PointSize or AbsolutePointSize) instead of Disk you don't have to worry about aspect ratios. – Brett Champion Jul 03 '12 at 03:39
  • That works for simple elements like Point and Line, but doesn't work for most other icons, eg, if you want a Rectangle, a Star, or any other shape-coded elements which are common in visualization nowadays. Do you know of a way to avoid Epilog? – alancalvitti Jul 03 '12 at 03:43
  • 1
    Inset is one possibility: – Brett Champion Jul 03 '12 at 03:51
  • star = Graphics[ Polygon[Table[{Cos[2 Pi (2 i + 1)/5], Sin[2 Pi (2 i + 1)/5]}, {i, 5}]]] – Brett Champion Jul 03 '12 at 03:51
  • Graphics[Table[ Inset[star, RandomReal[1, 2], Center, Offset[25]], {20}], AspectRatio -> 2] – Brett Champion Jul 03 '12 at 03:51
  • Thanks Brett, that works modulo Color in the above graphic, substituting for Disk --> Inset[star, {Last@#/10^6, First@#2}, Center, Offset[10]]. It appear Offset with one parameter controls the star's size; wasn't aware of this behavior. However, the stars are rendered black, not blue. If {Blue,star} are passed to Inset, the result is some weird blue paw-mark looking icons. Any clues? – alancalvitti Jul 03 '12 at 04:09
  • This is getting beyond the scope of a comment... You need to get the Blue inside star (which is a complete graphic, not just a polygon.) – Brett Champion Jul 03 '12 at 04:14
  • That works. I'm pushing WRI to provide a more orthogonal solution to this problem. It should be possible to specify Color or other modifiers to a sequence of graphics elements whether Inset or not. – alancalvitti Jul 03 '12 at 04:20
7

It is a Chart so one may want to use BarChart:

(* speakers are stored with [people] units so we have to get rid of it, 
   I'm also deleting Malavian since there is no data now*)
sorted = MapAt[QuantityMagnitude, #, {2}] & /@SortBy[data, #[[2]] &] // Rest
len = Length[sorted]

mark[{{xmin_, xmax_}, {ymin_, ymax_}}, ___] := {Black, AbsolutePointSize@7, 
                                              Point[{Scaled[{0, -.02}, {xmax, ymax}]}]}
topt = Table[{i, 2^i}, {i, 0, 9}];

BarChart[Log[2, sorted[[ ;; , 2]]/10^6], 
         ChartLabels -> sorted[[ ;; , 1]], BarOrigin -> Left, BarSpacing -> Large, 
         GridLines -> {None, Range@len}, GridLinesStyle -> Dotted, 
         ChartElementFunction -> mark, Frame -> {{False, True}, {True, True}},
         AxesOrigin -> {0, -1}, FrameTicks -> {{False, False}, {Automatic, topt}}]

enter image description here

Kuba
  • 136,707
  • 13
  • 279
  • 740
  • I like this approach, but I'm not seeing the chart labels. (Version 11.1.) As far as I can tell, the Frame option cannot be used if we want the label to show. (Bug?) – Alan Sep 16 '17 at 04:10
  • @Alan will try to find some time later to check. – Kuba Sep 16 '17 at 04:51
  • If the left-spine frame ticks are set to Automatic, then with Frame->True the labels appear and actually looks nice, imo. – Alan Sep 18 '17 at 01:11