DiscontinuousPlot

Graphs discontinuous functions, detecting the discontinuities and breaking the graph at these points

Marcus McGuff
Austin Community College
mmcguff@austin.cc.tx.us

Code

Needs["Utilities`FilterOptions`"];

DiscontinuousPlot::usage = "DiscontinuousPlot is a modified version \
of the built-in Plot command used to graph a function, but it will detect \
discontinuities and draw the graph with breaks at these points.  The sybtax \
is almost the same as for Plot:

DiscontinuousPlot[f, {x, xmin, xmax},options] will graph the function, \
skipping over the discontinuities.
DiscontinuousPlot[f, {x, x0, x1, ..., xn},options] will graph the function, \
breaking it at points x1 through xn-1. It will still skip other \
discontinuities it finds.
Warning: Unlike Plot, DiscontinuousPlot is not Listable, i.e., it will only \
plot one graph at a time.

Options: Sensitivity, ShowLines, LineStyle, ShowHorAsym, Method, NoBreak, \
PrintIntervals";

DiscontinuousPlot::notes = "DiscontinuousPlot is a modified version \
of the built-in Plot command used to graph a function, but it will detect \
discontinuities and draw the graph with breaks at these points.  The sybtax \
is almost the same as for Plot:

DiscontinuousPlot[f, {x, xmin, xmax},options] will graph the function, \
skipping over the discontinuities.
DiscontinuousPlot[f, {x, x0, x1, ..., xn},options] will graph the function, \
breaking it at points x1 through xn-1. It will still skip other \
discontinuities it finds.
Warning: Unlike Plot, DiscontinuousPlot is not Listable, i.e., it will only \
plot one graph at a time.

Options:
	Sensitivity - (Default is 4) Determines how quickly the slope of the \
unbroken graph has to change to be considered a discontinuity. If \
discontinuities are being missed, decrease this value; if too much is \
deleted, increase this vale. Sensitivity can be specified separately for each \
method by Sensitivity->{5,3}, for example. This gives a sensitivity of 5 for \
ChangeInSlopes and 3 for RatioSlopes. (The value actually refers to the power \
of 10 these methods are tested against.)

	ShowLines - (Default is False) Set to True to draw dashed vertical lines at \
discontinuities.

	LineStyle - (Default is {RGBColor[0.1,0.1,1],Dashing[{0.02}]}) Controls the \
appearance of these lines.

	ShowHorAsym - (Default is False) Set to True to compute and draw horizontal \
asymptotes (to the left and right). Uses LineStyle.

	Method - (Default is Auto, which is both methods) Use to select which method \
to use to look for discontinuities. ChangeInSlopes looks for very large \
approximate rate of change of slopes. RatioSlopes looks for very large ratios \
between successive slopes.

	NoBreak - (Default is {}) Used to specify points where the graph should NOT \
break. Usage: NoBreak->{2,5,7} will not break at x=2, 5, or 7, regardless of \
the change in slopes.

	PrintIntervals - (Default is False) Lists the intervals where the \
DiscontinuousPlot command considers the function continuous/discontinuous. \
Useful for determining sensitivity settings.";

\(\(Sensitivity::usage = "Sensitivity - (Default is 4) Determines how quickly the slope of the unbroken graph has to change to be considered a discontinuity. If discontinuities are being missed, decrease this value; if too much is deleted, increase this vale. Sensitivity can be specified separately for each method by Sensitivity->{5,3}, for example. This gives a sensitivity of 5 for ChangeInSlopes and 3 for RatioSlopes. (The value actually refers to the power of 10 these methods are tested against.)";\)
\)
ShowLines::usage = "ShowLines - (Default is False) Set to True to \
draw dashed vertical lines at discontinuities.";

LineStyle::usage = "LineStyle - (Default is \
{RGBColor[0.1,0.1,1],Dashing[{0.02}]}) Controls the appearance of these \
lines.";

ShowHorAsym::usage = "ShowHorAsym - (Default is False) Set to True \
to compute and draw horizontal asymptotes (to the left and right). Uses \
LineStyle.";

Method::usage = "Method - (Default is Auto, which is both methods) \
Use to select which method to use to look for discontinuities. ChangeInSlopes \
looks for very large approximate rate of change of slopes. RatioSlopes looks \
for very large ratios between successive slopes.";

NoBreak::usage = "NoBreak - (Default is {}) Used to specify points \
where the graph should NOT break. Usage: NoBreak->{2,5,7} will not break at \
x=2, 5, or 7, regardless of the change in slopes.";

\(\(PrintIntervals::usage = "PrintIntervals - (Default is False) Lists the intervals where the DiscontinuousPlot command considers the function continuous/discontinuous. Useful for determining sensitivity settings.";\)
\)
Options[DiscontinuousPlot] = {Sensitivity -> {4, 4}, 
          Method -> Auto, ShowLines -> False, 
          ShowHorAsym -> False, 
          LineStyle -> {RGBColor[0.1, 0.1, 1], Dashing[{0.02}]}, 
          NoBreak -> {}, 
          PrintIntervals -> 
            False};

Attributes[DiscontinuousPlot] = 
        HoldAll;

\(DiscontinuousPlot[f_, {x_, ranges__}, opts___Rule] := 
      Module[{alist, graphRegions, slopes, startPlot, coords, 
          xcoord, Δslopes, ratioslopes, splits, splitIntervals, 
          endPlot, sensitive, gridLineStyle, showGridLines, firstpt, 
          secondpt, grids, showIntervals, plotstyleGridLines, plotHorAsym, 
          tempHA, finalGridLines, tempGridLines, methodToUse, 
          useΔslopes, useRatioslopes, 
          noGridLines}, 
alist = {ranges}; 

sensitive = \(Sensitivity /. {opts}\) /. 
            Options[DiscontinuousPlot]; 
methodToUse = \(Method /. {opts}\) /. 
            Options[DiscontinuousPlot]; 
useΔslopes = \(useRatioslopes = 
            False\); 
Which[methodToUse === ChangeInSlopes, 
          useΔslopes = True, methodToUse === RatioSlopes, 
          useRatioslopes = True, Auto === Auto, 
          useΔslopes = \(useRatioslopes = 
              True\)]; 

showGridLines = \(ShowLines /. {opts}\) /. 
            Options[DiscontinuousPlot]; 
gridLineStyle = \(LineStyle /. {opts}\) /. 
            Options[DiscontinuousPlot]; 

plotstyleGridLines = \(GridLines /. {opts}\) /. Options[DiscontinuousPlot]; 
If[
          Length[plotstyleGridLines] < 2, 
          plotstyleGridLines = {{}, {}}]; 

plotHorAsym = \(ShowHorAsym /. {opts}\) /. 
            Options[DiscontinuousPlot]; 
If[
          plotHorAsym && \((Abs[
                  tempHA = 
                    Limit[f, 
                      x -> ∞]] < ∞)\), 
AppendTo[
            plotstyleGridLines[\([2]\)], {N[tempHA], 
              gridLineStyle}]]; 
If[
          plotHorAsym && \((Abs[
                  tempHA = 
                    Limit[f, 
                      x -> \(-∞\)]] < ∞)\), 
AppendTo[
            plotstyleGridLines[\([2]\)], {N[tempHA], 
              gridLineStyle}]]; 

showIntervals = \(PrintIntervals /. {opts}\) /. Options[
              DiscontinuousPlot]; 

noGridLines = \(NoBreak /. {opts}\) /. 
            Options[DiscontinuousPlot]; 
checkGridLine[
            testPoint_] := \(! \((Apply[
                Or, \(Abs[testPoint - #] < 
                      0.01 \((alist[\([Length[alist]]\)] - 
                            alist[\([1]\)])\) &\) /@ 
                  noGridLines])\)\); 


If[Length[alist] > 2, 
          graphRegions = \(DiscontinuousPlot[f, {x, #[\([1]\)], #[\([2]\)]}, 
                  opts, DisplayFunction -> Identity] &\) /@ \ 
              Partition[alist, 2, 
                1]]; 

If[
          Length[alist] == 
            2, 
Block[{$DisplayFunction = 
                Identity}, 

startPlot = 
              Plot[f, {x, alist[\([1]\)], alist[\([2]\)]}, 
                Evaluate[
                  FilterOptions[Plot, opts]]]; 
coords = 
              Transpose[
                Flatten[
                  Cases[startPlot, \ 
                    Line[pts_] :> pts, \ ∞], 
                  1]]; 
slopes = 
              Apply[Subtract, Partition[coords[\([2]\)], 2, 1], {1}]/
                  Apply[Subtract, Partition[coords[\([1]\)], 2, 1], {1}] /. \ 
                0.  -> 
                  10\^\(-15. \); 

Δslopes = 
              Apply[Subtract, Partition[slopes, 2, 1], {1}]/
                Delete[Apply[Subtract, 
                    Partition[coords[\([1]\)], 2, 1], {1}], 
                  Length[slopes]]; 

            ratioslopes = 
              Apply[Divide, Partition[slopes, 2, 1], {1}]/
                Delete[Apply[Subtract, 
                    Partition[coords[\([1]\)], 2, 1], {1}], 
                  Length[slopes]]; 

splits = {1, 
                Length[slopes] + 1}; 
If[
              Length[sensitive] ≠ 2, 
              sensitive = {sensitive, sensitive}]; 

If[
              useΔslopes, 
              MapIndexed[
                If[Abs[#1] > 10^sensitive[\([1]\)] && 
                      checkGridLine[\(coords[\([1]\)]\)[\([1 + #2]\)]], 
                    AppendTo[
                      splits, #2]] &, Δslopes]]; 


            If[useratioSlopes, 
              MapIndexed[
                If[Abs[#1] > 10^sensitive[\([2]\)] && 
                      checkGridLine[\(coords[\([1]\)]\)[\([1 + #2]\)]], 
                    AppendTo[splits, #2]] &, 
                ratioslopes]]; 

splitIntervals = 
              DeleteCases[\(Partition[Sort[Union[Flatten[splits]]], 2, 
                      1] /. \ 
                    1 -> 0\) /. {firstpt_Integer, 
                      secondpt_Integer} -> {firstpt + 1, 
                      secondpt}, {repeat_Integer, 
                  repeat_Integer}]; 


            endPlot = 
              startPlot /. 
                Line[pts_] :> \((\(Line[Take[pts, #\ ]] &\) /@ 
                      splitIntervals)\); 

If[showGridLines && Length[splitIntervals] > 1, 
              grids = \(coords[\([1]\)]\)[\([Apply[Floor[\(#1 + #2\)\/2] &, 
                        Partition[Take[Flatten[splitIntervals], {2, \(-2\)}], 
                          2], {1}]]\)] /. 
                  xcoord_Real -> {xcoord, gridLineStyle}, 
              grids = {}]; 

graphRegions = Show[endPlot, 
                GridLines -> {Union[grids, plotstyleGridLines[\([1]\)]], 
                    plotstyleGridLines[\([2]\)]}];
]
]; 

If[
          Length[alist] > 
            2, 
finalGridLines = \((GridLines /. 
                Options[graphRegions[\([1]\)], 
                  GridLines])\); 
\(AppendTo[
                finalGridLines[\([1]\)], {#, gridLineStyle}] &\) /@ 
            Take[alist, {2, \(-2\)}]; 
\(\((tempGridLines = \((GridLines /. Options[graphRegions[\([#]\)], 
                        GridLines])\); 
finalGridLines[\([1]\)] = 
                  Union[finalGridLines[\([1]\)], 
                    tempGridLines[\([1]\)]])\) &\) /@ 
            Range[2, Length[alist] - 1]
, 
          finalGridLines = \((GridLines /. 
                Options[graphRegions, 
                  GridLines])\)
]; 

Show[graphRegions, GridLines -> finalGridLines, 
          If[showIntervals\  && \ Length[alist] == 2, 
            PlotLabel -> 
              StringForm["Intervals:  ``", 
                MatrixForm[
                  Partition[\(coords[\([1]\)]\)[\([Flatten[
                          splitIntervals]]\)], 2]]], PlotLabel -> ""], 
          If[\((DisplayFunction /. {opts})\) === Identity, 
            DisplayFunction -> Identity, 
            DisplayFunction -> $DisplayFunction]]
]\)

Examples

DiscontinuousPlot[((20  x - 24  (x^2))/(7  (x^2) - 12)), {x, -10, 
        10}, ShowLines -> True, ShowHorAsym -> True]

DiscontinuousPlot[(((x^2) Abs[x - 4])/((x - 4) ((x^2) - 5))), {x, -5, 6}]

DiscontinuousPlot[(((x^2) Abs[x - 4])/((x - 4) ((x^2) - 5))), {x, -5, 0, 8}, ShowLines -> True, ShowHorAsym -> True, 
      Frame -> True, Axes -> False]

You can add your own gridlines in addition to the automatically generated ones:

DiscontinuousPlot[Sec[x], {x, -2 π, 2  π}, 
      ShowLines -> True, 
      GridLines -> {{}, {{1, {RGBColor[0.1, 0.1, 0.1], 
                Dashing[{0.04}]}}, {-1, {RGBColor[0.1, 0.1, 0.1], 
                Dashing[{0.04}]}}}}]

DiscontinuousPlot[Tan[(x^2)]/Cot[x], {x, -π, π}, 
      Sensitivity -> 5.8, ShowLines -> True, Axes -> False, 
      Frame -> True]

DiscontinuousPlot works fine with piecewise-defined functions. (In the graph below, I also set the sensitivity a bit higher than normal to minimize the breaks between the intervals. The PrintInterval options helps in adjusting this.)

pieces[x_] := 
      Which[x < -2, Sin[6  x], x > -2 && x ≤ 0, (x^2), 
        x > 0 && x < 3, Tan[x], x ≥ 3, 
        (1/(((x - 5))^2))]

DiscontinuousPlot[pieces[x], {x, -5, 8}, PlotPoints -> 100, 
      Frame -> True, ShowHorAsym -> True, Axes -> False, 
      ShowLines -> True, 
      LineStyle -> {RGBColor[0.8, 0.8, 0.8], Dashing[{0.01}]}, 
      PrintIntervals -> True, Sensitivity -> 4.2]

DiscontinuousPlot can also be useful when there are no discontinuities:

DiscontinuousPlot[ArcTan[x], {x, -10, 10}, 
      ShowHorAsym -> True, PlotRange -> {-2, 2}]