46
$\begingroup$

I would like to generate a ListPlot with the color for each point in the plot corresponding to a particular value (not associated with the position in the plot). I'd then like to add a legend indicating what the color means.

I'm currently solving the first part of the problem by essentially generating a separate ListPlot for each data point and then assigning a color to that ListPlot. Here's a toy example:

n = 5000;
pos = RandomVariate[NormalDistribution[0, 2], {n, 2}];
altitude = Norm /@ pos;
ListPlot[{#} & /@ pos, 
 PlotStyle -> ((Blend[{{Min[altitude], Yellow}, {Max[altitude], 
         Red}}, #] &) /@ altitude), AspectRatio -> 1]

enter image description here

So my questions are:

(1) Is this the only way to generate such a ListPlot (it does the job but it seems inelegant and I suspect it's inefficient, though that's not a big concern for my application)?

(2) Is there an easy way to generate a legend which indicates the value of the color (i.e., a gradient bar which shows the color scale)?

$\endgroup$
0

5 Answers 5

31
$\begingroup$

In this case I would use Point for plotting the points. For example

n = 5000;
pos = RandomVariate[NormalDistribution[0, 2], {n, 2}];
altitude = Norm /@ pos;
colorf = Blend[{{Min[altitude], Yellow}, {Max[altitude], Red}}, #] &

pl = Graphics[MapThread[{colorf[#1], Point[#2]} &, {altitude, pos}], 
  Axes -> True, AspectRatio -> 1]

As for plotting legends, that's a reoccurring issue in Mathematica. There is a package called PlotLegends` which you could try but it is not very user friendly and the legends it produces are quite ugly IMHO. I find that it's often faster to just create a legend by hand. For example, this is a function I use for creating legends with contour plots:

plotLegend[{min_, max_}, n_, col_] := 
 Graphics[MapIndexed[{{col[#1], 
      Rectangle[{0, #2[[1]] - 1}, {1, #2[[1]]}]}, {Black, 
      Text[NumberForm[N@#1, {4, 2}], {4, #2[[1]] - .5}, {1, 0}]}} &, 
   Rescale[Range[n], {1, n}, {min, max}]],
  Frame -> True, FrameTicks -> None, PlotRangePadding -> .5]

Here, n is the number of subdivisions and col is the colour function. You could combine the legend with the original plot using Grid, e.g.

leg = plotLegend[Through[{Min, Max}[altitude]], 20, colorf];
Grid[{{Show[pl, ImageSize -> {Automatic, 300}], 
  Show[leg, ImageSize -> {Automatic, 250}]}}]

Mathematica graphics

$\endgroup$
3
  • 9
    $\begingroup$ The numbers on the color legend aren't really nice. You could use FindDivisions to get a nice set, like so FindDivisions[Through[{Min, Max}[altitude]], 10] $\endgroup$ Feb 4, 2012 at 18:20
  • $\begingroup$ Very helpful! Many thanks to you and the others who posted answers and suggestions!!! $\endgroup$
    – Cassini
    Feb 5, 2012 at 12:42
  • 2
    $\begingroup$ WHY are the most minor, simple, obvious things so bloody difficult in MMa? I've been using MMa for about 6 months now, and this is my zillionth visit to SE to find out how to do something you'd accomplish in 1 line in Mathematica. Does Wolfram not think about real world use at all??? $\endgroup$ Jan 24, 2015 at 7:08
22
$\begingroup$

ListLinePlot (or alternately ListPlot with Joined->True) accepts a ColorFunction, which you can use to color your points. The lines can be later converted to points as:

colorFun = Function[{x, y}, Blend[{{Min[altitude], Yellow}, {Max[altitude], Red}}, 
    Norm[{x, y}]]];    
ListLinePlot[pos, ColorFunction -> colorFun, AspectRatio -> 1, 
      ColorFunctionScaling -> False] /. Line -> Point

Then using Heike's plot legends and Sjoerd's suggestion to use FindDivisions, you get:

enter image description here

$\endgroup$
4
  • $\begingroup$ +1 this gets the same faster rendering via VertexColors that my code does, but is arguably neater. I still like Inset for the legend; consider combining them. $\endgroup$
    – Mr.Wizard
    Feb 4, 2012 at 21:29
  • $\begingroup$ @Spartacus Thanks. I will, sometime later in the day. I'm off for now. $\endgroup$
    – rm -rf
    Feb 4, 2012 at 21:59
  • $\begingroup$ Hi. I am using this code with ListLinePlot but how to modify the point size with the Line->Point command. Thanks. $\endgroup$
    – Porty
    Oct 8, 2017 at 16:39
  • $\begingroup$ @Porty just add /. {Line -> Point, PointSize[___] -> PointSize[0.02]} or other value $\endgroup$ Nov 6, 2017 at 11:24
21
$\begingroup$

As usual Heike has a fine method, but I can tighten it up.

This will render quite a bit faster, it will use built-in color functions more easily, and it will IMO interactively rescale better.

Data:

n = 5000;
pos = RandomReal[NormalDistribution[0, 2], {n, 2}];
altitude = Norm /@ pos;
colorf = Blend[{Yellow, Red}, #] &;

Legend function (modified):

plotLegend[{min_, max_}, n_, col_] :=
 Graphics[
  {{col[(# - 1)/(n - 1)], Rectangle[{0, # - 1}, {1, #}]},
   {Black, Text[
      NumberForm[Rescale[#, {1, n}, {min, max}], {4, 2}],
      {3, # - .5}, {1, 0}]}
  } & /@ Range@n,
  Frame -> True, FrameTicks -> None, PlotRangePadding -> .5]

Plot:

Graphics[{
  Point[pos, VertexColors -> colorf /@ Rescale@altitude],
  Inset[plotLegend[{Min@#, Max@#}& @ altitude, 20, colorf],
    Scaled[{1, 1/2}],
    ImageScaled[{-0.1, 1/2}],
    Scaled[{1/4, 0.9}]]
 }, Axes -> True]

Mathematica graphics

It's easy to use other gradients. With colorf = ColorData["Rainbow"]:

Mathematica graphics

$\endgroup$
8
  • 2
    $\begingroup$ As with Heike's solution, yours has an ugly set of numbers. No professional typesetter/layouter would use these. Use FindDivision for a better set of numbers. $\endgroup$ Feb 5, 2012 at 22:14
  • $\begingroup$ @Sjoerd I only noticed this comment now. Okay, I'll improve this later. $\endgroup$
    – Mr.Wizard
    Feb 11, 2012 at 0:29
  • $\begingroup$ @Guillochon Thanks for noticing. :-) $\endgroup$
    – Mr.Wizard
    Mar 20, 2014 at 17:57
  • $\begingroup$ @Sjoerd I see I never did address your criticism. Sorry. This time I won't claim that I will, but perhaps; we'll see. $\endgroup$
    – Mr.Wizard
    Mar 20, 2014 at 17:58
  • $\begingroup$ Can you suggest where/how to insert a small label above the plot legend frame? I've been trying w/o success. A simple framelabel results in unacceptable resizing of the legend. $\endgroup$
    – ndetermin
    Aug 17, 2014 at 15:32
4
$\begingroup$
data = RandomReal[{0, 1}, {50, 3}];
ListPlot[Style[#[[{1, 2}]], Hue@#[[3]]] & /@ data, 
   PlotLegends -> BarLegend[{Hue, {0, 1}}]]
$\endgroup$
3
$\begingroup$

BarLegend and Legended:

Using the setup in Heike's and Mr.Wizard's answers:

n = 5000;
pos = RandomVariate[NormalDistribution[0, 2], {n, 2}];
altitude = Norm /@ pos;
colorf = Blend[{{Min[altitude], Yellow}, {Max[altitude], Red}}, #] &;

pl = Graphics[Point[pos, VertexColors -> (colorf /@ altitude)], 
   Axes -> True, AspectRatio -> 1];

bl = BarLegend[{colorf, Through[{Min, Max}[altitude]]}, 10, 
   LegendMarkerSize -> 300, LegendLabel -> Style["legend", 20, "Panel"]];

Legended[pl, Placed[bl, Left]]

enter image description here

To make the bar legend wider postprocess it using the function makeWide below:

ClearAll[makeWide]
makeWide[w_: 15] := RawBoxes[ToBoxes[#] /. {15/2 | 7.5 ->  w/2, -15/2 | (-7.5) -> -w/2}]&;

Legended[pl, makeWide[40]@bl]

enter image description here

$\endgroup$

Your Answer

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

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