-- -- 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 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 -- 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 self.hasUnboundedPointMeta = false -- will be set before the visualization phase starts. At least. self.plotIs3d = false -- do not use the global one. It may be outdated. self.stringToFunctionMap = pgfluamathfunctions.stringToFunctionMap 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 self.pointmetainputhandler ~= nil and self.pointmetainputhandler:isPointMetaBounded(pt.meta) 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 else -- FIXME : the TeX code also checks for 'bounded point meta' if there is no point meta input!? self.hasUnboundedPointMeta = true end end -- @see \pgfplotsplothandlersurveystart function Plothandler:surveystart() self.stringToFunctionMap["x"] = pseudoconstant_x self.stringToFunctionMap["y"] = pseudoconstant_y self.stringToFunctionMap["z"] = pseudoconstant_z self.stringToFunctionMap["rawx"] = pseudoconstant_rawx self.stringToFunctionMap["rawy"] = pseudoconstant_rawy self.stringToFunctionMap["rawz"] = pseudoconstant_rawz self.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 -- see \pgfplotsifpointmetaisbounded function PointMetaHandler:isPointMetaBounded(meta) if meta == nil then return false end if self.isSymbolic then if meta == "" then return false else return true end else -- check the number: return pgfplotsmath.isfinite(meta) end 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 = pgfluamathparser.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, true, 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 = pgfluamathparser.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 for i = 1,self:loopMax(),1 do if self.clipLimits[i] then 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 self:addSurveyedJump(plothandler, pt) end end end -- note that the TeX variant would increase the coord index here. -- We do it it surveypoint. end function Axis:addSurveyedJump(plothandler, pt) plothandler.plotHasJumps = true local serialized = self:addVisualizationDependencies(pt) plothandler:addSurveyedPoint(serialized) 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.hasUnboundedPointMeta then result = result .. "\\def\\pgfplotsaxisplothasunboundedpointmeta{1}" else result = result .. "\\def\\pgfplotsaxisplothasunboundedpointmeta{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