diff options
Diffstat (limited to 'Master/texmf-dist/tex/generic/pgfplots/lua')
11 files changed, 3773 insertions, 30 deletions
diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots.lua index dcb8ea94007..23aeaab2a84 100644 --- a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots.lua +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots.lua @@ -1,37 +1,45 @@ -pgfplotsGetLuaBinaryStringFromCharIndicesChunkSize = 7000; +require("pgfplots.binary") -if unpack == nil then - -- LUA 0.76 renamed unpack to table.unpack - pgfplotsUnpack = table.unpack; +-- all classes/globals will be added to this table: +pgfplots = {} + +-- will be set by TeX: +pgfplots.pgfplotsversion = nil + +if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.0" then + texio.write("pgfplots: setting 'lua support=false': the lua version on this system is " .. _VERSION .. "; expected at least 'Lua 5.2'. Use a more recent TeX distribution to benefit from LUA in pgfplots.\n") + + -- the entire lua backend will be switched off if this is false: + tex.sprint("\\pgfplotsset{lua support=false}") + return else - pgfplotsUnpack = unpack; + -- well, 5.2 is what this stuff has been written for. + -- Is there a good reason why it shouldn't work on 5.1 !? No, I guess not. Except that it took me a long time + -- to figure out that 5.2 broke compatibility in lots of ways - and it was difficult enough to get it up and running. + -- If someone wants (and needs) to run it in 5.1 - I would accept patches. end --- Takes a table containing an arbitrary number of integers in the range 0..255 and converts it --- into a binary stream of the corresponding binary chars. --- --- @param charIndices a table containing 0...N arguments; each in the range 0..255 --- --- @return a string containing binary content, one byte for each input integer. -function pgfplotsGetLuaBinaryStringFromCharIndices(charIndices) - -- unpack extracts only the indices (we can't provide a table to string.char). - -- note that pdf.immediateobj has been designed to avoid sanity checking for invalid UTF strings - - -- in other words: it accepts binary strings. - -- - -- unfortunately, this here fails for huge input tables: - -- pgfplotsretval=string.char(unpack(charIndices)); - -- we have to create it incrementally using chunks: - local len = #charIndices; - local chunkSize = pgfplotsGetLuaBinaryStringFromCharIndicesChunkSize; - local buf = {}; - -- ok, append all full chunks of chunkSize first: - local numFullChunks = math.floor(len/chunkSize); - for i = 0, numFullChunks-1, 1 do - table.insert(buf, string.char(pgfplotsUnpack(charIndices, 1+i*chunkSize, (i+1)*chunkSize))); - end - -- append the rest: - table.insert(buf, string.char(pgfplotsUnpack(charIndices, 1+numFullChunks*chunkSize))); - return table.concat(buf); +require("pgfplots.pgfplotsutil") + +-- see pgfrcs.code.tex -- all versions after 3.0.0 (excluding 3.0.0) will set this version: +if not pgf or not pgf.pgfversion then + pgfplots.log("log", "pgfplots.lua: loading complementary lua code for your pgf version...\n") + pgfplots.pgfluamathfunctions = require("pgfplotsoldpgfsupp.luamath.functions") + pgfplots.pgfluamathparser = require("pgfplotsoldpgfsupp.luamath.parser") +else + pgfplots.pgfluamathparser = require("pgf.luamath.parser") + pgfplots.pgfluamathfunctions = require("pgf.luamath.functions") end +pgfplots.pgftonumber = pgfplots.pgfluamathfunctions.tonumber +pgfplots.tostringfixed = pgfplots.pgfluamathfunctions.tostringfixed +pgfplots.toTeXstring = pgfplots.pgfluamathfunctions.toTeXstring + + +require("pgfplots.plothandler") +require("pgfplots.meshplothandler") +require("pgfplots.colormap") +require("pgfplots.streamer") +-- hm. perhaps this here should become a separate module: +require("pgfplots.pgfplotstexio") diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/binary.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/binary.lua new file mode 100644 index 00000000000..704778440bc --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/binary.lua @@ -0,0 +1,41 @@ + +-- Attention: functions in this file are part of the backend driver. +-- They are supposed to work back with Lua 5.1 . +-- Note that most of the 'lua backend' requires Lua 5.2 (currently) + +pgfplotsGetLuaBinaryStringFromCharIndicesChunkSize = 7000; + +if unpack == nil then + -- LUA 0.76 renamed unpack to table.unpack + pgfplotsUnpack = table.unpack; +else + pgfplotsUnpack = unpack; +end + +-- Takes a table containing an arbitrary number of integers in the range 0..255 and converts it +-- into a binary stream of the corresponding binary chars. +-- +-- @param charIndices a table containing 0...N arguments; each in the range 0..255 +-- +-- @return a string containing binary content, one byte for each input integer. +function pgfplotsGetLuaBinaryStringFromCharIndices(charIndices) + -- unpack extracts only the indices (we can't provide a table to string.char). + -- note that pdf.immediateobj has been designed to avoid sanity checking for invalid UTF strings - + -- in other words: it accepts binary strings. + -- + -- unfortunately, this here fails for huge input tables: + -- pgfplotsretval=string.char(unpack(charIndices)); + -- we have to create it incrementally using chunks: + local len = #charIndices; + local chunkSize = pgfplotsGetLuaBinaryStringFromCharIndicesChunkSize; + local buf = {}; + -- ok, append all full chunks of chunkSize first: + local numFullChunks = math.floor(len/chunkSize); + for i = 0, numFullChunks-1, 1 do + table.insert(buf, string.char(pgfplotsUnpack(charIndices, 1+i*chunkSize, (i+1)*chunkSize))); + end + -- append the rest: + table.insert(buf, string.char(pgfplotsUnpack(charIndices, 1+numFullChunks*chunkSize))); + return table.concat(buf); +end + diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/colormap.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/colormap.lua new file mode 100644 index 00000000000..28fd6ddd56b --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/colormap.lua @@ -0,0 +1,98 @@ +-- +-- This file contains parts of pgfplotscolormap.code.tex +-- + +local math=math +local pgfplotsmath = pgfplots.pgfplotsmath +local io=io +local type=type +local tostring=tostring +local error=error +local table=table + +do +-- all globals will be read from/defined in pgfplots: +local _ENV = pgfplots +----------------------------------- + +ColorSpace = newClass() +function ColorSpace:constructor(numComponents) + self.numComponents=numComponents +end + +rgb = ColorSpace.new(3) +cmyk = ColorSpace.new(4) +gray = ColorSpace.new(1) + + +ColorMap = newClass() + +ColorMap.range =1000 + +-- h: mesh width between adjacent values +-- colorspace: an instance of ColorSpace +-- values: an array (1-based table) with color components. Each color component is supposed to be a table with K entries where K is colorspace:numComponents +function ColorMap:constructor( h, colorspace, values) + if not h or not colorspace or not values then error("arguments must not be nil")end + + self.name = name + self.h = h + self.invh = 1/h + self.colorspace = colorspace + self.values = values + + local numComponents = self.colorspace.numComponents + for i = 1,#self.values do + local value = self.values[i] + if #value ~= numComponents then + error("Some value has an unexpected number of color components, expected " .. self.colorspace.numComponents .. " but was ".. #value); + end + end +end + +function ColorMap:findPrecomputed(inMin, inMax, x) + local transformed + if inMin == 0 and inMax == ColorMap.range then + transformed = x + else + local scale = ColorMap.range / (inMax - inMin) + + transformed = (x - inMin) * scale + end + transformed = math.max(0, transformed) + transformed = math.min(ColorMap.range, transformed) + + local divh = transformed * self.invh + local intervalno = math.floor(divh) + local factor = divh - intervalno + local factor_two = 1-factor + + + -- Step 2: interpolate the desired RGB value using vector valued interpolation on the identified interval + if intervalno+1 == #self.values then + -- ah- we are at the right end! + return self.values[#self.values] + end + + local left = self.values[intervalno+1] + local right = self.values[intervalno+2] + if not left or not right then error("Internal error: the color map does not have enough values for interval no " .. intervalno )end + + local result = {} + for i = 1,self.colorspace.numComponents do + local result_i = factor_two * left[i] + factor * right[i] + + result[i] = result_i + end + + return result +end + +----------------------------------- + +-- global registry of all colormaps. +-- Key: colormap name +-- Value: an instance of ColorMap +ColorMaps = {} + +end diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/meshplothandler.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/meshplothandler.lua new file mode 100644 index 00000000000..a3b492a5296 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/meshplothandler.lua @@ -0,0 +1,294 @@ +-- This file has dependencies to BOTH, the TeX part of pgfplots and the LUA part. +-- It is the only LUA component with this property. +-- +-- Its purpose is to encapsulate the communication between TeX and LUA in a central LUA file + +local pgfplotsmath = pgfplots.pgfplotsmath +local error=error +local table=table +local string=string +local tostring=tostring +local type=type +local io=io + +do +-- all globals will be read from/defined in pgfplots: +local _ENV = pgfplots + + +------------------------------------------------------- +-- A patch type. +-- @see \pgfplotsdeclarepatchclass + +PatchType = newClass() + +function PatchType:constructor(name, numVertices) + self.name = name + self.numVertices = numVertices +end + +function PatchType:__tostring() + return self.name +end + +function PatchType:newPatch(coords) + return Patch.new(self,coords) +end + +LinePatchType = newClassExtends(PatchType) + +function LinePatchType:constructor() + PatchType.constructor(self, "line", 2) +end + +TrianglePatchType = newClassExtends(PatchType) + +function TrianglePatchType:constructor() + PatchType.constructor(self, "triangle", 3) +end + + +RectanglePatchType = newClassExtends(PatchType) + +function RectanglePatchType:constructor() + PatchType.constructor(self, "rectangle", 4) +end + +------------------------------------------------------- +-- +-- a single patch. +-- @see \pgfplotsdeclarepatchclass + +Patch = newClass() + +function Patch:constructor(patchtype, coords) + if not patchtype or not coords then error("arguments must not be nil") end + if #coords ~= patchtype.numVertices then error("Unexpected number of coordinates provided; expected " .. tostring(patchtype.numVertices) .. " but got " .. tostring(#coords)) end + + self.patchtype = patchtype + self.coords = coords +end + +------------------------------------------------------- +-- Replicates \pgfplotsplothandlermesh (to some extend) +MeshPlothandler = newClassExtends(Plothandler) + +function MeshPlothandler:constructor(axis, pointmetainputhandler) + Plothandler.constructor(self,"mesh", axis, pointmetainputhandler) +end + +-- see \pgfplot@apply@zbuffer +function MeshPlothandler:reverseScanline(scanLineLength) + local coords = self.coords + local tmp + local scanlineOff + local numScanLines = #coords / scanLineLength + for scanline = 0,numScanLines-1,1 do + scanlineOff = scanline * scanLineLength + local reverseindex = scanlineOff + scanLineLength + for i = 0,scanLineLength/2-1,1 do + tmp = coords[1+scanlineOff+i] + coords[1+scanlineOff+i] = coords[reverseindex] + coords[reverseindex] = tmp + + reverseindex = reverseindex-1 + end + end +end + +-- see \pgfplot@apply@zbuffer +function MeshPlothandler:reverseTransposed(scanLineLength) + local coords = self.coords + local tmp + local scanlineOff + local numScanLines = #coords / scanLineLength + local reverseScanline = numScanLines-1 + for scanline = 0,numScanLines/2-1,1 do + scanlineOff = 1+scanline * scanLineLength + reverseScanlineOff = 1+reverseScanline * scanLineLength + for i = 0,scanLineLength-1 do + tmp = coords[scanlineOff+i] + coords[scanlineOff+i] = coords[reverseScanlineOff+i] + coords[reverseScanlineOff+i] = tmp + end + + reverseScanline = reverseScanline-1 + end +end + +-- see \pgfplot@apply@zbuffer +function MeshPlothandler:reverseStream() + local coords = self.coords + local tmp + local reverseindex = #coords + for i = 1,#coords/2 do + tmp = coords[i] + coords[i] = coords[reverseindex] + coords[reverseindex] = tmp + reverseindex = reverseindex-1 + end +end + + + +------------------------------------------------------- +-- +-- The (LUA!) visualizer for patch plots. It prepares stuff such that TeX only needs to work with lowlevel driver (PGF) streams. +-- + +MeshVisualizer = newClassExtends(PlotVisualizer) + +local COORDINATE_VALUE_OF_JUMPS = -16000 +local meshVisualizerTagEmptyCoordinates = function(pt) + pt.pgfXY= { COORDINATE_VALUE_OF_JUMPS, COORDINATE_VALUE_OF_JUMPS } + pt.x = { COORDINATE_VALUE_OF_JUMPS, COORDINATE_VALUE_OF_JUMPS, COORDINATE_VALUE_OF_JUMPS } +end + +function MeshVisualizer:constructor(sourcePlotHandler, patchType, rows, cols, isXVariesOrdering, isMatrixInput, isMatrixOutput, isZBufferSort) + PlotVisualizer.constructor(self,sourcePlotHandler) + self.patchType = patchType + self.isMatrixInput = isMatrixInput + self.isMatrixOutput = isMatrixOutput + self.isZBufferSort = isZBufferSort + self.rows = rows + self.cols = cols + self.isXVariesOrdering =isXVariesOrdering + + self.isOneDimMode= false + self.scanLineLength =-1 + if isMatrixInput then + -- isOneDimMode is ONLY interesting for matrix input + if cols <= 1 or rows <=1 then + self.isOneDimMode = true + self.patchType = LinePatchType.new() + -- this is not yet implemented (and cannot happen since the TeX call does catch this) + error("UNSUPPORTED OPERATION EXCEPTION") + end + + if isXVariesOrdering then + -- x varies (=rowwise) + self.scanLineLength = cols + else + -- y varies (=colwise) + self.scanLineLength = rows + end + + self.notifyJump = meshVisualizerTagEmptyCoordinates + else + -- disable any special handling + self.isXVariesOrdering = true + end + + -- log("initialized MeshVisualizer with " .. tostring(sourcePlotHandler) .. ", " .. tostring(patchType) .. ", isMatrixInput = " .. tostring(isMatrixInput) .. ", isMatrixOutput = " .. tostring(isMatrixOutput) .. ", isZBufferSort = " .. tostring(isZBufferSort) .. " rows = " ..tostring(rows) .. " cols = " ..tostring(cols) .. " is x varies=" .. tostring(isXVariesOrdering)) +end + +function MeshVisualizer:getVisualizationOutput() + local result = PlotVisualizer.getVisualizationOutput(self) + + if self.isMatrixInput and not self.isMatrixOutput then + result = self:decodeIntoPatches(result) + end + + if self.isZBufferSort then + result = self:applyZBufferSort(result) + end + + return result +end + +-- @param coords an array of Coord +function MeshVisualizer:applyZBufferSort(coords) + -- in order to sort this thing, we need to compute the sort key (view depth) for each coord. + -- furthermore, each list entry must be single patch... that means we need a (huge?) temporary table. + + local patchType = self.patchType + local numVertices = patchType.numVertices + + if (#coords % numVertices) ~= 0 then error("Got an unexpected number of input coordinates: each patch has " .. tostring(numVertices) .. " vertices, but the number of coords " .. tostring(#coords) .. " is no multiple of this number") end + local numPatches = #coords / numVertices + + -- STEP 1: compute an array of patches. + local patches = {} + local off=1 + for i = 1,numPatches do + local patchCoords = {} + for j = 1,numVertices do + local pt = coords[off] + off = off+1 + patchCoords[j] = pt + end + local patch = patchType:newPatch(patchCoords) + patches[i] = patch + end + if off ~= 1+#coords then error("Internal error: not all coordinates are part of patches (got " .. tostring(off) .. "/" .. tostring(#coords) ..")") end + + -- STEP 2: assign the sort key: the "element depth". + -- + -- the "element depth" is defined to be the MEAN of all + -- vertex depths. + -- And since the mean is 1/n * sum_{i=1}^n V_i, we can + -- directly omit the 1/n --- it is the same for every + -- vertex anyway, and we only want to compare the depth + -- values. + local axis = self.axis + local getVertexDepth = axis.getVertexDepth + for i=1,numPatches do + local patch = patches[i] + local patchcoords = patch.coords + + local sumOfVertexDepth = 0 + for j = 1,numVertices do + local vertex = patchcoords[j] + + local vertexDepth = getVertexDepth(axis,vertex) + + sumOfVertexDepth = sumOfVertexDepth + vertexDepth + end + patch.elementDepth = sumOfVertexDepth + end + + -- STEP 3: SORT. + local comparator = function(patchA, patchB) + return patchA.elementDepth > patchB.elementDepth + end + table.sort(patches, comparator) + + -- STEP 4: convert back into a list (in-place). + local off = 1 + for i=1,numPatches do + local patch = patches[i] + local patchcoords = patch.coords + for j = 1,numVertices do + coords[off] = patchcoords[j] + off = off+1 + end + end + if off ~= 1+#coords then error("Internal error: not all coordinates are part of patches (got " .. tostring(off) .. "/" .. tostring(#coords) ..")") end + + return coords +end + +function MeshVisualizer:decodeIntoPatches(coords) + local result = {} + + local scanLineLength = self.scanLineLength + local length = #coords + + local i = scanLineLength + while i < length do + local im = i-scanLineLength + + for j = 2,scanLineLength do + table.insert(result, coords[im+j-1]) -- (i-1,j-1) + table.insert(result, coords[im+j]) -- (i-1,j ) + table.insert(result, coords[i+j]) -- (i ,j ) + table.insert(result, coords[i+j-1]) -- (i ,j-1) + end + + i = i + scanLineLength + end + + return result +end + +end diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/pgfplotstexio.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/pgfplotstexio.lua new file mode 100644 index 00000000000..ca8b894a7c3 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/pgfplotstexio.lua @@ -0,0 +1,464 @@ +-- This file has dependencies to BOTH, the TeX part of pgfplots and the LUA part. +-- It is the only LUA component with this property. +-- +-- Its purpose is to encapsulate the communication between TeX and LUA in a central LUA file +-- +-- +-- Here is the idea how the TeX backend communicates with the LUA backend: +-- +-- * TeX can call LUA methods in order to "do something". The reverse direction is not true: LUA cannot call TeX methods. +-- +-- * the only way that LUA can read TeX input values or write TeX output values is the top layer (at the time of this writing: only pgfplotstexio.lua ). +-- +-- * The LUA backend has one main purpose: scalability and performance. +-- Its purpose is _not_ to run a standalone visualization. +-- +-- The precise meaning of "scalability" is: the LUA backend should do as much +-- as possible which is done for single coordinates. Coordinates constitute +-- "scalability": the number of coordinates can become arbitrarily large. +-- +-- "Performance" is related to scalability. But it is more: some dedicated +-- utility function might have a TeX backend and will be invoked whenever it +-- is needed. Candidates are colormap functions, z buffer arithmetics etc. +-- +-- Thus, the best way to ensure "scalability" is to move _everything_ which is to be done for a single coordinate to LUA. +-- +-- Sometimes, this will be impossible or too expensive. +-- Here, "Performance" might still be optimized by some dedicated LUA function. +-- +-- +-- Unfortunately, the LUA backend does not simplify the code base - it makes it more complicated. +-- This is due to the way it is used: one needs to know when the TeX backend +-- delegates all its work to LUA. It is also due to the fact that it +-- duplicates code: the same code is present in TeX and in LUA. I chose to keep +-- the two code bases close to each other. This has a chance to simplify maintenance: if I know +-- how something works in TeX, and I find some entry point in LUA, it will +-- hopefully be closely related. +-- +-- +-- It follows an overview over entry points into the LUA backend: +-- +-- * \begin{axis}. It invokes \pgfplots@prepare@LUA@api . +-- The purpose is to define the global pgfplots.gca "_g_et _c_urrent _a_xis" and to transfer some key presets. +-- +-- Log message: "lua backend=true: Activating LUA backend for axis." +-- +-- * \end{axis}. It invokes \pgfplots@LUA@visualization@update@axis . +-- The purpose is to transfer results of the survey phase to LUA; in particular axis properties like +-- the view direction, data scale transformations, axis wide limits and some related properties. +-- This has nothing to do with coordinates; the survey phase of coordinates is handled in a different way (see below). +-- +-- Eventually, \pgfplots@LUA@cleanup will clear the global pgfplots.gca . +-- +-- * \addplot . This has much to do with scalability, so much of its functionality is done in the LUA backend. +-- +-- Keep in mind that \addplot is part of the survey phase: it collects coordinates and updates axis limits. +-- Afterwards, it stores the survey results in the current axis. +-- +-- The survey phase currently has two different ways to communicate with the LUA backend: +-- +-- 1. PARTIAL MODE. In this mode, the coordinate stream comes from TeX: +-- some TeX code generates the coordinates. Whenever the stream is ready, +-- it will invoke \pgfplots@LUA@survey@point . This, in turn, calls the +-- LUA backend in order to complete the survey (which boils down to +-- pgfplots.texSurveyPoint). PARTIAL MODE saves lots of time, but its +-- scalability is limited due to the intensive use of TeX, it is less +-- powerful than COMPLETE MODE. +-- +-- 2. COMPLETE MODE. In this mode, the entire coordinate stream is on the +-- LUA side. The TeX code will merely call start the survey phase, call +-- LUA, and end the survey phase. This is the most efficient +-- implementation. At the time of this writing, it is limited to `\addplot +-- expression`: the code for `\addplot expression` tries to transfer the +-- entire processing to the LUA backend. If it succeeds, it will do +-- nothing on the TeX side. +-- +-- Both PARTIAL MODE and COMPLETE MODE call +-- \pgfplots@LUA@survey@start : transfer plot type and current axis arguments to LUA +-- and +-- \pgfplots@LUA@survey@end : copy LUA axis arguments back to TeX. +-- +-- Eventually, the axis will initiate the visualization phase for each plot. This is done by +-- a) \pgfplots@LUA@visualization@init : it calls pgfplots.texVisualizationInit() and results in the log message +-- "lua backend=true: Activating partial LUA backend for visualization of plot 0". +-- b) \pgfplots@LUA@visualization@of@current@plot : it transfers control +-- to LUA (pgfplots.texVisualizePlot) and does as much with the +-- coordinates as possible. Eventually, it streams the result back to +-- TeX which will visualize the stream by means of PGF's plot streams. +-- This is somewhat complicated since it modifies the TeX streaming. +local pgfplotsmath = pgfplots.pgfplotsmath +local tex=tex +local tostring=tostring +local error=error +local table=table +local string=string +local pairs=pairs +local pcall=pcall +local type=type +local lpeg = require("lpeg") +local math = math + +do +-- all globals will be read from/defined in pgfplots: +local _ENV = pgfplots + +local pgftonumber = pgfluamathfunctions.tonumber + +-- will be assigned by pgfplots at boot time. +LOAD_TIME_CATCODETABLE = nil + +-- Called during \addplot, i.e. during the survey phase. It is only called in PARTIAL MODE (see above). +function texSurveyPoint(x,y,z,meta) + local pt = Coord.new() + pt.x[1] = x + pt.x[2] = y + pt.x[3] = z + pt.meta = meta + + gca.currentPlotHandler:surveypoint(pt) +end + +-- Copies survey results of the current plot back to TeX. It prints a couple of executable TeX statements as result. +-- @see \pgfplots@LUA@survey@end +function texSurveyEnd() + local result = gca:surveyToPgfplots(gca.currentPlotHandler, true) + --log("returning " .. result .. "\n\n") + + tex.sprint(LOAD_TIME_CATCODETABLE, result); + gca.currentPlotHandler=nil +end + +-- A performance optimization: point meta transformation is done on the LUA side. +-- +-- expands to the transformed point meta +function texPerpointMetaTrafo(metaStr) + local meta = pgftonumber(metaStr) + local transformed = gca.currentPlotHandler:visualizationTransformMeta(meta); + tex.sprint(LOAD_TIME_CATCODETABLE, tostringfixed(transformed)); +end + +-- Called at the beginning of each plot visualization. +-- +-- expands to '1' if LUA is available for this plot and '0' otherwise. +-- @see texVisualizePlot +function texVisualizationInit(plotNum, plotIs3d) + if not plotNum or plotIs3d==nil then error("arguments must not be nil") end + + local currentPlotHandler = gca.plothandlers[plotNum+1] + gca.currentPlotHandler = currentPlotHandler; + if currentPlotHandler then + currentPlotHandler.plotIs3d = plotIs3d + currentPlotHandler:visualizationPhaseInit(); + tex.sprint("1") + else + -- ok, this plot has no LUA support. + tex.sprint("0") + end +end + +local pgfXyCoordSerializer = function(pt) + -- FIXME : it is unsure of whether this here really an improvement - or if it would be faster to compute that stuff in TeX... + if pt.pgfXY ~=nil then + return "{" .. tostringfixed(pt.pgfXY[1]) .. "}{" .. tostringfixed(pt.pgfXY[2]) .. "}" + else + return "{0}{0}" + end +end + +-- Actually does as much of the visualization of the current plot: it transforms all coordinates to some point where the TeX visualization mode continues. +-- +-- It expands to the resulting coordinates. Note that these coordinates are already mapped somehow (typically: to fixed point) +-- @see texVisualizationInit +function texVisualizePlot(visualizerFactory) + if not visualizerFactory then error("arguments must not be nil") end + if type(visualizerFactory) ~= "function" then error("arguments must be a function (a factory)") end + + local currentPlotHandler = gca.currentPlotHandler + if not currentPlotHandler then error("Illegal state: The current plot has no LUA plot handler!") end + + local visualizer = visualizerFactory(currentPlotHandler) + + local result = visualizer:getVisualizationOutput() + local result_str = currentPlotHandler:getCoordsInTeXFormat(gca, result, pgfXyCoordSerializer) + --log("returning " .. result_str .. "\n\n") + tex.sprint(LOAD_TIME_CATCODETABLE, result_str) +end + +-- Modifies the Surveyed coordinate list. +-- Expands to nothing +function texApplyZBufferReverseScanline(scanLineLength) + local currentPlotHandler = gca.currentPlotHandler + if not currentPlotHandler then error("This function cannot be used in the current context") end + + currentPlotHandler:reverseScanline(scanLineLength) +end + +-- Modifies the Surveyed coordinate list. +-- Expands to nothing +function texApplyZBufferReverseTransposed(scanLineLength) + local currentPlotHandler = gca.currentPlotHandler + if not currentPlotHandler then error("This function cannot be used in the current context") end + + currentPlotHandler:reverseTransposed(scanLineLength) +end + +-- Modifies the Surveyed coordinate list. +-- Expands to nothing +function texApplyZBufferReverseStream() + local currentPlotHandler = gca.currentPlotHandler + if not currentPlotHandler then error("This function cannot be used in the current context") end + + currentPlotHandler:reverseStream(scanLineLength) +end + +-- Modifies the Surveyed coordinate list. +-- +-- Note that this is UNRELATED to mesh/surface plots! They have their own (patch-based) z buffer. +-- +-- Expands to nothing +function texApplyZBufferSort() + local currentPlotHandler = gca.currentPlotHandler + if not currentPlotHandler then error("This function cannot be used in the current context") end + + currentPlotHandler:sortCoordinatesByViewDepth() +end + +-- Modifies the Surveyed coordinate list. +-- Expands to the resulting coordinates +function texGetSurveyedCoordsToPgfplots() + local currentPlotHandler = gca.currentPlotHandler + if not currentPlotHandler then error("This function cannot be used in the current context") end + + tex.sprint(LOAD_TIME_CATCODETABLE, currentPlotHandler:surveyedCoordsToPgfplots(gca)) +end + +-- Performance optimization: computes the colormap lookup. +function texColorMapPrecomputed(mapName, inMin, inMax, x) + local colormap = ColorMaps[mapName]; + if colormap then + local result = colormap:findPrecomputed( + pgftonumber(inMin), + pgftonumber(inMax), + pgftonumber(x)) + + local str = "" + for i = 1,#result do + if i>1 then str = str .. "," end + str = str .. tostringfixed(result[i]) + end + tex.sprint(LOAD_TIME_CATCODETABLE, str) + end +end + +local function isStripPrefixOrSuffixChar(char) + return char == ' ' or char == '{' or char == "}" +end + +-- Expressions can be something like +-- ( {(6+(sin(3*(x+3*y))+1.25)*cos(x))*cos(y)}, +-- {(6+(sin(3*(x+3*y))+1.25)*cos(x))*sin(y)}, +-- {((sin(3*(x+3*y))+1.25)*sin(x))} ); +-- +-- These result in expressions = { " {...}", " {...}", " {...} " } +-- -> this function removes the surrounding braces and the white spaces. +local function removeSurroundingBraces(expressions) + for i=1,#expressions do + local expr = expressions[i] + local startIdx + local endIdx + startIdx=1 + while startIdx<#expr and isStripPrefixOrSuffixChar(string.sub(expr,startIdx,startIdx)) do + startIdx = startIdx+1 + end + endIdx = #expr + while endIdx > 0 and isStripPrefixOrSuffixChar(string.sub(expr,endIdx,endIdx)) do + endIdx = endIdx-1 + end + + expr = string.sub(expr, startIdx, endIdx ) + expressions[i] = expr + end +end + +------------- +-- A parser for foreach statements - at least those which are supported in this LUA backend. +-- +local samplesAtToDomain +do + local P = lpeg.P + local C = lpeg.C + local V = lpeg.V + local match = lpeg.match + local space_pattern = P(" ")^0 + + local Exp = V"Exp" + local comma = P"," * space_pattern + -- this does not catch balanced braces. Ok for now... ? + local argument = C( ( 1- P"," )^1 ) * space_pattern + local grammar = P{ "initialRule", + initialRule = space_pattern * Exp * -1, + Exp = lpeg.Ct(argument * comma * argument * comma * P"..." * space_pattern * comma *argument ) + } + + -- Converts very simple "samples at" values to "domain=A:B, samples=N" + -- + -- @param foreachString something like -5,-4,...,5 + -- @return a table where + -- [0] = domain min + -- [1] = domain max + -- [2] = samples + -- It returns nil if foreachString is no "very simple value of 'samples at'" + samplesAtToDomain = function(foreachString) + local matches = match(grammar,foreachString) + + if not matches or #matches ~= 3 then + return nil + else + local arg1 = matches[1] + local arg2 = matches[2] + local arg3 = matches[3] + arg1= pgfluamathparser.pgfmathparse(arg1) + arg2= pgfluamathparser.pgfmathparse(arg2) + arg3= pgfluamathparser.pgfmathparse(arg3) + + if not arg1 or not arg2 or not arg3 then + return nil + end + + if arg1 > arg2 then + return nil + end + + local domainMin = arg1 + local h = arg2-arg1 + local domainMax = arg3 + + -- round to the nearest integer (using +0.5, should be ok) + local samples = math.floor((domainMax - domainMin)/h + 0.5) + 1 + + return domainMin, domainMax, samples + end + end +end + +-- This is the code which attempts to transfer control from `\addplot expression' to LUA. +-- +-- If it succeeds, the entire plot stream and the entire survey phase has been done in LUA. +-- +-- generates TeX output '1' on success and '0' on failure +-- @param debugMode one of a couple of strings: "off", "verbose", or "compileerror" +function texAddplotExpressionCoordinateGenerator( + is3d, + xExpr, yExpr, zExpr, + sampleLine, + domainxmin, domainxmax, + domainymin, domainymax, + samplesx, samplesy, + variablex, variabley, + samplesAt, + debugMode +) + local plothandler = gca.currentPlotHandler + local coordoutputstream = SurveyCoordOutputStream.new(plothandler) + + if samplesAt and string.len(samplesAt) >0 then + -- "samples at" has higher priority than domain. + -- Use it! + + domainxmin, domainxmax, samplesx = samplesAtToDomain(samplesAt) + if not domainxmin then + -- FAILURE: could not convert "samples at". + -- Fall back to a TeX based survey. + log("log", "LUA survey failed: The value of 'samples at= " .. tostring(samplesAt) .. "' is unsupported by the LUA backend (currently, only 'samples at={a,b,...,c}' is supported).\n") + tex.sprint("0") + return + end + + else + domainxmin= pgftonumber(domainxmin) + domainxmax= pgftonumber(domainxmax) + samplesx= pgftonumber(samplesx) + end + + local expressions + local domainMin + local domainMax + local samples + local variableNames + + -- allow both, even if sampleLine=1. We may want to assign a dummy value to y. + variableNames = { variablex, variabley } + + if sampleLine==1 then + domainMin = { domainxmin } + domainMax = { domainxmax } + samples = { samplesx } + else + local domainymin = pgftonumber(domainymin) + local domainymax = pgftonumber(domainymax) + local samplesy = pgftonumber(samplesy) + + domainMin = { domainxmin, domainymin } + domainMax = { domainxmax, domainymax } + samples = { samplesx, samplesy } + end + if is3d then + expressions = {xExpr, yExpr, zExpr} + else + expressions = {xExpr, yExpr} + end + removeSurroundingBraces(expressions) + + local generator = AddplotExpressionCoordinateGenerator.new( + coordoutputstream, + expressions, + domainMin, domainMax, + samples, + variableNames) + + local messageOnFailure + local compileErrorOnFailure + if debugMode == "compileerror" then + compileErrorOnFailure = true + messageOnFailure = true + elseif debugMode == "off" or debugMode == "verbose" then + messageOnFailure = true + compileErrorOnFailure = false + elseif debugMode == "off and silent" then + messageOnFailure = false + compileErrorOnFailure = false + else + error("Got unknown debugMode = " .. debugMode ) + end + + local success + if compileErrorOnFailure then + success = generator:generateCoords() + else + local resultOfGenerator + success, resultOfGenerator = pcall(generator.generateCoords, generator) + if success then + -- AH: "pcall" returned 'true'. In this case, 'success' is the boolean returned by generator + success = resultOfGenerator + end + + if messageOnFailure and not success and type(resultOfGenerator) ~= "boolean" then + log("log", "LUA survey failed: " .. resultOfGenerator .. ". Use \\pgfplotsset{lua debug} to see more.\n") + end + end + + if not type(success) == 'boolean' then error("Illegal state: expected boolean result") end + + if success then + tex.sprint("1") + else + tex.sprint("0") + end +end + +-- Creates the default plot visualizer factory. It simply applies data scale trafos. +function defaultPlotVisualizerFactory(plothandler) + return PlotVisualizer.new(plothandler) +end + +end diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/pgfplotsutil.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/pgfplotsutil.lua new file mode 100644 index 00000000000..b3b09fea688 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/pgfplotsutil.lua @@ -0,0 +1,139 @@ +local math=math +local string=string +local type=type +local tostring = tostring +local tonumber = tonumber +local setmetatable = setmetatable +local getmetatable = getmetatable +local print=print +local pairs = pairs +local table=table +local texio=texio + +do +local _ENV = pgfplots +--------------------------------------- +-- + +log=texio.write_nl + +local stringfind = string.find +local stringsub = string.sub +local tableinsert = table.insert + +-- Splits 'str' at delimiter and returns a table of strings +function stringsplit( str, delimiter ) + if not str or not delimiter then error("arguments must not be nil") end + local result = { } + local start = 1 + local findStart, findEnd = stringfind( str, delimiter, start ) + while findStart do + tableinsert( result, stringsub( str, start, findStart-1 ) ) + start = findEnd + 1 + findStart, findEnd = stringfind( str, delimiter, start ) + end + tableinsert( result, stringsub( str, start ) ) + return result +end + +function stringOrDefault(str, default) + if str == nil or type(str) == 'string' and string.len(str) == 0 then + return default + end + return tostring(str) +end + + +pgfplotsmath = {} + +function pgfplotsmath.isfinite(x) + if pgfplotsmath.isnan(x) or x == pgfplotsmath.infty or x == -pgfplotsmath.infty then + return false + end + return true +end + +local isnan = function(x) + return x ~= x +end + +pgfplotsmath.isnan = isnan + +local infty = 1/0 +pgfplotsmath.infty = infty + +local nan = math.sqrt(-1) +pgfplotsmath.nan = nan + +--------------------------------------- +-- + + +-- Creates and returns a new class object. +-- +-- Usage: +-- complexclass = newClass() +-- function complexclass:constructor() +-- self.re = 0 +-- self.im = 0 +-- end +-- +-- instance = complexclass.new() +-- +function newClass() + local result = {} + + -- we need this such that *instances* (which will have 'result' as meta table) + -- will "inherit" the class'es methods. + result.__index = result + local allocator= function (...) + local self = setmetatable({}, result) + self:constructor(...) + return self + end + result.new = allocator + return result +end + + + +-- Create a new class that inherits from a base class +-- +-- base = pgfplots.newClass() +-- function base:constructor() +-- self.variable= 'a' +-- end +-- +-- sub = pgfplots.newClassExtends(base) +-- function sub:constructor() +-- -- call super constructor. +-- -- it is ABSOLUTELY CRUCIAL to use <baseclass>.constructor here - not :constructor! +-- base.constructor(self) +-- end +-- +-- instance = base.new() +-- +-- instance2 = sub.new() +-- +-- @see newClass +function newClassExtends( baseClass ) + if not baseClass then error "baseClass must not be nil" end + + local new_class = newClass() + + -- The following is the key to implementing inheritance: + + -- The __index member of the new class's metatable references the + -- base class. This implies that all methods of the base class will + -- be exposed to the sub-class, and that the sub-class can override + -- any of these methods. + -- + local mt = {} -- getmetatable(new_class) + mt.__index = baseClass + setmetatable(new_class,mt) + + return new_class +end + + +end diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/plothandler.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/plothandler.lua new file mode 100644 index 00000000000..e3fc089e25d --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/plothandler.lua @@ -0,0 +1,962 @@ +-- +-- This file contains parts of pgfplotscoordprocessing.code.tex and pgfplotsplothandlers.code.tex . +-- +-- It contains +-- +-- pgfplots.Axis +-- pgfplots.Coord +-- pgfplots.Plothandler +-- +-- and some related classes. + +local math=math +local pgfplotsmath = pgfplots.pgfplotsmath +local type=type +local tostring=tostring +local error=error +local table=table +local pgfmathparse = pgfplots.pgfluamathparser.pgfmathparse + +do +-- all globals will be read from/defined in pgfplots: +local _ENV = pgfplots +----------------------------------- + +local pgftonumber =pgfluamathfunctions.tonumber + +Coord = newClass() + +function Coord:constructor() + self.x = { nil, nil, nil } + self.unboundedDir = nil + self.meta= nil + self.metatransformed = nil -- assigned during vis phase only + self.unfiltered = nil + self.pgfXY = nil -- assigned during visphase only + return self +end + +function Coord:copy(other) + for i = 1,#other.x do self.x[i] = other.x[i] end + self.meta = other.meta + self.metatransformed = other.metatransformed + self.unfiltered = nil -- not needed +end + +function Coord:__tostring() + local result = '(' .. stringOrDefault(self.x[1], "--") .. + ',' .. stringOrDefault(self.x[2], "--") .. + ',' .. stringOrDefault(self.x[3], "--") .. + ') [' .. stringOrDefault(self.meta, "--") .. ']' + + if not self.x[1] and self.unfiltered then + result = result .. "(was " .. tostring(self.unfiltered) .. ")" + end + return result +end + +local stringToFunctionMap = pgfluamathfunctions.stringToFunctionMap + +-- a reference to a Coord which is returned by math expressions involving 'x', 'y', or 'z' +-- see surveystart() +local pseudoconstant_pt = nil +local function pseudoconstant_x() return pseudoconstant_pt.x[1] end +local function pseudoconstant_y() return pseudoconstant_pt.x[2] end +local function pseudoconstant_z() return pseudoconstant_pt.x[3] end +local function pseudoconstant_rawx() return pgftonumber(pseudoconstant_pt.unfiltered.x[1]) end +local function pseudoconstant_rawy() return pgftonumber(pseudoconstant_pt.unfiltered.x[2]) end +local function pseudoconstant_rawz() return pgftonumber(pseudoconstant_pt.unfiltered.x[3]) end +local function pseudoconstant_meta() return pseudoconstant_pt.meta end + +-- @return the old value +local function updatePseudoConstants(pt) + local old = pseudoconstant_pt + pseudoconstant_pt = pt + return old +end + +------------------------------------------------------- + +LinearMap = newClass() + +-- A map such that +-- [inMin,inMax] is mapped linearly to [outMin,outMax] +-- +function LinearMap:constructor(inMin, inMax, outMin, outMax) + if not inMin or not inMax or not outMin or not outMax then error("arguments must not be nil") end + if inMin == inMax then + self.map = function (x) return inMin end + else + if inMin > inMax then error("linear map received invalid input domain " .. tostring(inMin) .. ":" .. tostring(inMax)) end + self.offset = outMin - (outMax-outMin)*inMin/(inMax-inMin) + self.scale = (outMax-outMin)/(inMax-inMin) + end +end + +function LinearMap:map(x) + return x*self.scale + self.offset +end + +PointMetaMap = newClass() + +function PointMetaMap:constructor(inMin,inMax, warnForfilterDiscards) + if not inMin or not inMax or warnForfilterDiscards == nil then error("arguments must not be nil") end + self._mapper = LinearMap.new(inMin,inMax, 0, 1000) + self.warnForfilterDiscards = warnForfilterDiscards +end + +function PointMetaMap:map(meta) + if pgfplotsmath.isfinite(meta) then + local result = self._mapper:map(meta) + result = math.max(0, result) + result = math.min(1000, result) + return result + else + if self.warnForfilterDiscards then + log("The per point meta data '" .. tostring(meta) .. " (and probably others as well) is unbounded - using the minimum value instead.\n") + self.warnForfilterDiscards=false + end + return 0 + end +end + + + +------------------------------------------------------- + +-- Abstract base class of all plot handlers. +-- It offers basic functionality for the survey phase. +Plothandler = newClass() + +-- @param name the plot handler's name (a string) +-- @param axis the parent axis +-- @param pointmetainputhandler an instance of PointMetaHandler or nil if there is none +function Plothandler:constructor(name, axis, pointmetainputhandler) + if not name or not axis then + error("arguments must not be nil") + end + self.axis = axis + self.config = PlothandlerConfig.new() + self.name = name + self.coordindex = 0 + self.metamin = math.huge + self.metamax = -math.huge + self.autocomputeMetaMin = true + self.autocomputeMetaMax = true + self.coords = {} + self.pointmetainputhandler = pointmetainputhandler + self.pointmetamap = nil -- will be set later + self.filteredCoordsAway = false + self.plotHasJumps = false + -- will be set before the visualization phase starts. At least. + self.plotIs3d = false + return self +end + +function Plothandler:__tostring() + return 'plot handler ' .. self.name +end + +-- @see \pgfplotsplothandlersurveybeforesetpointmeta +function Plothandler:surveyBeforeSetPointMeta() +end + +-- @see \pgfplotsplothandlersurveyaftersetpointmeta +function Plothandler:surveyAfterSetPointMeta() +end + +-- PRIVATE +-- +-- appends a fully surveyed point +function Plothandler:addSurveyedPoint(pt) + table.insert(self.coords, pt) + -- log("addSurveyedPoint(" .. tostring(pt) .. ") ...\n") +end + +-- PRIVATE +-- +-- assigns the point meta value by means of the PointMetaHandler +function Plothandler:setperpointmeta(pt) + if pt.meta == nil and self.pointmetainputhandler ~= nil then + self.pointmetainputhandler:assign(pt) + end +end + +-- PRIVATE +-- +-- updates point meta limits +function Plothandler:setperpointmetalimits(pt) + if pt.meta ~= nil then + if not type(pt.meta) == 'number' then error("got unparsed input "..tostring(pt)) end + if self.autocomputeMetaMin then + self.metamin = math.min(self.metamin, pt.meta ) + end + + if self.autocomputeMetaMax then + self.metamax = math.max(self.metamax, pt.meta ) + end + end +end + +-- @see \pgfplotsplothandlersurveystart +function Plothandler:surveystart() + stringToFunctionMap["x"] = pseudoconstant_x + stringToFunctionMap["y"] = pseudoconstant_y + stringToFunctionMap["z"] = pseudoconstant_z + stringToFunctionMap["rawx"] = pseudoconstant_rawx + stringToFunctionMap["rawy"] = pseudoconstant_rawy + stringToFunctionMap["rawz"] = pseudoconstant_rawz + stringToFunctionMap["meta"] = pseudoconstant_meta +end + +-- @see \pgfplotsplothandlersurveyend +-- returns executable TeX code to communicate return values. +function Plothandler:surveyend() + -- empty by default. + return "" +end + +-- @see \pgfplotsplothandlersurveypoint +function Plothandler:surveypoint(pt) + updatePseudoConstants(nil) + + local updateLimits = self.config.updateLimits + local current = self.axis:parsecoordinate(pt, self.config.filterExpressionByDir) + + -- this here defines the math functions for x, y, or z. + -- FIXME: are there any hidden callers which rely on these constants in parsecoordinate!? + updatePseudoConstants(current) + + if current.x[1] ~= nil then + current = self.axis:preparecoordinate(current) + if updateLimits then + self.axis:updatelimitsforcoordinate(current) + end + end + self.axis:datapointsurveyed(current, self) + + self.coordindex = self.coordindex + 1; +end + +-- PUBLIC +-- +-- @return a string containing all surveyed coordinates in the format which is accepted \pgfplotsaxisdeserializedatapointfrom +function Plothandler:surveyedCoordsToPgfplots(axis) + return self:getCoordsInTeXFormat(axis, self.coords) +end + +-- PUBLIC +-- +-- @return a string containing all coordinates in the format which is accepted \pgfplotsaxisdeserializedatapointfrom +-- @param extraSerializer a function which takes an instance of Coord and returns a string. can be nil. +function Plothandler:getCoordsInTeXFormat(axis, coords, extraSerializer) + if not axis then error("arguments must not be nil") end + local result = {} + for i = 1,#coords,1 do + local pt = coords[i] + local ptstr = self:serializeCoordToPgfplots(pt) + local axisPrivate = axis:serializeCoordToPgfplotsPrivate(pt) + if extraSerializer then + axisPrivate = extraSerializer(pt) .. "{" .. axisPrivate .. "}" + end + local serialized = "{" .. axisPrivate .. ";" .. ptstr .. "}" + table.insert(result, serialized) + end + return table.concat(result) +end + +-- PRIVATE +-- +-- does the same as \pgfplotsplothandlerserializepointto +function Plothandler:serializeCoordToPgfplots(pt) + return + toTeXstring(pt.x[1]) .. "," .. + toTeXstring(pt.x[2]) .. "," .. + toTeXstring(pt.x[3]) +end + +function Plothandler:visualizationPhaseInit() + if self.pointmetainputhandler ~=nil then + local rangeMin + local rangeMax + if self.config.pointmetarel == PointMetaRel.axiswide then + rangeMin = self.axis.axiswidemetamin + rangeMax = self.axis.axiswidemetamax + else + rangeMin = self.metamin + rangeMax = self.metamax + end + self.pointmetamap = PointMetaMap.new(rangeMin, rangeMax, self.config.warnForfilterDiscards) + end +end + +-- PRECONDITION: visualizationPhaseInit() has been called some time before. +function Plothandler:visualizationTransformMeta(meta) + if meta == nil then + log("could not access the 'point meta' (used for example by scatter plots and color maps). Maybe you need to add '\\addplot[point meta=y]' or something like that?\n") + return 1 + else + return self.pointmetamap:map(meta) + end +end + +-- Modifies coords inplace. +-- @return nothing. +-- see \pgfplots@apply@zbuffer@sort@coordinates +function Plothandler:sortCoordinatesByViewDepth() + local coords = self.coords + + local axis = self.axis + local viewdir = axis.viewdir + + -- Step 1: compute view depth for every coordinate + local getVertexDepth = axis.getVertexDepth + for i=1,#coords do + local vertexDepth = getVertexDepth(axis,coords[i]) + coords[i].vertexDepth = vertexDepth + end + + -- Step 2: sort (inplace) + local comparator = function(ptA, ptB) + return ptA.vertexDepth > ptB.vertexDepth + end + table.sort(coords, comparator) + + -- Step 3: cleanup: do not leave 'vertexDepth' inside of the array + for i=1,#coords do + coords[i].vertexDepth = nil + end +end + +------------------------------------------------------- +-- Generic plot handler: one which has the default survey phase +-- It is actually the same as Plothandler... + +GenericPlothandler = newClassExtends(Plothandler) + +function GenericPlothandler:constructor(name, axis, pointmetainputhandler) + Plothandler.constructor(self,name, axis, pointmetainputhandler) +end + + +------------------------------------------------------- + +UnboundedCoords = { discard="d", jump="j" } + +PointMetaRel = { axiswide = 0, perplot =1 } + + +-- contains static configuration entities. +PlothandlerConfig = newClass() + +function PlothandlerConfig:constructor() + self.unboundedCoords = UnboundedCoords.discard + self.warnForfilterDiscards=true + self.pointmetarel = PointMetaRel.axiswide + self.updateLimits = true + self.filterExpressionByDir = {"", "", ""} + return self +end + +------------------------------------------------------- +-- a PlotVisualizer takes an input Plothandler and visualizes its results. +-- +-- "Visualize" can mean +-- * apply the plot handler's default visualization phase +-- * visualize just plot marks at each of the collected coordinates +-- * visualize just error bars at each collected coordinate +-- * ... +-- + +-- this class offers basic visualization support. "Basic" means that it will merely transform and finalize input coordinates. +PlotVisualizer = newClass() +-- @param sourcePlotHandler an instance of Plothandler +function PlotVisualizer:constructor(sourcePlotHandler) + if not sourcePlotHandler then error("arguments must not be nil") end + self.axis = sourcePlotHandler.axis + self.sourcePlotHandler=sourcePlotHandler + if sourcePlotHandler.plotIs3d then + self.qpointxyz = self.axis.qpointxyz + else + self.qpointxyz = self.axis.qpointxy + end +end + +-- Visualizes the results. +-- +-- @return any results. The format of the results is currently a list of Coord, but I am unsure of whether it will stay this way. +-- +-- Note that a PlotVisualizer does _not_ modify self.sourcePlotHandler.coords +function PlotVisualizer:getVisualizationOutput() + local result = {} + local coords = self.sourcePlotHandler.coords + + -- standard z buffer choices (not mesh + sort) is currently handled in TeX + -- as well as other preparations + + -- FIXME : stacked plots? + -- FIXME : error bars? + + for i = 1,#coords do + local result_i + local result_i = Coord.new() + result_i:copy(coords[i]) + + if result_i.x[1] ~= nil then + self:visphasegetpoint(result_i) + else + self:notifyJump(result_i) + end + + result[i] = result_i + end + + return result +end + +-- PROTECTED +-- resembles \pgfplotsplothandlervisualizejump -- or at least that part which can be done in LUA. +-- It does not visualize anything, but it can be used to modify the coordinate +function PlotVisualizer:notifyJump(pt) + -- do nothing. +end + +function PlotVisualizer:visphasegetpoint(pt) + pt.untransformed = {} + for j = 1,#pt.x do + pt.untransformed[j] = pt.x[j] + end + + self.axis:visphasetransformcoordinate(pt) + + -- FIXME : prepare data point (only for stacked) + + pt.pgfXY = self.qpointxyz(pt.x) +end + + + +------------------------------------------------------- + +-- An abstract base class for a handler of point meta. +-- @see \pgfplotsdeclarepointmetasource +PointMetaHandler = newClass() + +-- @param isSymbolic +-- expands to either '1' or '0' +-- A numeric source will be processed numerically in float +-- arithmetics. Thus, the output of the @assign routine should be +-- a macro \pgfplots@current@point@meta in float format. +-- +-- The output of a numeric point meta source will result in meta +-- limit updates and the final map to [0,1000] will be +-- initialised automatically. +-- +-- A symbolic input routine won't be processed. +-- Default is '0' +-- +-- @param explicitInput +-- expands to either +-- '1' or '0'. In case '1', it expects explicit input from the +-- coordinate input routines. For example, 'plot file' will look for +-- further input after the x,y,z coordinates. +-- Default is '0' +-- +function PointMetaHandler:constructor(isSymbolic, explicitInput) + self.isSymbolic =isSymbolic + self.explicitInput = explicitInput + return self +end + +-- During the survey phase, this macro is expected to assign +-- \pgfplots@current@point@meta +-- if it is a numeric input method, it should return a +-- floating point number. +-- It is allowed to return an empty string to say "there is no point +-- meta". +-- PRECONDITION for '@assign': +-- - the coordinate input method has already assigned its +-- '\pgfplots@current@point@meta' (probably as raw input string). +-- - the other input coordinates are already read. +-- POSTCONDITION for '@assign': +-- - \pgfplots@current@point@meta is ready for use: +-- - EITHER a parsed floating point number +-- - OR an empty string, +-- - OR a symbolic string (if the issymbolic boolean is true) +-- The default implementation is +-- \let\pgfplots@current@point@meta=\pgfutil@empty +-- +function PointMetaHandler.assign(pt) + error("This instance of PointMetaHandler is not implemented") +end + + +-- A PointMetaHandler which merely acquires values of either x,y, or z. +CoordAssignmentPointMetaHandler = newClassExtends( PointMetaHandler ) +function CoordAssignmentPointMetaHandler:constructor(dir) + PointMetaHandler.constructor(self, false,false) + if not dir then error "nil argument for 'dir' is unsupported." end + self.dir=dir +end + +function CoordAssignmentPointMetaHandler:assign(pt) + if not pt then error("arguments must not be nil") end + pt.meta = pgftonumber(pt.x[self.dir]) +end + +XcoordAssignmentPointMetaHandler = CoordAssignmentPointMetaHandler.new(1) +YcoordAssignmentPointMetaHandler = CoordAssignmentPointMetaHandler.new(2) +ZcoordAssignmentPointMetaHandler = CoordAssignmentPointMetaHandler.new(3) + +-- A class of PointMetaHandler which takes the 'Coord.meta' as input +ExplicitPointMetaHandler = newClassExtends( PointMetaHandler ) +function ExplicitPointMetaHandler:constructor() + PointMetaHandler.constructor(self, false,true) +end + +function ExplicitPointMetaHandler:assign(pt) + if pt.unfiltered ~= nil and pt.unfiltered.meta ~= nil then + pt.meta = pgftonumber(pt.unfiltered.meta) + end +end + +-- a point meta handler which evaluates a math expression. +-- ATTENTION: the expression cannot depend on TeX macro values +ExpressionPointMetaHandler = newClassExtends( PointMetaHandler ) + +-- @param expression an expression. It can rely on functions which are only available in plot context (in plot expression, x and y are typically defined) +function ExpressionPointMetaHandler:constructor(expression) + PointMetaHandler.constructor(self, false,false) + self.expression = expression +end + +function ExpressionPointMetaHandler:assign(pt) + pt.meta = pgfmathparse(self.expression) + if not pt.meta then + error("point meta=" .. self.expression .. ": expression has been rejected.") + end +end + + +------------------------------------------------------- + +DatascaleTrafo = newClass() + +function DatascaleTrafo:constructor(exponent, shift) + self.exponent=exponent + self.shift=shift + self.scale = math.pow(10, exponent) +end + +function DatascaleTrafo:map(x) + return self.scale * x - self.shift +end + + +------------------------------------------------------- + +-- An axis. +Axis = newClass() + +function Axis:constructor() + self.is3d = false + self.clipLimits = true + self.autocomputeAllLimits = true -- FIXME : redundant!? + self.autocomputeMin = { true, true, true } + self.autocomputeMax = { true, true, true } + self.isLinear = { true, true, true } + self.min = { math.huge, math.huge, math.huge } + self.max = { -math.huge, -math.huge, -math.huge } + self.datamin = { math.huge, math.huge, math.huge } + self.datamax = { -math.huge, -math.huge, -math.huge } + self.axiswidemetamin = { math.huge, math.huge } + self.axiswidemetamax = { -math.huge, -math.huge } + -- will be populated by the TeX code: + self.plothandlers = {} + -- needed during visualization phase: + self.datascaleTrafo={} + -- needed during visualization phase: a vector of 3 elements, each is a vector of 2 elements. + -- self.unitvectors[1] is (\pgf@xx,\pgf@xy) + self.unitvectors={} + -- needed during visualization phase -- but only for 3d! + self.viewdir = {} + return self +end + +function Axis:getVertexDepth(pt) + local vertexDepth = 0 + local vertex = pt.x + local viewdir = self.viewdir + if vertex[1] == nil then + -- an empty coordinate. Get rid of it. + return 0 + end + + if #vertex ~=3 then + error("Cannot compute vertex depth of " .. tostring(pt) .. ": expected a 3d point but got " .. tostring(#vertex)) + end + if not viewdir or #viewdir~=3 then error("got unexpected view dir " ..tostring(viewdir) ) end + + for k = 1,3 do + local component = vertex[k] + vertexDepth = vertexDepth + component*viewdir[k] + end + + return vertexDepth +end + +function Axis:setunitvectors(unitvectors) + if not unitvectors or #unitvectors ~= 3 then error("got illegal arguments " .. tostring(unitvectors)) end + self.unitvectors = unitvectors + + local pgfxx = unitvectors[1][1] + local pgfxy = unitvectors[1][2] + local pgfyx = unitvectors[2][1] + local pgfyy = unitvectors[2][2] + local pgfzx = unitvectors[3][1] + local pgfzy = unitvectors[3][2] + + self.qpointxyz = function(xyz) + local result = {} + result[1] = xyz[1] * pgfxx + xyz[2] * pgfyx + xyz[3] * pgfzx + result[2] = xyz[1] * pgfxy + xyz[2] * pgfyy + xyz[3] * pgfzy + return result + end + + if pgfxy==0 and pgfyx ==0 then + self.qpointxy = function(xy) + local result = {} + result[1] = xy[1] * pgfxx + result[2] = xy[2] * pgfyy + return result + end + else + self.qpointxy = function(xyz) + local result = {} + result[1] = xyz[1] * pgfxx + xyz[2] * pgfyx + result[2] = xyz[1] * pgfxy + xyz[2] * pgfyy + return result + end + end +end + +-- PRIVATE +-- +-- applies user transformations and logs +-- +-- @see \pgfplots@prepared@xcoord +function Axis:preparecoord(dir, value) + -- FIXME : user trafos, logs (switches off LUA backend) + return value +end + +function Axis:filtercoord(dir, ptCoords, filterExpressionByDir) + if not dir or not ptCoords or not filterExpressionByDir then error("Arguments must not be nil") end + local result = ptCoords.x[dir] + if filterExpressionByDir[dir]:len() > 0 then + + for j = 1,#ptCoords.x do + ptCoords.x[j] = pgftonumber(ptCoords.x[j]) + end + local old = updatePseudoConstants(ptCoords) + + result = pgfmathparse(filterExpressionByDir[dir]) + + updatePseudoConstants(old) + end + return result +end + +-- PRIVATE +-- @see \pgfplotsaxisserializedatapoint@private +function Axis:serializeCoordToPgfplotsPrivate(pt) + return toTeXstring(pt.meta) +end + + +-- PRIVATE +-- +function Axis:validatecoord(dir, point) + if not dir or not point then error("arguments must not be nil") end + local result = pgftonumber(point.x[dir]) + + if result == nil then + result = nil + elseif result == pgfplotsmath.infty or result == -pgfplotsmath.infty or pgfplotsmath.isnan(result) then + result = nil + point.unboundedDir = dir + end + + point.x[dir] = result +end + +-- PRIVATE +-- +-- @see \pgfplotsaxisparsecoordinate +function Axis:parsecoordinate(pt, filterExpressionByDir) + -- replace empty strings by 'nil': + for i = 1,3 do + pt.x[i] = stringOrDefault(pt.x[i], nil) + end + pt.meta = stringOrDefault(pt.meta) + + if pt.x[3] ~= nil then + self.is3d = true + end + + local result = Coord.new() + + local unfiltered = Coord.new() + unfiltered.x = {} + unfiltered.meta = pt.meta + for i = 1,3 do + unfiltered.x[i] = pt.x[i] + end + result.unfiltered = unfiltered + + -- copy values such that filtercoord can access them in the same order as the TeX impl. + for i = 1,self:loopMax() do + result.x[i] = pt.x[i] + end + + -- FIXME : self:prefilter(pt[i]) + for i = 1,self:loopMax() do + result.x[i] = self:preparecoord(i, pt.x[i]) + result.x[i] = self:filtercoord(i, result, filterExpressionByDir) + end + -- FIXME : result.x = self:xyzfilter(result.x) + + for i = 1,self:loopMax() do + self:validatecoord(i, result) + end + + local resultIsBounded = true + for i = 1,self:loopMax() do + if result.x[i] == nil then + resultIsBounded = false + end + end + + if not resultIsBounded then + result.x = { nil, nil, nil} + end + + return result +end + +-- PROTECTED +-- +-- @see \pgfplotsaxispreparecoordinate +function Axis:preparecoordinate(pt) + -- the default "preparation" is to return it as is (no number parsing) + -- + -- FIXME : data cs! Stacking! + return pt +end + +-- PRIVATE +-- +-- returns either 2 if this axis is 2d or 3 otherwise +-- +-- FIXME : shouldn't this depend on the current plot handler!? +function Axis:loopMax() + if self.is3d then return 3 else return 2 end +end + +-- PRIVATE: +-- +-- updates axis limits for pt +-- @param pt an instance of Coord +function Axis:updatelimitsforcoordinate(pt) + local isClipped = false + if self.clipLimits then + for i = 1,self:loopMax(),1 do + if not self.autocomputeMin[i] then + isClipped = isClipped or pt.x[i] < self.min[i] + end + if not self.autocomputeMax[i] then + isClipped = isClipped or pt.x[i] > self.max[i] + end + end + end + + if not isClipped then + for i = 1,self:loopMax(),1 do + if self.autocomputeMin[i] then + self.min[i] = math.min(pt.x[i], self.min[i]) + end + + if self.autocomputeMax[i] then + self.max[i] = math.max(pt.x[i], self.max[i]) + end + end + end + + -- Compute data range: + if self.autocomputeAllLimits then + -- the data range will be acquired simply from the axis + -- range, see below! + else + for i = 1,self:loopMax(),1 do + self.datamin[i] = math.min(pt.x[i], self.min[i]) + self.datamax[i] = math.max(pt.x[i], self.max[i]) + end + end +end + +-- PRIVATE +-- +-- unfinished, see its fixmes +function Axis:addVisualizationDependencies(pt) + -- FIXME : 'visualization depends on' + -- FIXME : 'execute for finished point' + return pt +end + +-- PROTECTED +-- +-- indicates that a data point has been surveyed by the axis and that it can be consumed +function Axis:datapointsurveyed(pt, plothandler) + if not pt or not plothandler then error("arguments must not be nil") end + if pt.x[1] ~= nil then + plothandler:surveyBeforeSetPointMeta() + plothandler:setperpointmeta(pt) + plothandler:setperpointmetalimits(pt) + plothandler:surveyAfterSetPointMeta() + + -- FIXME : error bars + -- FIXME: collect first plot as tick + + -- note that that TeX code would also remember the first/last coordinate in a stream. + -- This is unnecessary here. + + local serialized = self:addVisualizationDependencies(pt) + plothandler:addSurveyedPoint(serialized) + else + -- COORDINATE HAS BEEN FILTERED AWAY + if plothandler.config.unboundedCoords == UnboundedCoords.discard then + plothandler.filteredCoordsAway = true + if plothandler.config.warnForfilterDiscards then + local reason + if pt.unboundedDir == nil then + reason = "of a coordinate filter." + else + reason = "it is unbounding (in " .. tostring(pt.unboundedDir) .. ")." + end + log("NOTE: coordinate " .. tostring(pt) .. " has been dropped because " .. reason .. "\n") + end + elseif plothandler.config.unboundedCoords == UnboundedCoords.jump then + if pt.unboundedDir == nil then + plothandler.filteredCoordsAway = true + if plothandler.config.warnForfilterDiscards then + local reason = "of a coordinate filter." + log("NOTE: coordinate " .. tostring(pt) .. " has been dropped because " .. reason .. "\n") + end + else + plothandler.plotHasJumps = true + + local serialized = self:addVisualizationDependencies(pt) + plothandler:addSurveyedPoint(serialized) + end + end + end + + -- note that the TeX variant would increase the coord index here. + -- We do it it surveypoint. +end + +local function axisLimitToTeXString(name, value) + if value == math.huge or value == -math.huge then + return "" + end + return "\\gdef" .. name .. "{" .. toTeXstring(value) .. "}" +end + +local function toTeXxyzCoord(namePrefix, pt ) + local x = toTeXstring(pt.x[1]) + local y = toTeXstring(pt.x[2]) + local z = toTeXstring(pt.x[3]) + return + "\\gdef" .. namePrefix .. "@x{" .. x .. "}" .. + "\\gdef" .. namePrefix .. "@y{" .. y .. "}" .. + "\\gdef" .. namePrefix .. "@z{" .. z .. "}"; +end + +local function findFirstValidCoord(coords) + for i=1,#coords do + local pt = coords[i] + if pt.x[1] ~=nil then + return pt + end + end + return nil +end + +local function findLastValidCoord(coords) + for i=#coords,1,-1 do + local pt = coords[i] + if pt.x[1] ~=nil then + return pt + end + end + return nil +end + +-- PUBLIC +-- +-- @return a set of (private) key-value pairs such that the TeX code of pgfplots can +-- access survey results of the provided plot handler +-- +-- @param plothandler an instance of Plothandler +function Axis:surveyToPgfplots(plothandler) + local plothandlerResult = plothandler:surveyend() + local firstCoord = findFirstValidCoord(plothandler.coords) or Coord.new() + local lastCoord = findLastValidCoord(plothandler.coords) or Coord.new() + + local result = + plothandlerResult .. + toTeXxyzCoord("\\pgfplots@currentplot@firstcoord", firstCoord) .. + toTeXxyzCoord("\\pgfplots@currentplot@lastcoord", lastCoord) .. + axisLimitToTeXString("\\pgfplots@metamin", plothandler.metamin) .. + axisLimitToTeXString("\\pgfplots@metamax", plothandler.metamax) .. + "\\c@pgfplots@coordindex=" .. tostring(plothandler.coordindex) .. " " .. + axisLimitToTeXString("\\pgfplots@xmin", self.min[1]) .. + axisLimitToTeXString("\\pgfplots@ymin", self.min[2]) .. + axisLimitToTeXString("\\pgfplots@xmax", self.max[1]) .. + axisLimitToTeXString("\\pgfplots@ymax", self.max[2]); + if self.is3d then + result = result .. + axisLimitToTeXString("\\pgfplots@zmin", self.min[3]) .. + axisLimitToTeXString("\\pgfplots@zmax", self.max[3]) .. + "\\global\\pgfplots@threedimtrue "; + end + if plothandler.plotHasJumps then + result = result .. + "\\def\\pgfplotsaxisplothasjumps{1}" + else + result = result .. + "\\def\\pgfplotsaxisplothasjumps{0}" + end + if plothandler.filteredCoordsAway then + result = result .. + "\\def\\pgfplotsaxisfilteredcoordsaway{1}" + else + result = result .. + "\\def\\pgfplotsaxisfilteredcoordsaway{0}" + end + + return result +end + +-- resembles \pgfplotsaxisvisphasetransformcoordinate +function Axis:visphasetransformcoordinate(pt) + for i = 1,#pt.x do + pt.x[i] = self.datascaleTrafo[i]:map( pt.x[i] ) + end +end + +-- will be set by TeX code (in \begin{axis}) +gca = nil + + +end diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/statistics.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/statistics.lua new file mode 100644 index 00000000000..57350254b30 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/statistics.lua @@ -0,0 +1,400 @@ +-- This file has dependencies to BOTH, the TeX part of pgfplots and the LUA part. +-- It is the only LUA component with this property. +-- +-- Its purpose is to encapsulate the communication between TeX and LUA in a central LUA file + +local pgfplotsmath = pgfplots.pgfplotsmath +local error=error +local table=table +local string=string +local tostring=tostring +local type=type +local io=io +local mathfloor=math.floor +local mathceil=math.ceil +local pgfmathparse = pgfplots.pgfluamathparser.pgfmathparse + +do +-- all globals will be read from/defined in pgfplots: +local _ENV = pgfplots + +local pgftonumber =pgfluamathfunctions.tonumber + +function texBoxPlotSurveyPoint(data) + gca.currentPlotHandler:semiSurveyedValue(data) +end + +------------------------------------------------------- + +PercentileEstimator = newClass() + +function PercentileEstimator:constructor() +end + +function PercentileEstimator:getIndex(data, i) + local idx = i + if idx < 1 then idx = 1 end + if idx > #data then idx = #data end + + local result = data[idx] + if not result then + error("Box plot percentile estimator '" .. tostring(self) .." accessed illegal array index " .. tostring(idx) .. " in array of length " .. tostring(#data)) + end + return result +end + + +-- @param percentile the requested percentile. Use 0.5 for the median, 0.25 for the first quartile, 0.95 for the 95% percentile etc. +function PercentileEstimator:getValue(percentile, data) + error("Use implementation of PercentileEstimator, not interface") +end + +-- LegacyPgfplotsPercentileEstimator is a minimally repaired percentile estimator as it has been shipped with pgfplots.10 . +-- I decided to mark it as deprecated because it is non-standard and not comparable with other programs. +LegacyPgfplotsPercentileEstimator = newClassExtends(PercentileEstimator) +function LegacyPgfplotsPercentileEstimator:constructor() +end +function LegacyPgfplotsPercentileEstimator:__tostring() + return "estimator=legacy"; +end +function LegacyPgfplotsPercentileEstimator:getValue(percentile, data) + if not percentile or not data then error("Arguments must not be nil") end + local numCoords = #data + local h = numCoords * percentile + + local offset_low = mathfloor(h) + local isInt = ( h==offset_low ) + + local offset_high = offset_low+1 + + local x_low = self:getIndex(data, offset_low) + local x_up = self:getIndex(data, offset_high) + local res = x_low + if not isInt then + res = 0.5 * (res + x_up) + end + return res +end + +-- LegacyBadPgfplotsPercentileEstimator is _the_ percentile estimator as it has been shipped with pgfplots 1.10. +-- It has bugs and is non-standard. Don't use it. +LegacyBadPgfplotsPercentileEstimator = newClassExtends(PercentileEstimator) +function LegacyBadPgfplotsPercentileEstimator:constructor() +end +function LegacyBadPgfplotsPercentileEstimator:__tostring() + return "estimator=legacy*"; +end +function LegacyBadPgfplotsPercentileEstimator:getValue(percentile, data) + if not percentile or not data then error("Arguments must not be nil") end + local numCoords = #data + local h = (numCoords-1) * percentile + + local offset_low = mathfloor(h) + local isInt = ( h==offset_low ) + + local offset_high = offset_low+1 + + local x_low = self:getIndex(data, offset_low+1) + local x_up = self:getIndex(data, offset_high+1) + local res = x_low + if not isInt then + res = 0.5 * (res + x_up) + end + return res +end +---------------- + +ParameterizedPercentileEstimator = newClassExtends(PercentileEstimator) + +function ParameterizedPercentileEstimator:__tostring() + return "estimator=" .. tostring(self.typeFlag) ; +end + +function ParameterizedPercentileEstimator:constructor( typeFlag ) + -- http://en.wikipedia.org/wiki/Quantile + self.typeFlag = typeFlag + + local getIndex = self.getIndex + + local stdLookup = function(data, h ) + local h_low = mathfloor(h) + local x_low = getIndex(self, data, h_low ) + local x_up = getIndex(self, data, h_low +1 ) + return x_low + (h - h_low) * (x_up - x_low) + end + + if typeFlag == 1 then + -- R1 + self.getValue = function(self, percentile, data) + local h= #data * percentile + return getIndex(self, data, mathceil(h) ) + end + elseif typeFlag == 2 then + -- R2 + self.getValue = function(self, percentile, data) + local h= #data * percentile + 0.5 + return 0.5*(getIndex(self, data, mathceil(h-0.5)) + getIndex(self, data, mathfloor(h+0.5) ) ) + end + elseif typeFlag == 3 then + -- R3 + self.getValue = function(self, percentile, data) + local h= #data * percentile + return getIndex(self, data, pgfluamathfunctions.round(h) ) + end + elseif typeFlag == 4 then + -- R4 + self.getValue = function(self, percentile, data) + local h= #data * percentile + return stdLookup(data,h) + end + elseif typeFlag == 5 then + -- R5 + self.getValue = function(self, percentile, data) + local h= #data * percentile + 0.5 + return stdLookup(data,h) + end + elseif typeFlag == 6 then + -- R6 + self.getValue = function(self, percentile, data) + local h= (#data +1) * percentile + return stdLookup(data,h) + end + elseif typeFlag == 7 then + -- R7 (Excel) + self.getValue = function(self, percentile, data) + local h= (#data -1) * percentile + 1 + return stdLookup(data,h) + end + elseif typeFlag == 8 then + -- R8 + self.getValue = function(self, percentile, data) + local h= (#data + 1/3) * percentile + 1/3 + return stdLookup(data,h) + end + elseif typeFlag == 9 then + -- R9 + self.getValue = function(self, percentile, data) + local h= (#data + 1/4) * percentile + 3/8 + return stdLookup(data,h) + end + else + error("Got unsupported type '" .. tostring(typeFlag) .. "'") + end +end + + +getPercentileEstimator = function(estimatorName) + if estimatorName == "legacy" then + return LegacyPgfplotsPercentileEstimator.new() + elseif estimatorName == "legacy*" then + return LegacyBadPgfplotsPercentileEstimator.new() + elseif estimatorName == "R1" then + return ParameterizedPercentileEstimator.new(1) + elseif estimatorName == "R2" then + return ParameterizedPercentileEstimator.new(2) + elseif estimatorName == "R3" then + return ParameterizedPercentileEstimator.new(3) + elseif estimatorName == "R4" then + return ParameterizedPercentileEstimator.new(4) + elseif estimatorName == "R5" then + return ParameterizedPercentileEstimator.new(5) + elseif estimatorName == "R6" then + return ParameterizedPercentileEstimator.new(6) + elseif estimatorName == "R7" then + return ParameterizedPercentileEstimator.new(7) + elseif estimatorName == "R8" then + return ParameterizedPercentileEstimator.new(8) + elseif estimatorName == "R9" then + return ParameterizedPercentileEstimator.new(9) + end + + error("Unknown estimator '" .. tostring(estimatorName) .. "'") +end + +BoxPlotRequest = newClass() + +-- @param lowerQuartialPercent: typically 0.25 +-- @param upperQuartialPercent: typically 0.75 +-- @param whiskerRange: typically 1.5 +-- @param estimator: an instance of PercentileEstimator +-- @param morePercentiles: either nil or an array of percentiles to compute +function BoxPlotRequest:constructor(lowerQuartialPercent, upperQuartialPercent, whiskerRange, estimator, morePercentiles) + if not lowerQuartialPercent or not upperQuartialPercent or not whiskerRange or not estimator then error("Arguments must not be nil") end + self.lowerQuartialPercent = pgftonumber(lowerQuartialPercent) + self.upperQuartialPercent = pgftonumber(upperQuartialPercent) + self.whiskerRange = pgftonumber(whiskerRange) + self.estimator = estimator + if not morePercentiles then + self.morePercentiles = {} + else + self.morePercentiles = morePercentiles + end +end + +------------------------------------------------------- + +BoxPlotResponse = newClass() + +function BoxPlotResponse:constructor() + self.lowerWhisker = nil + self.lowerQuartile = nil + self.median = nil + self.upperQuartile = nil + self.upperWhisker = nil + self.average = nil + self.morePercentiles = {} + self.outliers = {} +end + +-- @param boxPlotRequest an instance of BoxPlotRequest +-- @param data an indexed array with float values +-- @return an instance of BoxPlotResponse +function boxPlotCompute(boxPlotRequest, data) + if not boxPlotRequest or not data then error("Arguments must not be nil") end + + for i = 1,#data do + local data_i = data[i] + if data_i == nil or type(data_i) ~= "number" then + error("Illegal input array at index " .. tostring(i) .. ": " .. tostring(data_i)) + end + end + + table.sort(data) + + local sum = 0 + for i = 1,#data do + sum = sum + data[i] + end + + local numCoords = #data + + local lowerWhisker + local lowerQuartile = boxPlotRequest.estimator:getValue(boxPlotRequest.lowerQuartialPercent, data) + local median = boxPlotRequest.estimator:getValue(0.5, data) + local upperQuartile = boxPlotRequest.estimator:getValue(boxPlotRequest.upperQuartialPercent, data) + + local morePercentileValues = {} + for i = 1,#boxPlotRequest.morePercentiles do + morePercentileValues[i] = boxPlotRequest.estimator:getValue(boxPlotRequest.morePercentiles[i], data) + end + + local upperWhisker + local average = sum / numCoords + + local whiskerRange = boxPlotRequest.whiskerRange + local whiskerWidth = whiskerRange*(upperQuartile - lowerQuartile) + local upperWhiskerValue = upperQuartile + whiskerWidth + local lowerWhiskerValue = lowerQuartile - whiskerWidth + + local outliers = {} + for i = 1,numCoords do + local current = data[i] + if current < lowerWhiskerValue then + table.insert(outliers, current) + else + lowerWhisker = current + break + end + end + + for i = numCoords,1,-1 do + local current = data[i] + if upperWhiskerValue < current then + table.insert(outliers, current) + else + upperWhisker = current + break + end + end + + local result = BoxPlotResponse.new() + result.lowerWhisker = lowerWhisker + result.lowerQuartile = lowerQuartile + result.median = median + result.upperQuartile = upperQuartile + result.upperWhisker = upperWhisker + result.average = average + result.morePercentiles = morePercentileValues + result.outliers = outliers + + return result +end + +------------------------------------------------------- +-- Replicates the survey phase of \pgfplotsplothandlerboxplot +BoxPlotPlothandler = newClassExtends(Plothandler) + +-- drawDirection : either "x" or "y". +function BoxPlotPlothandler:constructor(boxPlotRequest, drawDirection, drawPosition, axis, pointmetainputhandler) + if not boxPlotRequest or not drawDirection or not drawPosition then error("Arguments must not be nil") end + Plothandler.constructor(self,"boxplot", axis, pointmetainputhandler) + self.boxPlotRequest = boxPlotRequest + + local function evaluateDrawPosition() + local result = pgfmathparse(drawPosition) + return result + end + + if drawDirection == "x" then + self.boxplotsetxy = function (a,b) return a,evaluateDrawPosition() + b end + elseif drawDirection == "y" then + self.boxplotsetxy = function (a,b) return evaluateDrawPosition() + b,a end + else + error("Illegal argument drawDirection="..tostring(drawDirection) ) + end +end + +function BoxPlotPlothandler:surveystart() + self.boxplotInput = {} + self.boxplotSurveyMode = true +end + + +function BoxPlotPlothandler:surveyend() + self.boxplotSurveyMode = false + + local computed = boxPlotCompute( self.boxPlotRequest, self.boxplotInput ) + + local texResult = + "\\pgfplotsplothandlersurveyend@boxplot@set{lower whisker}{" .. toTeXstring(computed.lowerWhisker) .. "}" .. + "\\pgfplotsplothandlersurveyend@boxplot@set{lower quartile}{" .. toTeXstring(computed.lowerQuartile) .. "}" .. + "\\pgfplotsplothandlersurveyend@boxplot@set{median}{" .. toTeXstring(computed.median) .. "}" .. + "\\pgfplotsplothandlersurveyend@boxplot@set{upper quartile}{" .. toTeXstring(computed.upperQuartile) .. "}" .. + "\\pgfplotsplothandlersurveyend@boxplot@set{upper whisker}{" .. toTeXstring(computed.upperWhisker) .. "}" .. + "\\pgfplotsplothandlersurveyend@boxplot@set{sample size}{" .. toTeXstring(# self.boxplotInput) .. "}" + + self.boxplotInput = nil + Plothandler.surveystart(self) + + local outliers = computed.outliers + for i =1,#outliers do + local outlier = outliers[i] + local pt = Coord.new() + -- this here resembles \pgfplotsplothandlersurveypoint@boxplot@prepared when it is invoked during boxplot: + local X,Y = self.boxplotsetxy(outlier, 0) + pt.x = { X, Y, nil } + Plothandler.surveypoint(self,pt) + end + Plothandler.surveyend(self) + + return texResult +end + +function BoxPlotPlothandler:semiSurveyedValue(data) + local result = pgftonumber(data) + if result then + table.insert( self.boxplotInput, result ) + end +end + +function BoxPlotPlothandler:surveypoint(pt) + if self.boxplotSurveyMode then + error("Unsupported Operation encountered: box plot survey in LUA are only in PARTIAL mode (i.e. only if almost all has been prepared in TeX. Use 'lua backend=false' to get around this.") + else + Plothandler.surveypoint(self,pt) + end +end + +------------------------------------------------------- + +end diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/streamer.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/streamer.lua new file mode 100644 index 00000000000..ab5b605d6d1 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplots/streamer.lua @@ -0,0 +1,193 @@ +-- Contains coordinate streamers, i.e. classes which generate coordinates and stream them to some output stream + +local math=math +local pgfplotsmath = pgfplots.pgfplotsmath +local type=type +local tostring=tostring +local error=error +local table=table + +do +-- all globals will be read from/defined in pgfplots: +local _ENV = pgfplots +----------------------------------- + +CoordOutputStream = newClass() + +function CoordOutputStream:constructor() +end + +-- @param pt an instance of Coord +function CoordOutputStream:coord(pt) +end + +----------------------------------- + +SurveyCoordOutputStream = newClassExtends(CoordOutputStream) + +function SurveyCoordOutputStream:constructor(targetPlothandler) + if not targetPlothandler then error("arguments must not be nil") end + self.plothandler= targetPlothandler +end + +function SurveyCoordOutputStream:coord(pt) + self.plothandler:surveypoint(pt) +end + +------------- +-- This is a partial reimplementation of \addplot expression: it samples points -- but entirely in LUA. Only the results are serialized back to TeX. + +AddplotExpressionCoordinateGenerator = newClass() + +function AddplotExpressionCoordinateGenerator:constructor(coordoutputstream, expressionsByDimension, domainMin, domainMax, samples, variableNames) + if not coordoutputstream or not expressionsByDimension or not domainMin or not domainMax or not samples or not variableNames then error("arguments must not be nil") end + if #variableNames ~= 2 then error("Expected 2 variableNames") end + self.coordoutputstream = coordoutputstream + self.is3d = #expressionsByDimension == 3 + self.expressions = expressionsByDimension + self.domainMin = domainMin + self.domainMax = domainMax + self.samples = samples + self.variableNames = variableNames + + -- log("initialized " .. tostring(self) .. "\n") +end + +-- @return true on success or false if the operation cannot be carried out. +-- this method is a replicate of \pgfplots@addplotimpl@expression@@ +function AddplotExpressionCoordinateGenerator:generateCoords() + local stringToFunctionMap = pgfluamathfunctions.stringToFunctionMap + -- create a backup of the 'x' and 'y' math expressions which + -- have been defined in \pgfplots@coord@stream@start: + local old_global_function_x = stringToFunctionMap["x"] + local old_global_function_y = stringToFunctionMap["y"] + + local coordoutputstream = self.coordoutputstream + local is3d = self.is3d + local expressions = self.expressions + local xExpr = expressions[1] + local yExpr = expressions[2] + local zExpr = expressions[3] + + local domainMin = self.domainMin + local domainMax = self.domainMax + local samples = self.samples + local h = {} + for i = 1,#domainMin do + h[i] = (domainMax[i] - domainMin[i]) / (samples[i]-1) + end + + local variableNames = self.variableNames + + local x,y + local sampleLine = #samples==1 + + local function pseudoconstantx() return x end + local pseudoconstanty + if sampleLine then + if yExpr ~= variableNames[2] then + -- suppress the warning - we want to allow (x,y,x^2) in this case. + pseudoconstanty = function() return 0 end + else + local didWarn = false + pseudoconstanty = function() + if not didWarn then + log("Sorry, you can't use 'y' in this context. PGFPlots expected to sample a line, not a mesh. Please use the [mesh] option combined with [samples y>0] and [domain y!=0:0] to indicate a twodimensional input domain\n") + didWarn = true + end + return 0 + end + end + else + pseudoconstanty = function() return y end + end + + local pgfmathparse = pgfluamathparser.pgfmathparse + local prepareX + if xExpr == variableNames[1] then + prepareX = function() return x end + else + prepareX = function() return pgfmathparse(xExpr) end + end + + local prepareY + if yExpr == variableNames[2] then + prepareY = function() return y end + else + prepareY = function() return pgfmathparse(yExpr) end + end + + local function computeXYZ() + stringToFunctionMap[variableNames[1]] = pseudoconstantx + stringToFunctionMap[variableNames[2]] = pseudoconstanty + local X = prepareX() + local Y = prepareY() + local Z = nil + if is3d then + Z = pgfmathparse(zExpr) + end + + local pt = Coord.new() + pt.x = { X, Y, Z} + + -- restore 'x' and 'y' + -- FIXME : defining the resulting x/y coordinates as 'x' and 'y' constants was a really really bad idea in the first place :-( + stringToFunctionMap["x"] = old_global_function_x + stringToFunctionMap["y"] = old_global_function_y + + coordoutputstream:coord(pt) + end + + if not sampleLine then + local xmin = domainMin[1] + local ymin = domainMin[2] + local hx = h[1] + local hy = h[2] + local max_i = samples[1]-1 + local max_j = samples[2]-1 + -- samples twodimensionally (a lattice): + for j = 0,max_j do + -- FIXME : pgfplots@plot@data@notify@next@y + y = ymin + j*hy + -- log("" .. j .. "\n") + for i = 0,max_i do + -- FIXME : pgfplots@plot@data@notify@next@x + x = xmin + i*hx + computeXYZ() + end + -- FIXME : \pgfplotsplothandlernotifyscanlinecomplete + end + else + local xmin = domainMin[1] + local hx = h[1] + local max_i = samples[1]-1 + for i = 0,max_i do + -- FIXME : pgfplots@plot@data@notify@next@x + x = xmin + i*hx + computeXYZ() + end + end + + stringToFunctionMap[variableNames[1]] = nil + stringToFunctionMap[variableNames[2]] = nil + return true +end + +function AddplotExpressionCoordinateGenerator:__tostring() + local result = "AddplotExpressionCoordinateGenerator[\n" + result = result .. "\n variable(s)=" .. self.variableNames[1] .. " " .. self.variableNames[2] + result = result .. "\n expressions=" + for i = 1,#self.expressions do + result = result .. self.expressions[i] ..", " + end + result = result .. "\n domain=" .. self.domainMin[1] .. ":" .. self.domainMax[1] + result = result .. "\n samples=" .. self.samples[1] + if #self.domainMin == 2 then + result = result .. "\n domain y=" .. self.domainMin[2] .. ":" .. self.domainMax[2] + result = result .. "\n samples y=" .. self.samples[2] + end + result = result .. "]" + return result +end + +end diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/functions.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/functions.lua new file mode 100644 index 00000000000..e5573f26f64 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/functions.lua @@ -0,0 +1,649 @@ +-------------------------------------------------------------------------------------------------- +------ This file is a copy of some part of PGF/Tikz. +------ It has been copied here to provide : +------ - compatibility with older PGF versions +------ - availability of PGF contributions by Christian Feuersaenger +------ which are necessary or helpful for pgfplots. +------ +------ For reasons of simplicity, I have copied the whole file, including own contributions AND +------ PGF parts. The copyrights are as they appear in PGF. +------ +------ Note that pgfplots has compatible licenses. +------ +------ This copy has been modified in the following ways: +------ - nested \input commands have been updated +------ +-- +-- Support for the contents of this file will NOT be done by the PGF/TikZ team. +-- Please contact the author and/or maintainer of pgfplots (Christian Feuersaenger) if you need assistance in conjunction +-- with the deployment of this patch or partial content of PGF. Note that the author and/or maintainer of pgfplots has no obligation to fix anything: +-- This file comes without any warranty as the rest of pgfplots; there is no obligation for help. +---------------------------------------------------------------------------------------------------- +-- Date of this copy: Mi 14. Jan 21:15:32 CET 2015 --- + + + +-- Copyright 2011 by Christophe Jorssen +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License. +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more details. +-- +-- $Id: functions.lua,v 1.2 2015/01/14 20:15:13 cfeuersaenger Exp $ +-- + +local pgfluamathfunctions = pgfluamathfunctions or {} + +-- Maps function names to their function. +-- +-- Note that this allows to register functions which are not in pgfluamathfunctions. +-- +-- Note that the string keys are not necessarily the same as the function +-- names. In particular, the math expression "not(1,1)" will execute notPGF(1,1) +-- +-- Note that each function which is added to pgfluamathfunctions will _automatically_ be inserted into this map, see __newindex. +-- (I fear it will not be erased directly...) +pgfluamathfunctions.stringToFunctionMap = {} + +local newFunctionAllocatedCallback = function(table,key,value) + local keyName = tostring(key):gsub("PGF","") + if not value then + stringToFunctionMap[keyName] = nil + elseif type(value) == 'function' then + -- remember it, and strip PGF suffix (i.e. remember 'not' instead of 'notPGF') + pgfluamathfunctions.stringToFunctionMap[keyName] = value + end + rawset(table,key,value) +end + +setmetatable(pgfluamathfunctions, { __newindex = newFunctionAllocatedCallback }) + +local mathabs, mathacos, mathasin = math.abs, math.acos, math.asin +local mathatan, mathceil = math.atan, math.ceil +local mathcos, mathdeg = math.cos, math.deg +local mathexp, mathfloor, mathfmod = math.exp, math.floor, math.fmod +local mathlog, mathmax = math.log, math.max +local mathmin, mathpi = math.min, math.pi +local mathrad, mathrandom = math.rad, math.random +local mathrandomseed, mathsin = math.randomseed, math.sin +local mathsqrt = math.sqrt +local mathtan = math.tan + +local trigFormatToRadians = mathrad + +local radiansToTrigFormat = mathdeg + +pgfluamathfunctions.TrigFormat = { 'deg', 'rad' } +pgfluamathfunctions.stringToFunctionMap["TrigFormat"] = nil + +-- choice is one of the valid choices in TrigFormat. +function pgfluamathfunctions.setTrigFormat(choice) + if choice == 'deg' then + trigFormatToRadians = mathrad + radiansToTrigFormat = mathdeg + elseif choice == 'rad' then + local identity = function(x) return x end + trigFormatToRadians = identity + radiansToTrigFormat = identity + else + error("The argument '" .. tostring(choice) .. "' is no valid choice for setTrigFormat.") + end +end +pgfluamathfunctions.stringToFunctionMap["setTrigFormat"] = nil + +pgfluamathfunctions.setRandomSeed = mathrandomseed +pgfluamathfunctions.stringToFunctionMap["setRandomSeed"] = nil + +------------------------------------------- + +function pgfluamathfunctions.add(x,y) + return x+y +end + +function pgfluamathfunctions.subtract(x,y) + return x-y +end + +function pgfluamathfunctions.neg(x) + return -x +end + +function pgfluamathfunctions.multiply(x,y) + return x*y +end + +function pgfluamathfunctions.veclen(x,y) + return mathsqrt(x*x+y*y) +end + +function pgfluamathfunctions.divide(x,y) + return x/y +end + +function pgfluamathfunctions.div(x,y) + return mathfloor(x/y) +end + +function pgfluamathfunctions.pow(x,y) + -- do not use math.pow -- it is deprecated as of LUA 5.3 + return x^y +end + +function pgfluamathfunctions.factorial(x) +-- TODO: x must be an integer + if x == 0 then + return 1 + else + return x * pgfluamathfunctions.factorial(x-1) + end +end + +function pgfluamathfunctions.ifthenelse(x,y,z) + if x~= 0 then + return y + else + return z + end +end + +function pgfluamathfunctions.equal(x,y) + if x == y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.greater(x,y) + if x > y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.less(x,y) + if x < y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.min(x,y) + return mathmin(x,y) +end + +function pgfluamathfunctions.max(x,y) + return mathmax(x,y) +end + +function pgfluamathfunctions.notequal(x,y) + if x ~= y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.notless(x,y) + if x >= y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.notgreater(x,y) + if x <= y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.andPGF(x,y) + if (x ~= 0) and (y ~= 0) then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.orPGF(x,y) + if (x ~= 0) or (y ~= 0) then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.notPGF(x) + if x == 0 then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.pi() + return mathpi +end + +function pgfluamathfunctions.e() + return mathexp(1) +end + +function pgfluamathfunctions.abs(x) + return mathabs(x) +end + +function pgfluamathfunctions.floor(x) + return mathfloor(x) +end + +function pgfluamathfunctions.ceil(x) + return mathceil(x) +end + +function pgfluamathfunctions.exp(x) + return mathexp(x) +end + +function pgfluamathfunctions.ln(x) + return mathlog(x) +end + +local logOf10 = mathlog(10) +function pgfluamathfunctions.log10(x) + return mathlog(x) / logOf10 +end + +local logOf2 = mathlog(2) +function pgfluamathfunctions.log2(x) + return mathlog(x) / logOf2 +end + +function pgfluamathfunctions.sqrt(x) + return mathsqrt(x) +end + +function pgfluamathfunctions.sign(x) + if x < 0 then + return -1.0 + elseif x == 0 then + return 0.0 + else + return 1.0 + end +end +function pgfluamathfunctions.real(x) + -- "ensure that x contains a decimal point" is kind of a no-op here, isn't it!? + return x +end + +function pgfluamathfunctions.rnd() + return mathrandom() +end + +function pgfluamathfunctions.rand() + return -1 + mathrandom() *2 +end + +function pgfluamathfunctions.random(x,y) + if x == nil and y == nil then + return mathrandom() + elseif y == nil then + return mathrandom(x) + else + return mathrandom(x,y) + end +end + +function pgfluamathfunctions.deg(x) + return mathdeg(x) +end + +function pgfluamathfunctions.rad(x) + return mathrad(x) +end + +function pgfluamathfunctions.round(x) + if x<0 then + return -mathfloor(mathabs(x)+0.5) + else + return mathfloor(x + 0.5) + end +end + +function pgfluamathfunctions.gcd(a, b) + if b == 0 then + return a + else + return pgfluamathfunctions.gcd(b, a%b) + end +end + +function pgfluamathfunctions.isprime(a) + local ifisprime = true + if a == 1 then + ifisprime = false + elseif a == 2 then + ifisprime = true +-- if a > 2 then + else + local i, imax = 2, mathceil(mathsqrt(a)) + 1 + while ifisprime and (i < imax) do + if pgfluamathfunctions.gcd(a,i) ~= 1 then + ifisprime = false + end + i = i + 1 + end + end + if ifisprime then + return 1 + else + return 0 + end +end + + +function pgfluamathfunctions.split_braces_to_explist(s) + -- (Thanks to mpg and zappathustra from fctt) + -- Make unpack available whatever lua version is used + -- (unpack in lua 5.1 table.unpack in lua 5.2) + local unpack = table.unpack or unpack + local t = {} + for i in s:gmatch('%b{}') do + table.insert(t, tonumber(i:sub(2, -2))) + end + return unpack(t) +end + +function pgfluamathfunctions.split_braces_to_table(s) + local t = {} + for i in s:gmatch('%b{}') do + table.insert(t, tonumber(i:sub(2, -2))) + end + return t +end + +function pgfluamathfunctions.mathtrue() + return 1.0 +end +pgfluamathfunctions.stringToFunctionMap["true"] = pgfluamathfunctions.mathtrue + +function pgfluamathfunctions.mathfalse() + return 0.0 +end +pgfluamathfunctions.stringToFunctionMap["false"] = pgfluamathfunctions.mathfalse + +function pgfluamathfunctions.frac(a) + -- should be positive, apparently + return mathabs(a - pgfluamathfunctions.int(a)) +end + +function pgfluamathfunctions.int(a) + if a < 0 then + return -mathfloor(mathabs(a)) + else + return mathfloor(a) + end +end + +function pgfluamathfunctions.iseven(a) + if (a % 2) == 0 then + return 1.0 + else + return 0.0 + end +end + +function pgfluamathfunctions.isodd(a) + if (a % 2) == 0 then + return 0.0 + else + return 1.0 + end +end + +function pgfluamathfunctions.mod(x,y) + if x/y < 0 then + return -(mathabs(x)%mathabs(y)) + else + return mathabs(x)%mathabs(y) + end +end + +function pgfluamathfunctions.Mod(x,y) + local tmp = pgfluamathfunctions.mod(x,y) + if tmp < 0 then + tmp = tmp + y + end + return tmp +end + +function pgfluamathfunctions.Sin(x) + return mathsin(trigFormatToRadians(x)) +end +pgfluamathfunctions.sin=pgfluamathfunctions.Sin + + +function pgfluamathfunctions.cosh(x) + -- math.cosh is deprecated as of LUA 5.3 . reimplement it: + return 0.5* (mathexp(x) + mathexp(-x)) +end +function pgfluamathfunctions.sinh(x) + -- math.sinh is deprecated as of LUA 5.3 . reimplement it: + return 0.5* (mathexp(x) - mathexp(-x)) +end + +local sinh = pgfluamathfunctions.sinh +local cosh = pgfluamathfunctions.cosh +function pgfluamathfunctions.tanh(x) + -- math.tanh is deprecated as of LUA 5.3 . reimplement it: + return sinh(x)/cosh(x) +end + +function pgfluamathfunctions.Cos(x) + return mathcos(trigFormatToRadians(x)) +end +pgfluamathfunctions.cos=pgfluamathfunctions.Cos + +function pgfluamathfunctions.Tan(x) + return mathtan(trigFormatToRadians(x)) +end +pgfluamathfunctions.tan=pgfluamathfunctions.Tan + +function pgfluamathfunctions.aSin(x) + return radiansToTrigFormat(mathasin(x)) +end +pgfluamathfunctions.asin=pgfluamathfunctions.aSin + +function pgfluamathfunctions.aCos(x) + return radiansToTrigFormat(mathacos(x)) +end +pgfluamathfunctions.acos=pgfluamathfunctions.aCos + +function pgfluamathfunctions.aTan(x) + return radiansToTrigFormat(mathatan(x)) +end +pgfluamathfunctions.atan=pgfluamathfunctions.aTan + +local mathatan2 +if math.atan2 == nil then + -- math.atan2 has been deprecated since LUA 5.3 + mathatan2 = function (y,x) return mathatan(y,x) end +else + mathatan2 = math.atan2 +end + +function pgfluamathfunctions.aTan2(y,x) + return radiansToTrigFormat(mathatan2(y,x)) +end +pgfluamathfunctions.atan2=pgfluamathfunctions.aTan2 +pgfluamathfunctions.atantwo=pgfluamathfunctions.aTan2 + +function pgfluamathfunctions.cot(x) + return pgfluamathfunctions.cos(x) / pgfluamathfunctions.sin(x) +end +function pgfluamathfunctions.sec(x) + return 1 / pgfluamathfunctions.cos(x) +end +function pgfluamathfunctions.cosec(x) + return 1 / pgfluamathfunctions.sin(x) +end + +function pgfluamathfunctions.pointnormalised (pgfx, pgfy) + local pgfx_normalised, pgfy_normalised + if pgfx == 0. and pgfy == 0. then + -- Orginal pgf macro gives this result + tex.dimen['pgf@x'] = "0pt" + tex.dimen['pgf@y'] = "1pt" + else + pgfx_normalised = pgfx/math.sqrt(pgfx^2 + pgfy^2) + pgfx_normalised = pgfx_normalised - pgfx_normalised%0.00001 + pgfy_normalised = pgfy/math.sqrt(pgfx^2 + pgfy^2) + pgfy_normalised = pgfy_normalised - pgfy_normalised%0.00001 + tex.dimen['pgf@x'] = tostring(pgfx_normalised) .. "pt" + tex.dimen['pgf@y'] = tostring(pgfy_normalised) .. "pt" + end + return nil +end + +local isnan = function(x) + return x ~= x +end + +pgfluamathfunctions.isnan = isnan + +local infty = 1/0 +pgfluamathfunctions.infty = infty + +local nan = math.sqrt(-1) +pgfluamathfunctions.nan = nan + +local stringlen = string.len +local globaltonumber = tonumber +local stringsub=string.sub +local stringformat = string.format +local stringsub = string.sub + +-- like tonumber(x), but it also accepts nan, inf, infty, and the TeX FPU format +function pgfluamathfunctions.tonumber(x) + if type(x) == 'number' then return x end + if not x then return x end + + local len = stringlen(x) + local result = globaltonumber(x) + if not result then + if len >2 and stringsub(x,2,2) == 'Y' and stringsub(x,len,len) == ']' then + -- Ah - some TeX FPU input of the form 1Y1.0e3] . OK. transform it + local flag = stringsub(x,1,1) + if flag == '0' then + -- ah, 0.0 + result = 0.0 + elseif flag == '1' then + result = globaltonumber(stringsub(x,3, len-1)) + elseif flag == '2' then + result = globaltonumber("-" .. stringsub(x,3, len-1)) + elseif flag == '3' then + result = nan + elseif flag == '4' then + result = infty + elseif flag == '5' then + result = -infty + end + else + local lower = x:lower() + if lower == 'nan' then + result = nan + elseif lower == 'inf' or lower == 'infty' then + result = infty + elseif lower == '-inf' or lower == '-infty' then + result = -infty + end + end + end + + return result +end + +local stringlen = string.len +local globaltonumber = tonumber +local stringformat = string.format +local stringsub = string.sub +local stringfind = string.find +local stringbyte = string.byte +local NULL_CHAR = string.byte("0",1) + +local function discardTrailingZeros(x) + local result = x + -- printf is too stupid: I would like to have + -- 1. a fast method + -- 2. a reliable method + -- 3. full precision of x + -- 4. a fixed point representation + -- the 'f' modifier has trailing zeros (stupid!) + -- the 'g' modified can switch to scientific notation (no-go!) + local periodOff = stringfind(result, '.',1,true) + if periodOff ~= nil then + -- strip trailing zeros + local chars = { stringbyte(result,1,#result) }; + local lastNonZero = #chars + for i = #chars, periodOff, -1 do + if chars[i] ~= NULL_CHAR then lastNonZero=i; break; end + end + if lastNonZero ~= #chars then + -- Ah: we had at least one trailing zero. + -- discard all but the last. + lastNonZero = mathmax(periodOff+1,lastNonZero) + end + result = stringsub(result, 1, lastNonZero) + end + return result; +end + +local function discardTrailingZerosFromMantissa(x) + local mantissaStart = stringfind(x, "e") + + local mantissa = stringsub(x,1,mantissaStart-1) + local exponent = stringsub(x,mantissaStart) + + return discardTrailingZeros(mantissa) .. exponent +end + + +-- a helper function which has no catcode issues when communicating with TeX: +function pgfluamathfunctions.tostringfixed(x) + if x == nil then + return "" + end + + return discardTrailingZeros(stringformat("%f", x)) +end + +-- converts an input number to a string which is accepted by the TeX FPU +function pgfluamathfunctions.toTeXstring(x) + local result = "" + if x ~= nil then + if x == infty then result = "4Y0.0e0]" + elseif x == -infty then result = "5Y0.0e0]" + elseif isnan(x) then result = "3Y0.0e0]" + elseif x == 0 then result = "0Y0.0e0]" + else + result = discardTrailingZerosFromMantissa(stringformat("%.10e", x)) + if x > 0 then + result = "1Y" .. result .. "]" + else + result = "2Y" .. stringsub(result,2) .. "]" + end + end + end + return result +end + +return pgfluamathfunctions diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/parser.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/parser.lua new file mode 100644 index 00000000000..cc9663f145f --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/parser.lua @@ -0,0 +1,495 @@ +-------------------------------------------------------------------------------------------------- +------ This file is a copy of some part of PGF/Tikz. +------ It has been copied here to provide : +------ - compatibility with older PGF versions +------ - availability of PGF contributions by Christian Feuersaenger +------ which are necessary or helpful for pgfplots. +------ +------ For reasons of simplicity, I have copied the whole file, including own contributions AND +------ PGF parts. The copyrights are as they appear in PGF. +------ +------ Note that pgfplots has compatible licenses. +------ +------ This copy has been modified in the following ways: +------ - nested \input commands have been updated +------ +-- +-- Support for the contents of this file will NOT be done by the PGF/TikZ team. +-- Please contact the author and/or maintainer of pgfplots (Christian Feuersaenger) if you need assistance in conjunction +-- with the deployment of this patch or partial content of PGF. Note that the author and/or maintainer of pgfplots has no obligation to fix anything: +-- This file comes without any warranty as the rest of pgfplots; there is no obligation for help. +---------------------------------------------------------------------------------------------------- +-- Date of this copy: Mi 14. Jan 21:15:32 CET 2015 --- + + + +-- Copyright 2011 by Christophe Jorssen and Mark Wibrow +-- Copyright 2014 by Christian Feuersaenger +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License. +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more details. +-- +-- $Id: parser.lua,v 1.1 2014/12/27 14:11:49 cfeuersaenger Exp $ +-- +-- usage: +-- +-- pgfluamathparser = require("pgfplotsoldpgfsupp.luamath.parser") +-- +-- local result = pgfluamathparser.pgfmathparse("1+ 2*4^2") +-- +-- This LUA class has a direct backend in \pgfuselibrary{luamath}, see the documentation of that TeX package. + +local pgfluamathparser = pgfluamathparser or {} + +pgfluamathfunctions = require("pgfplotsoldpgfsupp.luamath.functions") + +-- lpeg is always present in luatex +local lpeg = require("lpeg") + +local S, P, R = lpeg.S, lpeg.P, lpeg.R +local C, Cc, Ct = lpeg.C, lpeg.Cc, lpeg.Ct +local Cf, Cg, Cs = lpeg.Cf, lpeg.Cg, lpeg.Cs +local V = lpeg.V +local match = lpeg.match + +local space_pattern = S(" \n\r\t")^0 +local tex_unit = + P('pt') + P('mm') + P('cm') + P('in') + + -- while valid units, the font-depending ones need special attention... move them to the TeX side. For now. + -- P('ex') + P('em') + + P('bp') + P('pc') + + P('dd') + P('cc') + P('sp'); + +local one_digit_pattern = R("09") +local positive_integer_pattern = one_digit_pattern^1 +-- FIXME : it might be a better idea to remove '-' from all number_patterns! Instead, rely on the prefix operator 'neg' to implement negative numbers. +-- Is that wise? It is certainly less efficient... +local integer_pattern = S("+-")^-1 * positive_integer_pattern +-- Valid positive decimals are |xxx.xxx|, |.xxx| and |xxx.| +local positive_integer_or_decimal_pattern = positive_integer_pattern * ( P(".") * one_digit_pattern^0)^-1 + + (P(".") * one_digit_pattern^1) +local integer_or_decimal_pattern = S("+-")^-1 * positive_integer_or_decimal_pattern +local fpu_pattern = R"15" * P"Y" * positive_integer_or_decimal_pattern * P"e" * P("-")^-1 * R("09")^1 * P"]" +local unbounded_pattern = P"inf" + P"INF" + P"nan" + P"NaN" + P"Inf" +local number_pattern = C(unbounded_pattern + fpu_pattern + integer_or_decimal_pattern * (S"eE" * integer_pattern + C(tex_unit))^-1) + +local underscore_pattern = P("_") + +local letter_pattern = R("az","AZ") +local alphanum__pattern = letter_pattern + one_digit_pattern + underscore_pattern + +local identifier_pattern = letter_pattern^1 * alphanum__pattern^0 + +local openparen_pattern = P("(") * space_pattern +local closeparen_pattern = P(")") +local opencurlybrace_pattern = P("{") +local closecurlybrace_pattern = P("}") +local openbrace_pattern = P("[") +local closebrace_pattern = P("]") + +-- hm. what about '\\' or '\%' ? +-- accept \pgf@x, \count0, \dimen42, \c@pgf@counta, \wd0, \ht0, \dp 0 +local controlsequence_pattern = P"\\" * C( (R("az","AZ") + P"@")^1) * space_pattern* C( R"09"^0 ) + +-- local string = P('"') * C((1 - P('"'))^0) * P('"') + +local comma_pattern = P(",") * space_pattern + + +---------------- +local TermOp = C(S("+-")) * space_pattern +local RelationalOp = C( P"==" + P"!=" + P"<=" + P">=" + P"<" + P">" ) * space_pattern +local FactorOp = C(S("*/")) * space_pattern + +-- Grammar +local Exp, Term, Factor = V"Exp", V"Term", V"Factor" +local Prefix = V"Prefix" +local Postfix = V"Postfix" + + + +local function eval (v1, op, v2) + if (op == "+") then return v1 + v2 + elseif (op == "-") then return v1 - v2 + elseif (op == "*") then return v1 * v2 + elseif (op == "/") then return v1 / v2 + else + error("This function must not be invoked for operator "..op) + end +end + +local pgfStringToFunctionMap = pgfluamathfunctions.stringToFunctionMap +local function function_eval(name, ... ) + local f = pgfStringToFunctionMap[name] + if not f then + error("Function '" .. name .. "' is undefined (did not find pgfluamathfunctions."..name .." (looked into pgfluamathfunctions.stringToFunctionMap))") + end + -- FIXME: validate signature + return f(...) +end + + +local func = + (C(identifier_pattern) * space_pattern * openparen_pattern * Exp * (comma_pattern * Exp)^0 * closeparen_pattern) / function_eval; + +local functionWithoutArg = identifier_pattern / function_eval + +-- this is what can occur as exponent after '^'. +-- I have the impression that the priorities could be implemented in a better way than this... but it seems to work. +local pow_exponent = + -- allows 2^-4, 2^1e4, 2^2 + -- FIXME : why not 2^1e2 ? + Cg(C(integer_or_decimal_pattern) + -- 2^pi, 2^multiply(2,2) + + Cg(func+functionWithoutArg) + -- 2^(2+2) + + openparen_pattern * Exp * closeparen_pattern ) + +local function prefix_eval(op, x) + if op == "-" then + return pgfluamathfunctions.neg(x) + elseif op == "!" then + return pgfluamathfunctions.notPGF(x) + else + error("This function must not be invoked for operator "..op) + end +end + + +local prefix_operator = C( S"-!" ) +local prefix_operator_pattern = (prefix_operator * space_pattern * Cg(Prefix) ) / prefix_eval + +-- apparently, we need to distinghuish between <expr> ! and <expr> != <expr2>: +local postfix_operator = C( S"r!" - P"!=" ) + C(P"^") * space_pattern * pow_exponent + + +local ternary_eval = pgfluamathfunctions.ifthenelse + +local factorial_eval = pgfluamathfunctions.factorial +local deg = pgfluamathfunctions.deg +local pow_eval = pgfluamathfunctions.pow + +-- @param prefix the argument before the postfix operator. +-- @param op either nil or the postfix operator +-- @param arg either nil or the (mandatory) argument for 'op' +local function postfix_eval(prefix, op, arg) + local result + if op == nil then + result = prefix + elseif op == "r" then + if arg then error("parser setup error: expected nil argument") end + result = deg(prefix) + elseif op == "!" then + if arg then error("parser setup error: expected nil argument") end + result = factorial_eval(prefix) + elseif op == "^" then + if not arg then error("parser setup error: ^ with its argument") end + result = pow_eval(prefix, arg) + else + error("Parser setup error: " .. tostring(op) .. " unexpected in this context") + end + return result +end + +local function relational_eval(v1, op, v2) + local fct + if (op == "==") then fct = pgfluamathfunctions.equal + elseif (op == "!=") then fct = pgfluamathfunctions.notequal + elseif (op == "<") then fct = pgfluamathfunctions.less + elseif (op == ">") then fct = pgfluamathfunctions.greater + elseif (op == ">=") then fct = pgfluamathfunctions.notless + elseif (op == "<=") then fct = pgfluamathfunctions.notgreater + else + error("This function must not be invoked for operator "..op) + end + return fct(v1,v2) +end + +-- @return either the box property or nil +-- @param cs "wd", "ht", or "dp" +-- @param intSuffix some integer +local function get_tex_box(cs, intSuffix) + -- assume get_tex_box is only called when a dimension is required. + local result + pgfluamathparser.units_declared = true + local box =tex.box[tonumber(intSuffix)] + if not box then error("There is no box " .. intSuffix) end + if cs == "wd" then + result = box.width / 65536 + elseif cs == "ht" then + result = box.height / 65536 + elseif cs == "dp" then + result = box.depth / 65536 + else + result = nil + end + return result +end + + +local function controlsequence_eval(cs, intSuffix) + local result + if intSuffix and #intSuffix >0 then + if cs == "count" then + result= pgfluamathparser.get_tex_count(intSuffix) + elseif cs == "dimen" then + result= pgfluamathparser.get_tex_dimen(intSuffix) + else + result = get_tex_box(cs,intSuffix) + if not result then + -- this can happen - we cannot expand \chardef'ed boxes here. + -- this will be done by the TeX part + error('I do not know/support the TeX register "\\' .. cs .. '"') + end + end + else + result = pgfluamathparser.get_tex_register(cs) + end + return result +end + +pgfluamathparser.units_declared = false +function pgfluamathparser.get_tex_register(register) + -- register is a string which could be a count or a dimen. + if pcall(tex.getcount, register) then + return tex.count[register] + elseif pcall(tex.getdimen, register) then + pgfluamathparser.units_declared = true + return tex.dimen[register] / 65536 -- return in points. + else + error('I do not know the TeX register "' .. register .. '"') + return nil + end + +end + +function pgfluamathparser.get_tex_count(count) + -- count is expected to be a number + return tex.count[tonumber(count)] +end + +function pgfluamathparser.get_tex_dimen(dimen) + -- dimen is expected to be a number + pgfluamathparser.units_declared = true + return tex.dimen[tonumber(dimen)] / 65536 +end + +function pgfluamathparser.get_tex_sp(dimension) + -- dimension should be a string + pgfluamathparser.units_declared = true + return tex.sp(dimension) / 65536 +end + + +local initialRule = V"initial" + +local Summand = V"Summand" +local Relational = V"Relational" +local LogicalOr = V"LogicalOr" +local LogicalAnd = V"LogicalAnd" + +local pgftonumber = pgfluamathfunctions.tonumber +local tonumber_withunit = pgfluamathparser.get_tex_sp +local function number_optional_units_eval(x, unit) + if not unit then + return pgftonumber(x) + else + return tonumber_withunit(x) + end +end + +-- @param scale the number. +-- @param controlsequence either nil in which case just the number must be returned or a control sequence +-- @see controlsequence_eval +local function scaled_controlsequence_eval(scale, controlsequence, intSuffix) + if controlsequence==nil then + return scale + else + return scale * controlsequence_eval(controlsequence, intSuffix) + end +end + +-- Grammar +-- +-- for me: +-- - use '/' to evaluate all expressions which contain a _constant_ number of captures. +-- - use Cf to evaluate expressions which contain a _dynamic_ number of captures +-- +-- see unittest_luamathparser.tex for tons of examples +local G = P{ "initialRule", + initialRule = space_pattern* Exp * -1; + -- ternary operator (or chained ternary operators): + -- FIXME : is this chaining a good idea!? + Exp = Cf( Relational * Cg(P"?" * space_pattern * Relational * P":" *space_pattern * Relational )^0, ternary_eval) ; + -- FIXME : do we really allow something like " 1 == 1 != 2" ? I would prefer (1==1) != 2 !? + Relational = Cf(LogicalOr * Cg(RelationalOp * LogicalOr)^0, relational_eval); + LogicalOr = Cf(LogicalAnd * (P"||" * space_pattern * LogicalAnd)^0, pgfluamathfunctions.orPGF); + LogicalAnd = Cf(Summand * (P"&&" * space_pattern * Summand)^0, pgfluamathfunctions.andPGF); + Summand = Cf(Term * Cg(TermOp * Term)^0, eval) ; + Term = Cf(Prefix * Cg(FactorOp * Prefix)^0, eval); + Prefix = prefix_operator_pattern + Postfix; + -- this calls 'postfix_eval' with nil arguments if it is no postfix operation.. but that does not hurt (right?) + Postfix = Factor * (postfix_operator * space_pattern)^-1 / postfix_eval; + Factor = + ( + number_pattern / number_optional_units_eval * + -- this construction will evaluate number_pattern with 'number_optional_units_eval' FIRST. + -- also accept '0.5 \pgf@x' here: + space_pattern *controlsequence_pattern^-1 / scaled_controlsequence_eval + + func + + functionWithoutArg + + openparen_pattern * Exp * closeparen_pattern + + controlsequence_pattern / controlsequence_eval + ) *space_pattern + ; +} + +-- does not reset units_declared. +local function pgfmathparseinternal(str) + local result = match(G,str) + if result == nil then + error("The string '" .. str .. "' is no valid PGF math expression. Please check for syntax errors.") + end + return result +end + + +-- This is the math parser function in this module. +-- +-- @param str a string like "1+1" which is accepted by the PGF math language +-- @return the result of the expression. +-- +-- Throws an error if the string is no valid expression. +function pgfluamathparser.pgfmathparse(str) + pgfluamathparser.units_declared = false + + return pgfmathparseinternal(str) +end + +local pgfmathparse = pgfluamathparser.pgfmathparse +local tostringfixed = pgfluamathfunctions.tostringfixed +local tostringfpu = pgfluamathfunctions.toTeXstring + +local tmpFunctionArgumentPrefix = "tmpVar" +local stackOfLocalFunctions = {} + +-- This is a backend for PGF's 'declare function'. +-- \tikzset{declare function={mu(\x,\i)=\x^\i;}} +-- will boil down to +-- pgfluamathparser.declareExpressionFunction("mu", 2, "#1^#2") +-- +-- The local function will be pushed on a stack of known local functions and is +-- available until popLocalExpressionFunction() is called. TeX will call this using +-- \aftergroup. +-- +-- @param name the name of the new function +-- @param numArgs the number of arguments +-- @param expression an expression containing #1, ... #n where n is numArgs +-- +-- ATTENTION: local functions behave DIFFERENTLY in LUA! +-- In LUA, local variables are not expanded whereas TeX expands them. +-- The difference is +-- +-- declare function={mu1(\x,\i)=\x^\i;} +-- \pgfmathparse{mu1(-5,2)} --> -25 +-- \pgfluamathparse{mu1(-5,2)} --> 25 +-- +-- x = -5 +-- \pgfmathparse{mu1(x,2)} --> 25 +-- \pgfluamathparse{mu1(x,2)} --> 25 +-- +-- In an early prototype, I simulated TeX's expansion to fix the first case (successfully). +-- BUT: that "simulated expansion" broke the second case because LUA will evaluate "x" and hand -5 to the local function. +-- I decided to keep it as is. Perhaps we should fix PGF's expansion approach in TeX (which is ugly anyway) +function pgfluamathparser.pushLocalExpressionFunction(name, numArgs, expression) + -- now we have "tmpVar1^tmpVar2" instead of "#1^#2" + local normalizedExpr = expression:gsub("#", tmpFunctionArgumentPrefix) + local restores = {} + local tmpVars = {} + for i=1,numArgs do + local tmpVar = tmpFunctionArgumentPrefix .. tostring(i) + tmpVars[i] = tmpVar + end + + local newFunction = function(...) + local args = table.pack(...) + + -- define "tmpVar1" ... "tmpVarN" to return args[i]. + -- Of course, we need to restore "tmpVar<i>" after we return! + for i=1,numArgs do + local tmpVar = tmpVars[i] + local value = args[i] + restores[i] = pgfStringToFunctionMap[tmpVar] + pgfStringToFunctionMap[tmpVar] = function () return value end + end + + -- parse our expression. + + -- FIXME : this here is an attempt to mess around with "units_declared". + -- It would be better to call pgfmathparse and introduce some + -- semaphore to check if pgfmathparse is a nested call-- in this case, it should + -- not reset units_declared. But there is no "finally" block and pcall is crap (looses stack trace). + local success,result = pcall(pgfmathparseinternal, normalizedExpr) + + -- remove 'tmpVar1', ... from the function table: + for i=1,numArgs do + local tmpVar = tmpVars[i] + pgfStringToFunctionMap[tmpVar] = restores[i] + end + + if success==false then error(result) end + return result + end + table.insert(stackOfLocalFunctions, name) + pgfStringToFunctionMap[name] = newFunction +end + +function pgfluamathparser.popLocalExpressionFunction() + local name = stackOfLocalFunctions[#stackOfLocalFunctions] + pgfStringToFunctionMap[name] = nil + -- this removes the last element: + table.remove(stackOfLocalFunctions) +end + + +-- A Utility function which simplifies the interaction with the TeX code +-- @param expression the input expression (string) +-- @param outputFormatChoice 0 if the result should be a fixed point number, 1 if it should be in FPU format +-- @param showErrorMessage (boolean) true if any error should be displayed, false if errors should simply result in an invocation of TeX's parser (the default) +-- +-- it defines \pgfmathresult and \ifpgfmathunitsdeclared +function pgfluamathparser.texCallParser(expression, outputFormatChoice, showErrorMessage) + local success, result + if showErrorMessage then + result = pgfmathparse(expression) + success = true + else + success, result = pcall(pgfmathparse, expression) + end + + if success and result then + local result_str + if outputFormatChoice == 0 then + -- luamath/output format=fixed + result_str = tostringfixed(result) + else + -- luamath/output format=fixed + result_str = tostringfpu(result) + end + tex.sprint("\\def\\pgfmathresult{" .. result_str .. "}") + if pgfluamathparser.units_declared then + tex.sprint("\\pgfmathunitsdeclaredtrue") + else + tex.sprint("\\pgfmathunitsdeclaredfalse") + end + else + tex.sprint("\\def\\pgfmathresult{}") + tex.sprint("\\pgfmathunitsdeclaredfalse") + end +end + +return pgfluamathparser |