Proper way to Plot a single function in two different styles?

20

11

I've got this code

Plot[{(V^2/360)/0.4,  ConditionalExpression[(V^2/860)/0.4, V < 12], 
  ConditionalExpression[(V^2/860)/0.4, V > 12]}, {V, 8, 18},  
 PlotRange -> {0, 1.5}, 
 PlotStyle -> {{AbsoluteThickness[3]}, {AbsoluteThickness[3], 
    Dashed}, { AbsoluteThickness[3], ColorData[1, 2]}}, 
 GridLines -> Automatic, 
 Epilog -> {{Thick, Dashed, Black, Line[{{12, 0}, {12, 2}}]}, {Thick, 
    Dashed, Black, Line[{{8, 1}, {18, 1}}]}}] 

to plot a function in two pieces, each in a different style:

enter image description here

This works OK for me, though I didn't like it that I had to use ColorData[1, 2] to get the second part in the same color. Are there better ways to do this (possibly something with Piecewise)?

stevenvh

Posted 2012-07-11T08:22:34.197

Reputation: 6 446

Related: http://mathematica.stackexchange.com/q/1128/121

– Mr.Wizard – 2013-04-27T20:10:42.783

Answers

22

Note - this does not work since version 10

I've taken Mr. Wizard's clever trick of using Sequence and I to get both sections of the plot assigned to the same element of PlotStyle, and also used the ability of PlotStyle to take functions as well as graphics directives.

First define the functions splitplot and splitstyle:

SetAttributes[splitplot, HoldAll];
splitplot[pieces__] := Piecewise[{#}, I] & /@ Unevaluated @ pieces
splitplot[{v_, c_}] := splitplot[{v, c}, {v, ! c}]

splitstyle[styles__] := Module[{st = Directive /@ {styles}},
{{Last[st = RotateLeft @ st], #}} & ]

Usage:

  • splitplot takes pairs of {value, condition} like Piecewise, and is used as the function to be plotted.
  • There can be any number of "pieces" to the plot.
  • If only a single piece is specified, a second piece {value, Not[condition]} will be automatically added.
  • splitstyle takes style directives, which are applied to those pieces. The styles are used cyclically.
  • Each style directive can be a list like {Red, Thick}
  • The splitstyle function can itself be a member of a list, for example {Thick, splitstyle[Red, Blue]} will split the plot into red and blue parts, both of which are thick.
  • An empty list {} can be given to apply the default style with no alterations.

Simple example - apply dashing for x<0.5

Plot[{x, splitplot[{x^2, x < 0.5}]}, {x, 0, 1},
 PlotStyle -> {Thick, {Thick, splitstyle[Dashed, {}]}}]

enter image description here

Note that if the condition describes multiple unconnected regions this will generate multiple "pieces". In the plot below the condition x<2||x>4 produces two pieces, which are assigned to the first two style directives in splitstyle.

Plot[splitplot[{x, x < 2 || x > 4}, {13 x - 2 x^2 - 16, 2 < x < 4}], {x, 0, 6},
 PlotStyle -> splitstyle[Dashed, Green, Red]]

enter image description here

Multiple splitplot and splitstyle can be used in a single Plot

Plot[{splitplot[{6 - 2 x, x < 2}, {2 x - 2, x > 2}], 
  splitplot[{2 x, x < 3}, {12 - 2 x, x > 3}]}, {x, 0, 5}, 
 PlotStyle -> {splitstyle[{}, Dashed], splitstyle[Dashed, {}]}]

enter image description here

Simon Woods

Posted 2012-07-11T08:22:34.197

Reputation: 81 905

2@Simon Woods Your useful splitplot and splitsyle functions don't work anymore with MMA V10. Can it be easily updated ? Thanks – Sigis K – 2015-04-01T12:41:40.113

We've got a good thing going. I see some room for code reduction here; may I make the edits? – Mr.Wizard – 2012-07-14T18:48:33.697

@Mr.Wizard, please go right ahead! – Simon Woods – 2012-07-14T20:14:03.497

1Edits made; please tell me what you think. There is something unfinished: Piecewise normally does not evaluate all value expressions, e.g.: x = 7; Piecewise[{{Print[1], x < 5}, {Print[2], x > 10}}, Print[3]] does not print 1 or 2. If you think there is value in preserving this behavior we could write: Replace[Unevaluated@pieces, x_ :> Piecewise[{x}, I], {1}] -- I'm not sure if this is needed given the application however. – Mr.Wizard – 2012-07-15T01:17:09.970

BTW, I restrained myself from using ~infix~ notation as not everyone likes that, but there are a couple of binary operators that are amenable to that here. :-) – Mr.Wizard – 2012-07-15T01:18:20.433

@Mr.Wizard, thanks for the edits, all good. I've tweaked splitstyle again to compact it a tiny bit more. Regarding the evaluation of all the value expressions, I don't think the change is necessary. Or rather, I can't think of any practical example where it would make a difference. There's one other difference between splitplot and Piecewise, which is that the latter returns as soon as a condition evaluates to True. I thought about replicating that behaviour, but decided that it was quite cool that splitplot allows you to do multivalued plot functions. – Simon Woods – 2012-07-15T12:20:30.487

3Regarding infix, I'm not convinced that it would improve readability in this case :-) BTW (and off topic) my favourite infix function is opt_~of~obj_ := opt /. AbsoluteOptions[obj, opt] – Simon Woods – 2012-07-15T12:20:54.633

Interesting function of; I don't think I use AbsoluteOptions very often but perhaps I should. re: splitstyle I considered that compaction but I thought perhaps the readability was better in two terms (I have to fight the tendency to Golf answers at the expense of readability sometimes). – Mr.Wizard – 2012-07-15T15:32:05.577

Having recently updated to MMA V11.1 I tried again your splitplot and splitstyle functions with the same result as in my previous comment. with MMA V10. Can it be easily fixed now? Thanks – Sigis K – 2017-05-01T09:24:58.727

@SigisK, no - the use of functions in PlotStyle was undocumented and since it went away in version 10 there's no reason to think it will come back in any future version. – Simon Woods – 2017-05-01T11:59:28.777

9

Your method already looks pretty good to me. However, since you are looking for alternatives, here is one, though honestly I'd do it the way you started for clarity and robustness.

This uses the behavior shown in this answer, then /. (ReplaceAll) to style the separate Line expressions afterward.

Piecewise is used with a default argument of I (imaginary number) which does not plot.

The replacement works on the internal format of the Graphics expression produced by Plot as described here.

Plot[
  {V^2/144`,
   Sequence[
    Piecewise[{{V^2/344`, V < 12}}, I],
    Piecewise[{{V^2/344`, V > 12}}, I]
   ]
  },
  {V, 8, 18},
  PlotRange -> {0, 1.5},
  PlotStyle -> AbsoluteThickness[3],
  GridLines -> Automatic,
  Epilog -> {Thick, Dashed, Black, Line[{{{12, 0}, {12, 2}}, {{8, 1}, {18, 1}}}]}
] /. {sty__, x_Line, y_Line} :> {sty, y, Dashed, x}

Mathematica graphics

Mr.Wizard

Posted 2012-07-11T08:22:34.197

Reputation: 259 163

6

You can use a Piecewise to combine your curve, and then use Mesh and MeshShading to specify different styles for different ranges. In the example below there is only a split at 12, so Mesh -> {{12}}.

MeshShading is then used to style the different ranges.

Plot[{(V^2/360)/0.4, 
     Piecewise[{{(V^2/860)/0.4, V < 12}, {(V^2/860)/0.4, V > 12}}]
   },
   {V, 8, 18},
   PlotRange -> {0, 1.5},
   GridLines -> Automatic, 
   Mesh -> {{12}},
   MeshShading -> {Dashed, Automatic}, 
   PlotStyle -> AbsoluteThickness[3], 
   Epilog -> {{Thick, Dashed, Black, Line[{{12, 0}, {12, 2}}]}, {Thick, 
      Dashed, Black, Line[{{8, 1}, {18, 1}}]}}
]

Exported plot

I did not manage to only make one of the curves dashed with one call.

A hackery way to do it is to create two plots, the first with a Transparent curve. I don't recommend this though, as your version is cleaner.

p1 = Plot[{1, Piecewise[{{(V^2/860)/0.4, V < 12}, {(V^2/860)/0.4, V > 12}}]},
  {V, 8, 18}, PlotRange -> {0, 1.5}, 
  PlotStyle -> {Transparent, {AbsoluteThickness[3]}}, 
  GridLines -> Automatic, Mesh -> {{12}}, 
  MeshShading -> {Dashed, Automatic}, 
  Epilog -> {{Thick, Dashed, Black, Line[{{12, 0}, {12, 2}}]}, {Thick,
      Dashed, Black, Line[{{8, 1}, {18, 1}}]}}]

p2 = Plot[(V^2/360)/0.4, {V, 8, 18}, PlotRange -> {0, 1.5}, 
  PlotStyle -> AbsoluteThickness[3]]

Show[p1, p2]

The result is the same as your output.

Malte Lenz

Posted 2012-07-11T08:22:34.197

Reputation: 2 433

As you can see your first method gives a different output, I can't use that. Thanks for answering, anyway. – stevenvh – 2012-07-11T10:41:17.667

Yes, I know. The answer is to the more general question how to plot a single function in two different styles, which can be done with Mesh and MeshShading. I hope someone comes up with a clean answer for your case where there are multiple curves, and only one of them is styled in two ways. – Malte Lenz – 2012-07-11T10:45:25.760

I'm rather new to MMA, and realize that I still do lots of things in a less than optimal way. But I have already experienced that MMA is quite powerful and flexible, not only its strictly analytic engine, so I wouldn't be surprised if there's a better way here too. – stevenvh – 2012-07-11T10:51:53.663

4

Something else for your consideration:

p1 = Plot[V^2 / 144`, {V, 8, 18}, PlotRange -> {0, 1.5}];
p2 = Plot[V^2 / 344`, {V, #, #2}, PlotStyle -> #3] & @@@
       {{8, 12, {{Red, Dashed}}}, {12, 18, Red}};

Show[
  {p1, p2},
  BaseStyle -> {14, AbsoluteThickness[3]},
  GridLines -> Automatic,
  Epilog -> {Thick, Dashed, Black, 
              Line[{{{12, 0}, {12, 2}}, {{8, 1}, {18, 1}}}]}
]

Mathematica graphics

Mr.Wizard

Posted 2012-07-11T08:22:34.197

Reputation: 259 163

4

While I think splitplot and splitstyle is a very cool answer, here is my usual, very simplistic way to construct plots like this. Show is your friend, and there is little need for Epilog and Prolog anymore.

With[{a = 0.4, b1 = 360, b2 = 860},
  Show[{
    Plot[v^2/(a b1), {v, 8, 18}, PlotRange -> {0, 1.5}, 
     GridLines -> Automatic, GridLinesStyle -> LightGray, 
     PlotStyle -> Thick],
    Plot[v^2/(a b2), {v, 8, 12}, 
     PlotStyle -> {Thick, Dashed, ColorData[1][2]}],
    Plot[v^2/(a b2), {v, 12, 18}, 
     PlotStyle -> {Thick, ColorData[1][2]}],
    Graphics@{Thick, Opacity[0.4], Line@{{8, 1}, {18, 1}}, 
      Line@{{12, 0}, {12, 1.5}}}
    }
  ]
]

Daniel Flatin

Posted 2012-07-11T08:22:34.197

Reputation: 451