Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

In my work as a math teacher, I create lots of graphs to illustrate ideas. I have lots of math books and will often try to duplicate a particular style of graph. Below, one very simple example, created with Mathematica. (but as a "one off")

enter image description here

I wish my Mathematica skills were better, I spend too much time grading and lesson planning, and I know I could be more effective with lesson writing if my toolkit was bigger and more robust.

So I decided to try to write some functions to create these kinds of plots, and thought it would be best to put them into a package so I could use them easily.

I have a program that created the plot, and it works fine, but I'm embarrassed to share it, it's so clunky...I don't care about speed and concise code, I wanted to be able to understand all the steps and pieces...

specialA[xmin_, xmax_, xscale_, ymin_, ymax_, yscale_, xtickmin_, 
  xtickmax_, xtickinc_, ytickmin_ , ytickmax_, ytickinc_, funclist_, 
  stylelist_, ratio_] := 
 Module [{xvals, lightx, darkx, gridx1, gridx2, xgrid, yvals, lighty, 
   darky, gridy1, gridy2, ygrid, xticks, yticks},

  xvals = Range[xmin, xmax, xscale];
  lightx = Select[xvals, Not[IntegerQ[#/5]] &];
  darkx = Select[xvals, IntegerQ[#/5] &];
  gridx1 = {#, GrayLevel[0.8]} & /@ lightx;
  gridx2 = {#, Black} & /@ darkx;
  xgrid = Flatten[{gridx1, gridx2}, 1];
  yvals = Range[ymin, ymax, yscale];
  lighty = Select[yvals, Not[IntegerQ[#/5]] &];
  darky = Select[yvals, IntegerQ[#/5] &];
  gridy1 = {#, GrayLevel[0.8]} & /@ lighty;
  gridy2 = {#, Black} & /@ darky;
  ygrid = Flatten[{gridy1, gridy2}, 1];
  xticks = Range[xtickmin, xtickmax, xtickinc];
  yticks =  Range[ytickmin, ytickmax, ytickinc];

  Plot[funclist, {x, xmin, xmax}, 
   PlotStyle -> stylelist,
   GridLines -> {xgrid, ygrid}, 
   PlotRange -> {{xmin, xmax}, {ymin, ymax}}, 
   AspectRatio -> ratio, 
   Ticks -> {xticks, yticks}, 
   ImageSize -> 430, 
   Epilog -> Text[0, {0, 0}, {1, 1}],
   BaseStyle -> {FontSize -> 18, FontFamily -> "Times"}]
  ]

So, for example....

    f[x_] := 2 x^2 - x - 8;
g[x_] := -x^2 - 2 x + 2
specialA [-7, 7, 1, -32, 32, 2, -5, 5, 5, -30, 30, 10, {f[x], g[x], 
  f[x] - g[x], g[x] - f[x]}, {Red, Blue, Green, Magenta}, 2]

I'd welcome any comments on that and welcome any nice plotting routines for "high school math" graphs that others might be using.

But here is my real question.

I'm no expert but have created a few simple Mathematica packages. I put this function into a package, wrote a usage statement, defined the function.

When I use it, I get a grid, but the functions don't display. I am sure this has something to do with contexts , but I just have no clue.

How can I improve my function definition, and how would I include it in a working package of similar functions to draw other graphs?

(I save my package online in case that would be helpful... https://dl.dropbox.com/u/24411154/grapher.nb)

I know this is a huge question, I do appreciate the feedback and help from this group.

Tom

share|improve this question
    
Just in case: are you aware of the GridLines option? –  J. M. Oct 24 '12 at 13:14
2  
@J.M. he is, it is in his code. –  rcollyer Oct 24 '12 at 13:34
3  
+1 from me because I honestly admire the intent of this question. Math teachers, like Tom, in the trenches thinking about ways to teach better and trying to use state of the art tools to do it -- all of this deserves encouragement and support not just for and from this community (which it helps by preparing the next generation) but for and from the whole world by developing the thinkers and problem solvers it will need. Nice to have you around. –  Jagra Oct 24 '12 at 14:01
3  
I think you are right that it's a context problem. The x in your call to specialA is in the Global context, whereas the x in the Plot command is in the package context. You could try including x as one of the arguments to specialA –  Simon Woods Oct 24 '12 at 14:26
2  
I don't think your style is "clunky". Code for creating complex plots or graphics often looks this way. What I would recommend is making some of the arguments into options. Makes it easy give them defaults and to document them and makes them order independent when calling specialA. –  m_goldberg Oct 24 '12 at 14:44

1 Answer 1

up vote 21 down vote accepted

I promised to give you some hints and I want to concentrate on some specific points in your implementation.

Calling style of your custom function.

When I looked at your function and how you call it I instantly thought that those are many parameters you have to remember. On every call of your function, you have to input 15 values where some of them maybe don't change.

My Question is here: Your function is basically a plot, why don't you keep the simple call style of it and ensure that it does something useful even if you don't supply all settings. The real work of your own implementation is the creation of the grid. So what you basically want is

  • A function where you are able to set the grid-density and how many lines are highlighted.
  • A function which takes certain inital values for Options to e.g. ImageSize, BaseStyle,.. where (and here comes the point) you want to be able to easily change them without adjusting the code.
  • A function which works like Plot meaning you can set any Options you like.

Let me give you an outline for such a function

Options[PlotSpecial] = {
   GridStep -> Automatic,
   GridHighlight -> 5,
   ImageSize -> 400, Epilog -> Text[0, {0, 0}, {1, 1}], 
   BaseStyle -> {FontSize -> 18, FontFamily -> "Times"},
   AspectRatio -> Automatic,
   GridLines -> Automatic};

SetAttributes[PlotSpecial, {HoldAll}];

PlotSpecial[f_, range : {x_, xmin_, xmax_}, opts : OptionsPattern[{PlotSpecial, Plot}]] := 
  Module[{(*...*)}, 

    (*...*)

    Plot[f, {x, xmin, xmax}, GridLines -> grid, 
      Evaluate@FilterRules[Flatten@{opts, Options[PlotSpecial]}, Options[Plot]]]
]

As you can see I switched from a giving parameters every time to a set option when needed style. The Options of your PlotSpecial contain of (1) some new options and (2) some options which are concrete settings for Plot. In the function definition you can see how you tell that PlotSpecial that it takes options from both functions. Since the options are optional you have the same calling style as Plot itself and since you give the symbol x you circumvent the issue the issue of a global symbol inside a package.

Since it sometimes happens, that x has a value (set with for instance x=2) we have to prevent the evaluation when you call e.g. PlotSpecial[f,{x,0,10}]. If we wouldn't hold the evaluation back, x and f would, before anything happens, be evaluated into numbers and the plot would not work. Therefore, we prevent evaluation, by giving PlotSpecial the attribute HoldAll.

The last lines show in principle what we call. We use Plot, set our calculated GridLines and supply at the end all other options which Plot can handle. These options are (1) the ones which are fixed in the options-list of PlotSpecial and (2) the ones which may be set by the user in the function call.

Calcuation of GridLines

This is in my opinion a minor issue, because calculating something step by step can sometimes be better decrypted by a possible reader of your code. On the other hand you are doing almost the same thing twice for xgrid and ygrid which is a waste of useful time. Maybe you are a bit overwhelmed by all this, but I give my best to explain everything in detail.

Let's first find a function which takes a list in the form of the result of Range[min, max, scale] and returns a correct grid definition like you want, so for instance

{{1, GrayLevel[0.5]}, {2, GrayLevel[0.5]}, {3, GrayLevel[0.5]}, {4, 
  GrayLevel[0.5]}, {6, GrayLevel[0.5]}, {7, GrayLevel[0.5]}, {8, 
  GrayLevel[0.5]}, {9, GrayLevel[0.5]}, {0, GrayLevel[0]}, {5, 
  GrayLevel[0]}, {10, GrayLevel[0]}}

We first want to divide the list given by Range[min,max,scale] into numbers which are divisible by 5 and those which are not. There are many ways, but let's stick with Select which you chose. You don't need to call Select twice because the other list ist just the Complement of the other. Assume vals are the values of the grid then you all those which are divisible by some integer and then you calculate the complement to this:

{#, Complement[vals, #]} &@Select[vals, Mod[#, 5] === 0 &]

To bring now the colors in, let me create a function from the above line and add one little Transpose and call it with Range[0,10,1], so we see the output

Function[{vals},Transpose[{{#,Complement[vals,#]}&@
  Select[vals,Mod[#,5]===0&],{Black,Gray}}]][Range[0,10,1]]

(* {{{0,5,10},GrayLevel[0]},{{1,2,3,4,6,7,8,9},GrayLevel[0.5]}} *)

Now we want the color in the last part of each sublist get anyhow in between the numbers. Funny enough, Riffle does exactly this

Riffle[{0,5,10},GrayLevel[0],{2,-1,2}]
(* {0,GrayLevel[0],5,GrayLevel[0],10,GrayLevel[0]} *)

So what we want to do is to Apply this special Riffle (with the last argument which states where to put the colors) to both sublists. This can be achieve with the @@@ operator which applies the function the the sublists. Combining this with what we have already gives

Function[{vals},Riffle[##,{2,-1,2}]&@@@Transpose[{{#,Complement[vals,#]}&@
  Select[vals,Mod[#,5]===0&],{Black,Gray}}]][Range[0,10,1]]
(*
  {{0,GrayLevel[0],5,GrayLevel[0],10,GrayLevel[0]},
  {1,GrayLevel[0.5],2,GrayLevel[0.5],3,GrayLevel[0.5],4,GrayLevel[0.5],
  6,GrayLevel[0.5],7,GrayLevel[0.5],8,GrayLevel[0.5],9,GrayLevel[0.5]}}
*)

This looks already quite close to the output we want and indeed the only thing which is left, is to Flatten this list completely and to collect every two elements again with Partition[...,2].

The funny thing is, that this function can be applied on any range specification, may it be the x-direction or the y-direction. Therefore the first 12 lines of your function can be reduced to something like

grid = Function[{vals},Partition[Flatten[Riffle[##,{2,-1,2}]&@@@
 Transpose[{{#,Complement[vals,#]}&@Select[vals,Mod[#,5]===0&],
 {Black,Gray}}]],2]] /@ vals

where vals at the end is {xvals, yvals} which are created with Range like you did.

Calculating ticks

As you can see I did not introduce a special ticks options. This is because we will just use the option Ticks for our purpose, meaning if it is set to Automatic we will draw ticks on all the black lines. Nevertheless, by giving an explicit setting of Ticks to the function call, you can always adjust this.

This part of the code is going to be very small

ticks = If[OptionValue[Ticks] === Automatic,
   Select[#, crit] & /@ vals,
   OptionValue[Ticks]
   ];

One thing is new: crit. Since we use the Mod[#,optGH]===0& function in the grid calculation and here, we just introduce a variable crit for it. Note, that you may not want to use 5 everytime for the positions of the black lines. Therefore, our function has the new GridHighlight option which is set to 5 per default but can take any value and black grid-lines and ticks can appear even at multiples of 2 or whatever.

The rest of the values

You might ask now: "Where is the rest of the information which I gave in my 15 parameters, because you only used 2 options?". Here's the deal. xmin, xmax, ymin, ymax is only the PlotRange. This can be set by just setting PlotRange or it can be extracted from a plot if you don't want to set it manually. So we do a trick here: We make a plot as first step in our function which is only used to extract an absolute value of the PlotRange.

origopts = AbsoluteOptions[Plot[f, range, Evaluate@FilterRules[{opts},
  Options[Plot]]],PlotRange];

Next parameters are {xscale,scale} which are extracted from the GridStep option. xtickmin, ... are parameters I did not use. I just asume you want ticks everywhere where black lines are.

From the plot-range in origopts and the scale-parameters we can calculate vals by

{xscale, yscale} = OptionValue[GridStep];
vals = Range @@@ Append @@@ Transpose[{Round[PlotRange /. origopts], {xscale, yscale}}];

Final code and examples

Final changes I made are, that the grid is only calculated when the option GridLines is set to Automatic (which is the default).

Options[PlotSpecial] = {
   GridStep -> {1, 1},
   GridHighlight -> 5,
   ImageSize -> 400, Epilog -> Text[0, {0, 0}, {1, 1}], 
   BaseStyle -> {FontSize -> 18, FontFamily -> "Times"},
   AspectRatio -> Automatic,
   GridLines -> Automatic};

SetAttributes[PlotSpecial, {HoldAll}];

PlotSpecial[f_, range:{x_, xmin_, xmax_}, opts:OptionsPattern[{PlotSpecial, Plot}]] := 
 Module[{origopts, xscale, yscale, grid, optGH, vals, crit, ticks}, 
  origopts = AbsoluteOptions[
    Plot[f, range, Evaluate@FilterRules[{opts}, Options[Plot]]], PlotRange];

  {xscale, yscale} = OptionValue[GridStep];
  optGH = OptionValue[GridHighlight];
  vals = Range @@@ Append @@@ Transpose[{Round[PlotRange /. origopts], {xscale, yscale}}];
  crit = Mod[#, optGH] === 0 &;
  ticks = If[OptionValue[Ticks] === Automatic,
    Select[#, crit] & /@ vals,
    OptionValue[Ticks]
    ];

  grid = If[OptionValue[GridLines] === Automatic,
    Function[{vals}, Partition[Flatten[Riffle[##, {2, -1, 2}] & @@@ 
    Transpose[{{#, Complement[vals, #]} &@Select[vals,crit], 
    {Black, GrayLevel[0.9]}}]],2]] /@ vals,
    OptionValue[GridLines]];  
  Plot[f, {x, xmin, xmax}, GridLines -> grid, Ticks -> ticks, 
   Evaluate@FilterRules[Flatten@{opts, Options[PlotSpecial]}, Options[Plot]]]
  ]

Now you can call your function without any additional options and get

f[x_] := 6 Sin[.1 x^2];
g[x_] := 3 Sin[.2 x] + 4 Cos[.3 x]
PlotSpecial[{f[x], g[x], f[x] - g[x], g[x] - f[x]}, {x, -10, 10}]

Mathematica graphics

Or you want to change the grid

PlotSpecial[{f[x], g[x], f[x] - g[x], g[x] - f[x]}, {x, -6, 6}, GridHighlight -> 3]

Mathematica graphics

Or you adjust several options. Note, that I set PlotRange manually and still the grid works, although it completely relies on it

PlotSpecial[{f[x], g[x], f[x] - g[x], g[x] - f[x]}, {x, -10, 10}, 
 PlotRange -> {Automatic, {0, 10}}, GridHighlight -> 4, 
 GridStep -> {1/2, 1/2}, PlotStyle -> Thick]

Mathematica graphics

Or you overwrite your BaseStyle. Even exact rationals are possible for the grid

PlotSpecial[{f[x], g[x], f[x] - g[x], g[x] - f[x]}, {x, -10,
  10}, GridHighlight -> 7/3, GridStep -> {2/3, 2/3}, 
 PlotStyle -> Directive[ColorData[6, 1]], 
 BaseStyle -> {FontSize -> 22, ColorData[6, 2], 
   FontFamily -> "Euler"}]

Mathematica graphics

And here comes the last example: since grid and ticks are only calculated when their setting is Automatic, you can set both manually bypassing your whole approach.

PlotSpecial[{f[x], g[x], f[x] - g[x], g[x] - f[x]}, {x, -2 Pi,
  2 Pi}, PlotStyle -> Directive[Thick, ColorData[6, 1]], Ticks -> None, 
 BaseStyle -> {FontSize -> 22, ColorData[6, 2], FontFamily -> "Euler"}, 
 GridLines -> {{-2 Pi, -Pi, 0, Pi, 2 Pi}, Automatic}]

Mathematica graphics

I hope this gives you some ideas. Please, don't think that this is the way to go. There are always many ways and you should find your own way.

Additionally, I tested the function inside a package and it seems to work fine. Don't forget to give the your custom options a usage message to export them too.

share|improve this answer
1  
Nice step-by-step guide. –  VLC Oct 26 '12 at 8:24
    
I would consider adding HoldAll, and putting everything inside Block[{x}, ...] to block evaluation of x. Oh, and +1. –  rcollyer Oct 26 '12 at 12:22
    
@rcollyer oh, I forgot the attributes. Is there a reason to additionally localizing x with Block? Because the only occurrence of the held x is in the Plot call which has HoldAll too. –  halirutan Oct 26 '12 at 12:28
    
You're right, Block doesn't seem necessary. I'd almost consider using x_Symbol, instead of x_, but Plot will complain appropriately if x isn't a symbol. –  rcollyer Oct 26 '12 at 12:33
    
Wow, I am so thankful for all the time you took to respond, that is incredible and so very helpful. That will really,really help with any future developments, I'll take the time to work through all the things you shared, thank you! –  Tom De Vries Oct 26 '12 at 16:48

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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