Here is a new version of the script that doesn't require a temporary file to do the computation. It uses Laszlo's "Monster" expression script. However, since it parses the expression each time, it calculates much slower than the temporary script with its values hardcoded into it.
Code:
;;;;;;;;;;;;;;;
;
; Function Grapher by jonny
; (with code by Laszlo)
;
;
;;;Functions;;;
;
;GraphCreate(id,x,y,w,h,pre="")
; Initializes the graph, sets its options,
; and draws it at the specified location.
; Returns 1 if it's successful, 0 if there's
; a logical error (e.g. bad option), and blank
; if there's a problem allocating memory.
;
; id:
; The window ID (ahk_id) of the window to
; draw the graph on.
;
; x:
; The x-coordinate to draw the graph at.
;
; y:
; The y-coordinate to draw the graph at.
;
; w:
; The width of the graph.
;
; h:
; The height of the graph.
;
; pre:
; The prefix for the global option variables,
; explained further below.
; For example, if pre = "GraphOpts_", then it
; will read GraphOpts_xScl for the xScl option
;
;
;Graph(equation,color=defGraphColor)
; Computes and draws the given equation on
; the graph. The equation must be in terms
; of the variable 'x', and it can contain any
; standard AHK operators and math functions.
; Returns 1 if it's successful, and 0 if
; the graph doesn't exist or if there's a
; problem with the computation.
;
; equation:
; The equation to use. This must be in the form
; of a string, NOT a bare expression; however,
; it will eventually be parsed, so it must be
; valid as an expression.
;
; color:
; The color to draw the equation in. This must
; be in RGB form (0xRRGGBB), not an HTML color.
; If this parameter is unspecified or not a valid
; RGB color, the defGraphColor option will be used.
;
;
;GraphClear()
; Clears all equations off the graph.
;
;
;GraphDraw()
; Redraws the graph. Shouldn't be necessary to
; use, since GraphCreate binds this to WM_PAINT.
;
;
;GraphDestroy()
; Frees all memory associated with the graph.
; You MUST call this before exiting the script,
; or there will be a memory leak.
; Note that this will not remove the graph's image
; from the display. The window must be redrawn
; to visually clear it.
;
;
;
;;;Options;;;
;
; To use these, set any of them as global variables
; with a consistent prefix (required). Then pass
; the prefix to GraphCreate as a string, via the
; 'pre' parameter. Any options left unset will get
; their default values.
;
;
;paperColor (0xFFFFFF - white)
; The color of the graph's background, or the
; "graph paper." Must be a full RGB color.
;
;axisColor (0x000000 - black)
; The color of the x and y axes, if visible.
; Must be a full RGB color.
;
;gridColor (0xDFDFDF - light grey)
; The color of the grid beneath the graph.
; Must be a full RGB color.
;
;defGraphColor (0x0000FF - blue)
; The default color for Graph(), if the 'color'
; parameter isn't valid or specified.
;
;lineWidth (3)
; The width of the lines generated by Graph().
; Must be greater than or equal to zero.
;
;xScl (1)
; The scale of the x-plane of the grid, in
; logical units. For instance, a value of 10
; would draw a vertical line wherever x is
; a multiple of 10, and a value of 0.5 would
; draw two vertical lines for every integral
; x-value. Must be greater than zero.
;
;yScl (1)
; The scale of the y-plane of the grid, in
; logical units. Same as above, except for
; the horizontal lines with y. Must be greater
; than zero.
;
;xMin (-10)
; The smallest x-value displayed on the graph,
; or the left edge of its viewable area. Must
; be less than xMax.
;
;yMin (-10)
; The smallest y-value displayed on the graph,
; or the bottom edge of its viewable area. Must
; be less than yMax.
;
;xMax (10)
; The largest x-value displayed on the graph,
; or the right edge of its viewable area. Must
; be greater than xMin.
;
;yMax (10)
; The largest y-value displayed on the graph,
; or the top edge of its viewable area. Must be
; greater than yMin.
;
;
;
;;;Remarks;;;
;
; Use this at your own risk. I've tested it a lot,
; but there's still a chance it'll cause a serious
; memory leak.
;
; Avoid naming any global variables with the prefix
; '__graph_' while the graph is created, since these
; functions use it to communicate.
;
; Graph() uses two temporary files which it creates
; in A_Temp, so don't be alarmed if you notice that
; your script is suddenly opening weird files. (It
; deletes them when it's finished, so they'll only
; consume space for a few milliseconds.)
;
GraphCreate(WindowID,xCoord,yCoord,Width,Height,OptPre="")
{
global
local paperColor,axisColor,gridColor,xScl,yScl,xMin,yMin
,xMax,yMax,Pen,Brush
if __graph_Exists
return 0
;options
;background color (white)
paperColor := %OptPre%paperColor ? %OptPre%paperColor : 0xFFFFFF
;color for axes (black)
axisColor := %OptPre%axisColor ? %OptPre%axisColor : 0x000000
;grid color (grey)
gridColor := %OptPre%gridColor ? %OptPre%gridColor : 0xDFDFDF
;default color for new equations (blue)
__graph_color := %OptPre%defGraphColor ? %OptPre%defGraphColor : 0x0000FF
;width of equations' graphs
__graph_lineWidth := %OptPre%lineWidth ? %OptPre%lineWidth : 3
;scale of grid for the x plane
xScl := %OptPre%xScl ? %OptPre%xScl : 1
;scale of grid for the y plane
yScl := %OptPre%yScl ? %OptPre%yScl : 1
;lowest x-value shown on graph
xMin := %OptPre%xMin ? %OptPre%xMin : -10
;lowest y-value shown on graph
yMin := %OptPre%yMin ? %OptPre%yMin : -10
;highest x-value shown on graph
xMax := %OptPre%xMax ? %OptPre%xMax : 10
;highest y-value shown on graph
yMax := %OptPre%yMax ? %OptPre%yMax : 10
;sanity checks
if (paperColor > 0xFFFFFF || paperColor < 0
|| axisColor > 0xFFFFFF || axisColor < 0
|| gridColor > 0xFFFFFF || gridColor < 0
|| __graph_color > 0xFFFFFF || __graph_color < 0
|| __graph_lineWidth < 0
|| xScl <= 0 || yScl <= 0
|| xMin >= xMax || yMin >= yMax)
{
__graph_color =
__graph_lineWidth =
return 0
}
__graph_X := xCoord
__graph_Y := yCoord
__graph_W := Width
__graph_H := Height
;compute how many units on the graph each pixel
;corresponds to
__graph_xUnit := Width / (xMax - xMin)
__graph_yUnit := Height / (yMax - yMin)
;how far, in graph units, the left
;edge is from the origin
__graph_leftDist := xMin * __graph_xUnit
;how far the top edge is from the
;origin, this time in terms of y
__graph_topDist := yMax * __graph_yUnit
;Device Context for the window to be drawn to
__graph_WindowDC := DllCall("GetDC", UInt,WindowID)
if not __graph_WindowDC
return
;first memory DC, containing the "graph paper,"
;the background of the graph
__graph_PaperDC := DllCall("CreateCompatibleDC", UInt,WindowDC)
if not __graph_PaperDC
return
;create a bitmap on the DC
__graph_PaperDC_BM := DllCall("CreateCompatibleBitmap"
, UInt,__graph_WindowDC, UInt,Width, UInt,Height)
DllCall("SelectObject", UInt,__graph_PaperDC
, UInt,__graph_PaperDC_BM)
;second Memory DC, containing the finished product
;of the paper with the graphed equations on it
__graph_MemoryDC := DllCall("CreateCompatibleDC", UInt,WindowDC)
if not __graph_MemoryDC
return
;create a bitmap on the DC
__graph_MemoryDC_BM := DllCall("CreateCompatibleBitmap"
, UInt,__graph_WindowDC, UInt,Width, UInt,Height)
DllCall("SelectObject", UInt,__graph_MemoryDC
, UInt,__graph_MemoryDC_BM)
;set up the graph paper
;first the background and border
Pen := DllCall("CreatePen", UInt,0, UInt,0, UInt,paperColor)
DllCall("SelectObject", UInt,__graph_PaperDC, UInt,Pen)
Brush := DllCall("CreateSolidBrush", UInt,paperColor)
DllCall("SelectObject", UInt,__graph_PaperDC, UInt,Brush)
DllCall("Rectangle", UInt,__graph_PaperDC, UInt,0, UInt,0
, UInt,Width, UInt,Height)
DllCall("DeleteObject", UInt,Pen)
DllCall("DeleteObject", UInt,Brush)
;now the grid
Pen := DllCall("CreatePen", UInt,0, UInt,0, UInt,gridColor)
DllCall("SelectObject", UInt,__graph_PaperDC, UInt,Pen)
;vertical lines (x-plane)
Loop
{
if (A_Index >= ( (xMax - xMin) / xScl ))
break
DllCall("MoveToEx", UInt,__graph_PaperDC
, UInt,Round(A_Index*__graph_xUnit*xScl), UInt,0, UInt,0)
DllCall("LineTo", UInt,__graph_PaperDC
, UInt,Round(A_Index*__graph_xUnit*xScl), UInt,Height)
}
;horizontal lines (y-plane)
Loop
{
if (A_Index >= ( (yMax - yMin) / yScl ))
break
DllCall("MoveToEx", UInt,__graph_PaperDC
, UInt,0, UInt,Round(A_Index*__graph_yUnit*yScl), UInt,0)
DllCall("LineTo", UInt,__graph_PaperDC
, UInt,Width, UInt,Round(A_Index*__graph_yUnit*yScl))
}
DllCall("DeleteObject", UInt,Pen)
;axes
Pen := DllCall("CreatePen", UInt,0, UInt,0, UInt,axisColor)
DllCall("SelectObject", UInt,__graph_PaperDC, UInt,Pen)
if (xMin < 0 and xMax > 0)
{
DllCall("MoveToEx", UInt,__graph_PaperDC
, UInt,Round((-xMin)*__graph_xUnit), UInt,0, UInt,0)
DllCall("LineTo", UInt,__graph_PaperDC
, UInt,Round((-xMin)*__graph_xUnit), UInt,Height)
}
if (yMin < 0 and yMax > 0)
{
DllCall("MoveToEx", UInt,__graph_PaperDC
, UInt,0, UInt,Round(yMax*__graph_yUnit), UInt,0)
DllCall("LineTo", UInt,__graph_PaperDC
, UInt,Width, UInt,Round(yMax*__graph_yUnit))
}
DllCall("DeleteObject", UInt,Pen)
__graph_Exists := true
GraphClear()
;WM_PAINT: 0x0F
OnMessage(0x0F,"GraphDraw")
return 1
}
Graph(Equation,Color = -1)
{
global
local Pen,FirstVal,GraphSpec,R,G,B
if (!__graph_Exists or !A_AhkPath)
return 0
if (Color < 0 or Color > 0xFFFFFF)
Color := __graph_color
;CreatePen expects a BGR-formatted COLORREF
R := Color & 0x0000FF
G := Color & 0x00FF00
B := Color & 0xFF0000
R <<= 16
B >>= 16
Color := R | G | B
;px/py: Positional coordinates used to
; place the graph
;
;x/y: Actual coordinates that the function
; is computed in
;
;Equation must be in terms of 'x'
Loop %__graph_W%
{
px := A_Index
x := (px + __graph_leftDist) / __graph_xUnit
y := Eval("x:=" . x . "; " . Equation) * __graph_yUnit
py := Round(__graph_topDist - y)
GraphSpec .= py . ";"
}
Pen := DllCall("CreatePen", UInt,0, UInt,__graph_lineWidth, UInt,Color)
DllCall("SelectObject", UInt,__graph_MemoryDC, UInt,Pen)
StringTrimRight,GraphSpec,GraphSpec,1
FirstVal := InStr(GraphSpec,";")
DllCall("MoveToEx", UInt,__graph_MemoryDC, UInt,1, UInt,SubStr(GraphSpec,1,FirstVal-1), UInt,0)
StringTrimLeft,GraphSpec,GraphSpec,FirstVal
Loop,Parse,GraphSpec,;
DllCall("LineTo", UInt,__graph_MemoryDC, UInt,A_Index + 1, UInt,A_LoopField)
DllCall("DeleteObject", UInt,Pen)
GraphDraw()
return 1
}
GraphClear()
{
global
if not __graph_Exists
return
DllCall("BitBlt", UInt,__graph_MemoryDC, UInt,0, UInt,0
, UInt,__graph_W, UInt,__graph_H, UInt,__graph_PaperDC
, UInt,0, UInt,0, UInt,0x00CC0020)
GraphDraw()
}
GraphDraw()
{
global
if not __graph_Exists
return
DllCall("BitBlt", UInt,__graph_WindowDC, UInt,__graph_X
, UInt,__graph_Y, UInt,__graph_W, UInt,__graph_H
, UInt,__graph_MemoryDC, UInt,0, UInt,0, UInt,0x00CC0020)
}
GraphDestroy()
{
global
;WM_PAINT: 0x0F
OnMessage(0x0F,"")
DllCall("DeleteObject", UInt,__graph_PaperDC)
DllCall("DeleteObject", UInt,__graph_PaperDC_BM)
DllCall("DeleteObject", UInt,__graph_MemoryDC)
DllCall("DeleteObject", UInt,__graph_MemoryDC_BM)
DllCall("ReleaseDC", UInt,0, UInt,__graph_WindowDC)
__graph_PaperDC =
__graph_PaperDC_BM =
__graph_MemoryDC =
__graph_MemoryDC_BM =
__graph_WindowDC =
__graph_X =
__graph_Y =
__graph_W =
__graph_H =
__graph_color =
__graph_lineWidth =
__graph_xUnit =
__graph_yUnit =
__graph_leftDist =
__graph_topDist =
__graph_Exists =
}
;; Taken from Laszlo's "Monster" expression evaluation script (version 20070301):
;; http://www.autohotkey.com/forum/viewtopic.php?t=17058
Eval(x) { ; PRE/POST PROCESSING (Global Dynamic vars, Array y: y1, y2, y3)
Local FORM, i, z
SetFormat Integer, D ; decimal intermediate results!
RegExMatch(x, "\$(b|h|x|\d*)", y)
FORM := y1 ; HeX, Bin, .{digits} output format
SetFormat FLOAT, % y1 >= 0 ? "0." . y1 : 0.6 ; Default = 6 decimal
StringReplace x, x, %y% ; remove ${num}
x := RegExReplace(x,"\s*") ; remove whitespace
If RegExMatch(x, "(.*);(.*)", u) {
z := u2 ; workaround local-global mixup with arrays
Eval(u1)
Return Eval(z)
}
StringGetPos i, x, := ; execute leftmost ":=" operator
If (i >= 0) {
z := "x" . SubStr(x,1,i) ; user vars start with x to avoid name conflicts
Return %z% := Eval(SubStr(x,3+i))
}
StringReplace x, x,`%, \, All ; % -> \ for MOD
StringReplace x, x, **, @, All ; ** -> @ for easier process
Loop {
If !RegExMatch(x, "i)(.*)(0x[a-f\d]*)(.*)", y)
Break
x := y1 . y2+0 . y3 ; convert hex numbers to decimal
}
Loop {
If !RegExMatch(x, "(.*)'([01]*)(.*)", y)
Break
x := y1 . FromBin(y2) . y3 ; convert binary numbers to decimal: sign = first bit
}
x := RegExReplace(x,"([a-z_A-Z]\w*)([^\w\(\]]|$)","%x$1%$2") ; var -> %var%; func(.., [op] remains
Transform x, Deref, %x% ; dereference all %var%
StringReplace x, x, -, #, All ; # = subtraction, different from sign
x := RegExReplace(x, "(^|\(|\])\+", "$1") ; (+x -> (x, ]+x -> ]x
Loop { ; find innermost (..)
If !RegExMatch(x, "(.*)\(([^\(\)]*)\)(.*)", y)
Break
x := y1 . Eval@(y2) . y3 ; replace "(x)" with value of x
}
If FORM is Digit ; return result: no more (..)
Return Eval@(x)
If FORM = b ; binary
Return ToBin(Eval@(x))
x := Eval@(x) ; Hex
SetFormat Integer, Hex
Return x+0
}
Eval@(x) { ; EVALUATE PRE-PROCESSED EXPRESSIONS
IfEqual x,, Return 0 ; empty = 0
StringGetPos i, x, ||, R ; execute rightmost || operator
IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) || Eval@(SubStr(x,3+i))
StringGetPos i, x, &&, R ; execute rightmost && operator
IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) && Eval@(SubStr(x,3+i))
; execute rightmost =, <> operator
RegExMatch(x, "(.*)([^\<\>])(\<\>|=)(.*)", y)
IfEqual y3,=, Return Eval@(y1 . y2) = Eval@(y4)
IfEqual y3,<>, Return Eval@(y1 . y2) <> Eval@(y4)
; execute rightmost <,>,<=,>= operator
RegExMatch(x, "(.*)([^\<\>])(\<=?|\>=?)([^\<\>])(.*)", y)
IfEqual y3,<, Return Eval@(y1 . y2) < Eval@(y4 . y5)
IfEqual y3,>, Return Eval@(y1 . y2) > Eval@(y4 . y5)
IfEqual y3,<=, Return Eval@(y1 . y2) <= Eval@(y4 . y5)
IfEqual y3,>=, Return Eval@(y1 . y2) >= Eval@(y4 . y5)
; execute rightmost user operator (low precedence)
RegExMatch(x, "i)(.*)(\[gcd\]|\[min\]|\[max\]|\[choose\])(.*)", y)
IfEqual y2,[Gcd], Return GCD( Eval@(y1),Eval@(y3))
IfEqual y2,[Min], Return Min( Eval@(y1),Eval@(y3))
IfEqual y2,[Max], Return Max( Eval@(y1),Eval@(y3))
IfEqual y2,[choose],Return Choose(Eval@(y1),Eval@(y3))
StringGetPos i, x, |, R ; execute rightmost | operator
IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) | Eval@(SubStr(x,2+i))
StringGetPos i, x, ^, R ; execute rightmost ^ operator
IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ^ Eval@(SubStr(x,2+i))
StringGetPos i, x, &, R ; execute rightmost & operator
IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) & Eval@(SubStr(x,2+i))
; execute rightmost <<, >> operator
RegExMatch(x, "(.*)(\<\<|\>\>)(.*)", y)
IfEqual y2,<<, Return Eval@(y1) << Eval@(y3)
IfEqual y2,>>, Return Eval@(y1) >> Eval@(y3)
; execute rightmost +- (not unary) operator
RegExMatch(x, "(.*[^\(\@\*\/\\\~\+\#])(\+|\#)(.*)", y) ; lower precedence ops are already handled
IfEqual y2,+, Return Eval@(y1) + Eval@(y3)
IfEqual y2,#, Return Eval@(y1) - Eval@(y3)
; execute rightmost */% operator
RegExMatch(x, "(.*)(\*|\/|\\)(.*)", y)
IfEqual y2,*, Return Eval@(y1) * Eval@(y3)
IfEqual y2,/, Return Eval@(y1) / Eval@(y3)
IfEqual y2,\, Return Mod(Eval@(y1),Eval@(y3))
; execute rightmost power
StringGetPos i, x, @, R
IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ** Eval@(SubStr(x,2+i))
; execute rightmost function
If !RegExMatch(x,"i)(.*)(\+|\#|\~|Abs|Ceil|Exp|Floor|Log|Ln|Round|Sqrt|Sin|Cos|Tan|ASin|ACos|ATan|Sgn|Fib|fac)([-\d\.]*)", y)
Return x ; no more function
IfEqual y2,+,Return Eval@(y1 . y3) ; unary + and...
IfEqual y2,#,Return Eval@(y1 . -y3) ; unary - and...
IfEqual y2,~,Return Eval@(y1 . ~y3) ; unary ~ behaves like a function
GoTo %y2% ; functions checked last: y3 is number
Abs:
Return Eval@(y1 . Abs(y3))
Ceil:
Return Eval@(y1 . Ceil(y3))
Exp:
Return Eval@(y1 . Exp(y3))
Floor:
Return Eval@(y1 . Floor(y3))
Log:
Return Eval@(y1 . Log(y3))
Ln:
Return Eval@(y1 . Ln(y3))
Round:
Return Eval@(y1 . Round(y3))
Sqrt:
Return Eval@(y1 . Sqrt(y3))
Sin:
Return Eval@(y1 . Sin(y3))
Cos:
Return Eval@(y1 . Cos(y3))
Tan:
Return Eval@(y1 . Tan(y3))
ASin:
Return Eval@(y1 . ASin(y3))
ACos:
Return Eval@(y1 . ACos(y3))
ATan:
Return Eval@(y1 . ATan(y3))
Sgn:
Return Eval@(y1 . Sgn(y3))
Fib:
Return Eval@(y1 . Fib(y3))
Fac:
Return Eval@(y1 . Fac(y3))
}
ToBin(n) { ; Binary representation of n. 1st bit is SIGN: -8 -> 1000, -1 -> 11, 0 -> 0, 8 -> 01000
m := -(n<0)
Loop {
b := n&1 . b
If (m = n >>= 1)
Return b = "0" ? b : -m . b
}
}
FromBin(bits) { ; Number converted from the binary "bits" string, 1st bit is SIGN
n = 0
Loop Parse, bits
n += n + A_LoopField
Return n - (SubStr(bits,1,1)<<StrLen(bits))
}
MIN(a,b) { ; The less of a, b
Return a<b ? a : b
}
MAX(a,b) { ; The greater of a, b
Return a>b ? a : b
}
GCD(a,b) { ; Euclidean GCD
Return b=0 ? Abs(a) : GCD(b, mod(a,b))
}
Choose(n,k) { ; Binomial coefficient
p := 1, i := 0, k := k < n-k ? k : n-k
Loop %k%
p *= (n-i)/(k-i), i+=1
Return Round(p)
}
Sgn(x) { ; Sign of x
Return (x>0)-(x<0)
}
Fib(n) { ; n-th Fibonacci number (iterative to avoid globals)
a := 0, b := 1
Loop % n-1
c := b, b += a, a := c
Return b
}
fac(n) { ; n!
Return n<2 ? 1 : n*fac(n-1)
}