Function transformations

Shifting, stretching, and reflecting functions and playing with parameters

- Marcus McGuff, Austin Community College

In the "Transform base function" tab, choose a base function, then click on the "Shift", "Stretch", or "Reflection" button.  Then grab the graph with your mouse and drag to transform the graph.  In shift mode, just drag the graph to where you want it.  In stretch mode, drag upwards for a vertical stretch, downwards for a vertical shrink, right for a horizontal stretch, and left for a horizontal shrink.  (I'll let you guess what dragging in different directions does in reflection mode...)  You can see the equation for your "transformed" graph, then make additional transformations and see how they combine together to change the equation.  (If you hover over a graph, it will tell you the transformations needed to draw it.)

In the "Investigate the effects of parameters" tab, choose a function with different "parameters" in it (a, b, c, h, k, etc.), then use the sliders on the left to see how the graph changes in response to varying that parameter.  

In either tab, you can click on "Pan graph" to move to another part of the graph to look at, zoom in, or zoom out.  If you are using the full version of Mathematica, you can also click on the little triangles next to the functions to open up a box to enter your own functions.  Have fun...

paramFunctions={
{ToBoxes[a (((x-h))^2)+k]},
{ToBoxes[a (x^2)+b x+c]},
{ToBoxes[ m x+b]},
{ToBoxes[ a Abs[x-h]+k]},
{ToBoxes[a (Sqrt[b (x-h)])+k]},
{ToBoxes[a (b^x)]},
{ToBoxes[A Sin[ω x-ϕ]+k]}
};

baseFunctions={
{ToBoxes[(x^2)]},
{ToBoxes[(x^3)]},
{ToBoxes[ x]},
{ToBoxes[ Abs[x]]},
{ToBoxes[ (Sqrt[x])]},
{ToBoxes[ (x^(1/3))]},
{ToBoxes[ (1/x)]},
{ToBoxes[ (1/(x^2))]},
{ToBoxes[(E^x)]},
{ToBoxes[(2^x)]},
{ToBoxes[Log[x]]},
{ToBoxes[Log[10,x]]},
{ToBoxes[Sin[x]]},
{ToBoxes[Cos[x]]},
{ToBoxes[Tan[x]]},
{ToBoxes[Cot[x]]},
{ToBoxes[Sec[x]]},
{ToBoxes[Csc[x]]}
};

transformFunctions[eq_,indVar_,hshift_,vshift_,hstretch_,vstretch_,hreflect_,vreflect_]:=(vreflect*vstretch*(eq)+vshift)/.indVar->(hreflect*indVar)/.indVar->(indVar/hstretch)/.indVar->(indVar-hshift);

scaleToStretch[oldStretch_,scale_]:=If[scale≥0,oldStretch*(5scale+1),oldStretch*(scale+1)];

listTransforms[shift_,stretch_,reflect_]:=Module[{return={},shifted={},stretched={},reflected={}},

If[shift=!={0,0},shifted={"Shifted:  "}];
If[shift[[1]]=!=0,AppendTo[shifted,Abs@shift[[1]]]];
If[shift[[1]]>0,AppendTo[shifted,"→  "]];
If[shift[[1]]<0,AppendTo[shifted,"←  "]];
If[shift[[2]]=!=0,AppendTo[shifted,Abs@shift[[2]]]];
If[shift[[2]]>0,AppendTo[shifted,"↑  "]];
If[shift[[2]]<0,AppendTo[shifted,"↓  "]];

If[stretch=!={1.,1.},stretched={"Stretched:  "}];
If[stretch[[1]]=!=1.,AppendTo[stretched,stretch[[1]]]];
If[stretch[[1]]>1,AppendTo[stretched,"Hor"]];
If[stretch[[1]]<1,AppendTo[stretched,"Hor"]];
If[stretch[[2]]=!=1.,AppendTo[stretched,stretch[[2]]]];
If[stretch[[2]]>1,AppendTo[stretched,"Ver"]];
If[stretch[[2]]<1,AppendTo[stretched,"Ver"]];

If[reflect=!={1,1},reflected={"Reflected:  "}];
If[reflect[[1]]=!=1,AppendTo[reflected,"Hor"]];
If[reflect[[2]]=!=1,AppendTo[reflected,"Ver"]];

If[shifted=!={},AppendTo[return,shifted]];
If[stretched=!={},AppendTo[return,stretched]];
If[reflected=!={},AppendTo[return,reflected]];

Return[return]
]

Manipulate[

Refresh[
If[var===Null,
Clear[var];
var["highrect"]={};
var["computePlotrange"]=plotrange];
If[paramText===Null,Clear[x,y];
paramText=paramFunctions[[1,1]]];
If[baseText===Null,Clear[x,y];
baseText=baseFunctions[[1,1]]];
readMouse[startOrEnd_]:=(var[startOrEnd]=MousePosition["Graphics"];
var[startOrEnd<>"Scaled"]=MousePosition["GraphicsScaled"]),
None];

(* Convert input text boxes into expressions in TraditionalForm*)
paramEq=ToExpression[paramText,TraditionalForm];
baseEq=ToExpression[baseText,TraditionalForm];

Refresh[If[graphList=={}||!MemberQ[graphList,Tooltip[baseEq,_]],
graphList={Tooltip[baseEq,"Base graph"]};shiftList={0.,0.};stretchList={1.,1.};reflectList={1,1}],TrackedSymbols:>{baseText,baseEq}];

(* Finds parameters in paramEq only, then writes the replacement rules for them; can specify exclusion cases here; set default value for all parameters here with {_->1.} *)
params=SortBy[Thread[Rule[#,Replace[#,Join[params,{_->1.}],1]]],First]&[Cases[Level[{paramEq},-1],s_Symbol/;And[FreeQ[{x},s],FreeQ[Attributes[s],Locked|Constant]]]];

Switch[ctrlPane,
1,

Grid[{{Dynamic@Text@Style[
Grid[{{"Base graph: f(x) = ",TraditionalForm[baseEq]},
{"Final graph: g(x) = ",TraditionalForm[Chop@graphList[[Length[graphList]]]]}}]]},
{EventHandler[
Tooltip[
Dynamic@Graphics[{

Table[First@Plot[Evaluate@graphList[[k]],Evaluate@Prepend[plotrange[[1]],x],PlotRange->Dynamic@Evaluate@plotrange,Exclusions->If[vAsymYN,{1/(0.00000001+First[graphList[[k]]])==0},Automatic],ExclusionsStyle->If[vAsymYN,Directive[Opacity[0.5],Dashing[Tiny]],None],PlotStyle->{Thickness[0.003],ColorData[3,"ColorList"][[k]]}],{k,1,Length[graphList]}],

If[drawYN,
Text[Style[Grid[listTransforms[If[snapYN,Round[var["shiftList"],0.1],var["shiftList"]],If[snapYN,Round[var["stretchList"],0.1],var["stretchList"]],var["reflectList"]]],FontSize->24,FontColor->Directive[Opacity[1],White],Background->Directive[Opacity[0.8],Blue]],{0,1}],
Dynamic[var["highrect"]]],

If[drawYN,
(* Draw the graph during transforms *)

Dynamic@First@Plot[Evaluate[var["moving"]=
transformFunctions[baseEq,x,var["shiftList"][[1]],var["shiftList"][[2]],var["stretchList"][[1]],var["stretchList"][[2]],var["reflectList"][[1]],var["reflectList"][[2]]]],
Evaluate@Prepend[plotrange[[1]],x],PlotRange->Dynamic@Evaluate@plotrange,PlotStyle->{Thickness[0.006],Red},
Exclusions->If[vAsymYN,{1/(var["moving"])==0},Automatic],ExclusionsStyle->If[vAsymYN,Directive[Opacity[0.5],Dashing[Tiny]],None]],

(* Highlight rectangle for zooming *)
Dynamic[var["highrect"]]]

},
Frame->False,ImageSize-> 450,GridLines->Automatic,AxesOrigin->{0,0},Axes->True,AxesLabel->{x,y},GridLinesStyle->Directive[Blue,Dashed],AspectRatio->1,PlotRange->Dynamic@Evaluate@plotrange],
Dynamic[Text@Style[MousePosition["Graphics"],Medium]]],

{"MouseClicked":>
Switch[graphMode,
"Pan graph",None,
"Zoom In",None,
"Zoom Out",None,
"special",None],
"MouseDown":>
(readMouse["start"];
Switch[graphMode,
"Shift",drawYN=True,
"Stretch",drawYN=True,
"Reflection",drawYN=True,
"Pan graph",None,
"Zoom In",None,
"Zoom Out",None,
"special",None];
If[graphMode=="Shift"||graphMode=="Stretch"||graphMode=="Reflection",
var["shiftList"]=shiftList;var["stretchList"]=stretchList;var["reflectList"]=reflectList]
),
"MouseDragged":>
(readMouse["end"];
Switch[graphMode,
"Shift",var["shiftList"]=shiftList+(var["end"]-var["start"]),
"Stretch",
If[Abs[(var["endScaled"]-var["startScaled"])[[1]]]>Abs[(var["endScaled"]-var["startScaled"])[[2]]],var["stretchList"]={scaleToStretch[stretchList[[1]],(var["endScaled"]-var["startScaled"])[[1]]],stretchList[[2]]},
var["stretchList"]={stretchList[[1]],scaleToStretch[stretchList[[2]],(var["endScaled"]-var["startScaled"])[[2]]]}],
"Reflection",
If[Abs[(var["endScaled"]-var["startScaled"])[[1]]]>Abs[(var["endScaled"]-var["startScaled"])[[2]]],
var["reflectList"]=reflectList*{-1,1},
var["reflectList"]=reflectList*{1,-1}],
"Pan graph",(plotrange=plotrange-Apply[Subtract,-plotrange,1](var["endScaled"]-var["startScaled"]);
readMouse["start"]),
"Zoom In",var["highrect"]={Opacity[0.3],Blue,Rectangle[var["start"],var["end"]]},
"Zoom Out",var["highrect"]={Opacity[0.3],Red,Rectangle[var["start"],var["end"]]},
"special",None]
),
"MouseUp":>(Switch[graphMode,
"Shift",shiftList=If[snapYN,Round[var["shiftList"],0.1],var["shiftList"]],
"Stretch",stretchList=If[snapYN,Round[var["stretchList"],0.1],var["stretchList"]],
"Reflection",reflectList=var["reflectList"],
"Pan graph",None,
"Zoom In",If[var["highrect"]=!={},
plotrange=Sort/@Transpose[var/@{"start","end"}]],
"Zoom Out",If[var["highrect"]=!={},
plotrange=1/Abs[(var["endScaled"]-var["startScaled"])]plotrange],
"special",None];
var["highrect"]={};
drawYN=False;
readMouse["start"];
If[graphMode=="Shift"||graphMode=="Stretch"||graphMode=="Reflection",
AppendTo[graphList,
Tooltip[transformFunctions[baseEq,x,shiftList[[1]],shiftList[[2]],stretchList[[1]],stretchList[[2]],reflectList[[1]],reflectList[[2]]],Text@Grid[Prepend[listTransforms[shiftList,stretchList,reflectList],{"f(x) = ",transformFunctions[baseEq,x,shiftList[[1]],shiftList[[2]],stretchList[[1]],stretchList[[2]],reflectList[[1]],reflectList[[2]]],SpanFromLeft,SpanFromLeft}]]]]];
var["computePlotrange"]=plotrange)}]
},
{Text@Grid[Prepend[If[Length[graphList]>1,listTransforms[shiftList,stretchList,reflectList],{}],{"Last transformation",SpanFromLeft,SpanFromLeft,SpanFromLeft}],Alignment->Center,Dividers->{False,{False,True,False,False}},ItemStyle->Directive[Black,10,FontFamily->"Times"]]}}],

2,
If[!MemberQ[{"Pan graph","Zoom In","Zoom Out"},graphMode],graphMode="Pan graph"];

Grid[{{Text@Style[Row[{"\!\(\*Cell[TextData[Cell[BoxData[
FormBox[
RowBox[{"f", " ", 
RowBox[{"(", "x", ")"}]}], TraditionalForm]]]]]\) = ",TraditionalForm[paramEq/.params]}]]},
{EventHandler[
Tooltip[
Graphics[{

Dynamic@First@Plot[paramEq/.params,Evaluate@Prepend[plotrange[[1]],x],PlotRange->Dynamic@Evaluate@plotrange,PlotStyle->Red,
Exclusions->If[vAsymYN,{1/(paramEq/.params)==0},Automatic],ExclusionsStyle->If[vAsymYN,Directive[Opacity[0.5],Dashing[Tiny]],None]],

(* Highlight rectangle for zooming *)
Dynamic[var["highrect"]]
},
Frame->False,ImageSize-> 450,GridLines->Automatic,AxesOrigin->{0,0},Axes->True,AxesLabel->{x,y},GridLinesStyle->Directive[Blue,Dashed],AspectRatio->1,PlotRange->Dynamic@Evaluate@plotrange],
Dynamic[Text@Style[MousePosition["Graphics"],Medium]]],

{"MouseClicked":>
Switch[graphMode,
"Pan graph",None,
"Zoom In",None,
"Zoom Out",None,
"special",None],
"MouseDown":>
(readMouse["start"];
Switch[graphMode,
"Pan graph",None,
"Zoom In",None,
"Zoom Out",None,
"special",None]
),
"MouseDragged":>
(readMouse["end"];
Switch[graphMode,
"Pan graph",(plotrange=plotrange-Apply[Subtract,-plotrange,1](var["endScaled"]-var["startScaled"]);
readMouse["start"]),
"Zoom In",var["highrect"]={Opacity[0.3],Blue,Rectangle[var["start"],var["end"]]},
"Zoom Out",var["highrect"]={Opacity[0.3],Red,Rectangle[var["start"],var["end"]]},
"special",None]),
"MouseUp":>(Switch[graphMode,
"Pan graph",None,
"Zoom In",If[var["highrect"]=!={},
plotrange=Sort/@Transpose[var/@{"start","end"}]],
"Zoom Out",If[var["highrect"]=!={},
plotrange=1/Abs[(var["endScaled"]-var["startScaled"])]plotrange],
"special",None];
var["highrect"]={};
readMouse["start"];
var["computePlotrange"]=plotrange)}]
},
{Text@Grid[{{"Mouse actions",SpanFromLeft},{"Drag - Move graph",SpanFromLeft},
{"Alt(Cmd)/Drag - Zoom in","","Shift/Alt(Cmd)/Drag - Zoom out"}},Alignment->Center,Dividers->{False,{False,True,False,False}},ItemStyle->Directive[Black,10,FontFamily->"Times"]]}}]

],

{{ctrlPane,1,""},{
1->"Transform base functions",
2->"Investigate the effects of parameters"}},

Delimiter,

PaneSelector[
{
1->Row[{
OpenerView[{
Row[{
Control[{{baseText,,"\!\(\*Cell[TextData[Cell[BoxData[
FormBox[Cell[TextData[Cell[BoxData[
FormBox[
RowBox[{"f", " ", 
RowBox[{"(", "x", ")"}]}], TraditionalForm]]]]], TraditionalForm]]]]]\) = "},(#->ToExpression[#,StandardForm,TraditionalForm])&/@baseFunctions[[All,1]],ControlType->PopupMenu}]}],
Dynamic@If[$LicenseType≠"Player",
Row[{Control[{{baseText,,"\!\(\*Cell[TextData[Cell[BoxData[
FormBox[Cell[TextData[Cell[BoxData[
FormBox[
RowBox[{"f", " ", 
RowBox[{"(", "x", ")"}]}], TraditionalForm]]]]], TraditionalForm]]]]]\) = "},TraditionalForm[InputField[Dynamic[baseText],Boxes]]&}]}],Text@Style[
"Extra input options not available in the free Mathematica Player; to enter your own equations, you must be using a full version of Mathematica",Italic]]}],
"       ",
Button["Default Range",plotrange=plotDefault,BaseStyle->Small,ImageSize->All],
"       ",
Button["Clear Transforms",
graphList={baseEq};
shiftList={0.,0.};
stretchList={1.,1.};
reflectList={1,1},
BaseStyle->Small,ImageSize->All]}],
2-> Row[{
OpenerView[{
Row[{
Control[{{paramText,,"\!\(\*Cell[TextData[Cell[BoxData[
FormBox[Cell[TextData[Cell[BoxData[
FormBox[
RowBox[{"f", " ", 
RowBox[{"(", "x", ")"}]}], TraditionalForm]]]]], TraditionalForm]]]]]\) = "},(#->ToExpression[#,StandardForm,TraditionalForm])&/@paramFunctions[[All,1]],ControlType->PopupMenu}]}],
Dynamic@If[$LicenseType≠"Player",
Row[{Control[{{paramText,,"\!\(\*Cell[TextData[Cell[BoxData[
FormBox[Cell[TextData[Cell[BoxData[
FormBox[
RowBox[{"f", " ", 
RowBox[{"(", "x", ")"}]}], TraditionalForm]]]]], TraditionalForm]]]]]\) = "},TraditionalForm[InputField[Dynamic[paramText],Boxes]]&}]}],Text@Style[
"Extra input options not available in the free Mathematica Player; to enter your own equations, you must be using a full version of Mathematica",Italic]]}],
"       ",
Button["Default Range",plotrange=plotDefault,BaseStyle->Small,ImageSize->All]}]
},
Dynamic[ctrlPane],ImageSize->All],

Delimiter,

PaneSelector[
{
1->Row[{Control@{{graphMode,"Shift","Mouse mode: "},{"Shift","Stretch","Reflection","Pan graph","Zoom In","Zoom Out"},ControlType->Setter},
"     ",
Column[{Control@{{snapYN,True,"Snap to grid"},{True,False}},
Control@{{vAsymYN,False,"Vertical Asymptotes"},{True,False}}}]}],
2-> Row[{"       ",Control@{{graphMode,"Pan graph","Mouse mode: "},{"Pan graph","Zoom In","Zoom Out"},ControlType->Setter},
Control@{{vAsymYN,False,"Vertical Asymptotes"},{True,False}}}]
},
Dynamic[ctrlPane],ImageSize->All],

Item[Dynamic@If[ctrlPane==2,Column[{"Parameters",Dynamic[Grid[MapIndexed[{params[[First@#2,1]],Manipulator[Dynamic[params[[First@#2,2]]],{-5,5,0.01},ImageSize->Small,Appearance->{"Labeled","Open"}]}&,params]]]}],""],ControlPlacement->Right],

Delimiter,
(* Produces sliders dynamically for parameters *)

{paramEq,ControlType->None},
{baseEq,ControlType->None},
{{params,{}},ControlType->None},
{{plotDefault,{{-5,5},{-5,5}}},ControlType->None},
{{plotrange,plotDefault},ControlType->None},

(* List of cummulative transforms - {hor,vert}; add new shifts to shifList, multiply by new stretches, multiply reflectList by -1 for each new flip *)
{{shiftList,{0.,0.}},ControlType->None},
{{stretchList,{1.,1.}},ControlType->None},
{{reflectList,{1,1}},ControlType->None},
{{graphList,{}},ControlType->None},

(* Set to True while transforming graphs *)
{{drawYN,False},{False,True},ControlType->None},

(* temporary holding variable *)
{var,ControlType->None},

SaveDefinitions->True,Alignment->Center]