Module:Sandbox/Innesw/Charts SVG explained

-- Module Charts SVG

local Args, Parms =, local SeriesData, OriginalData =, local SType, YAxis2, Labels, Color, LineShow, LineWidth, LineDash, Marker, MarkerFill, MarkerSize, FillPattern, FillPatternColor =,,,,,,,,,,, local SeriesText, GroupText, ChartText =,, local SeriesCount, BarSeriesCount, SeriesMaxLen, GroupsCount, ChartTextCount = 0, 0, 0, 0, 0local AutoDataPointsLimit, DataPointsCount = 100, 0local DoPie, DoHorizontal, DoGroupsTopDown, DoArea, DoStack, DoStack100, DoYAxis2, DoChartAdjust = false, false, false, false, false, false, false, falselocal Msgs =

local FontSiz, Siz, Pos, Mult, Adjusts =,,,, local ImageWidth, ImageHeightlocal GroupWidth, UnitWidth, BarWidth, BarSpace

local BaseUnit, BaseFontSize, BaseLineWidth = 3, 3, 1

local r, tr, t =

-- To translate parameters, translate the values in the KeyWords table. Do not translate the keys.local KeyWords =

-- To translate messages, translate the values in the MessageTexts table. Do not translate the keys.local MessageTexts =

local DefColor =

-- grayscale equivalents of the above colourslocal GrayColor =

local DashPattern =

--

function barChart(frame)

Args = frame.args

getAllParms

DoStack = (Parms["Stack"] ~= nil) DoStack100 = (Parms["Stack100"] ~= nil) DoYAxis2 = (Parms["Y2Max"] ~= nil) DoChartAdjust = (Parms["ChartAdjust"] ~= nil)

DoHorizontal = (Parms["HorizontalBarGraph"] ~= nil) DoGroupsTopDown = (Parms["HorizontalBarGraph"] ~= nil) and (Parms["GroupsTopDown"] ~= nil)

-- fill internal tables

for i = 1, SeriesCount do SeriesData[i] = transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2) DataPointsCount = DataPointsCount + #SeriesData[i] SType[i] = KeyWords["bar"] end copyTable(SeriesData, OriginalData) -- preserve original data BarSeriesCount = SeriesCount

build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)

build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)

build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)

build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil) build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)

build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)

build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)

build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)

if DoStack or DoStack100 then calcStack DoYAxis2 = false end

if not checkParms then -- messages, instead of SVG output return table.concat(r, "\n") end

if not outputDebugInfo then -- stop after debug output return table.concat(r, "\n") end

-- build the SVG

prelimsCommon prelimsAxes

commonTop

codeAxisGrids

stylesAreas defsAreas elementsGraphs

codeAxes

commonBottom

return table.concat(r, "\n")end

function lineChart(frame)

Args = frame.args

getAllParms

DoArea = (Parms["Area"] ~= nil) DoStack = (Parms["Stack"] ~= nil) DoStack100 = (Parms["Stack100"] ~= nil) DoYAxis2 = (Parms["Y2Max"] ~= nil) DoChartAdjust = (Parms["ChartAdjust"] ~= nil)

-- fill internal tables

for i = 1, SeriesCount do SeriesData[i] = transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2) DataPointsCount = DataPointsCount + #SeriesData[i] SType[i] = KeyWords["line"] end copyTable(SeriesData, OriginalData) -- preserve original data BarSeriesCount = 0

build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)

build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)

build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)

build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes' build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil) build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)

build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil) build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)

build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil) build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)

build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)

build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)

build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)

if DoStack or DoStack100 then calcStack DoArea = true DoYAxis2 = false end

if not checkParms then -- messages, instead of SVG output return table.concat(r, "\n") end

if not outputDebugInfo then -- stop after debug output return table.concat(r, "\n") end

-- build the SVG

prelimsCommon prelimsAxes

commonTop

codeAxisGrids

if DoArea then stylesAreas defsAreas else stylesLines defsMarkers end elementsGraphs

codeAxes

commonBottom

return table.concat(r, "\n")end

function scatterChart(frame)

Args = frame.args

getAllParms

DoYAxis2 = (Parms["Y2Max"] ~= nil) DoChartAdjust = (Parms["ChartAdjust"] ~= nil)

-- fill internal tables

for i = 1, SeriesCount do SeriesData[i] = transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2) DataPointsCount = DataPointsCount + #SeriesData[i] SType[i] = KeyWords["line"] end copyTable(SeriesData, OriginalData) -- preserve original data BarSeriesCount = 0

build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)

build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)

build("Series", "Color", SeriesCount, Color, DefColor, nil, nil)

build("Series", "Line", SeriesCount, LineShow, KeyWords["none"], nil, nil) -- the default line visibility is 'none' build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil) build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)

build("Series", "Marker", SeriesCount, Marker, nil, true, nil) -- the default marker type is the series number build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil) build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)

build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)

build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)

if not checkParms then -- messages, instead of SVG output return table.concat(r, "\n") end

if not outputDebugInfo then -- stop after debug output return table.concat(r, "\n") end

-- build the SVG

prelimsCommon prelimsAxes

commonTop

codeAxisGrids

stylesLines defsMarkers elementsGraphs

codeAxes

commonBottom

return table.concat(r, "\n")end

function mixedChart(frame)

Args = frame.args

getAllParms

DoYAxis2 = (Parms["Y2Max"] ~= nil) DoChartAdjust = (Parms["ChartAdjust"] ~= nil)

-- fill internal tables

build("Series", "Type", SeriesCount, SType, "line", nil, nil) for i = 1, SeriesCount do if SType[i]

KeyWords["bar"] then BarSeriesCount = BarSeriesCount + 1 end end

for i = 1, SeriesCount do SeriesData[i] = transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2) DataPointsCount = DataPointsCount + #SeriesData[i] end copyTable(SeriesData, OriginalData) -- preserve original data

build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)

build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)

build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)

build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes' build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil) build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)

build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil) build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)

build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil) build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)

build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)

build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)

build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)

if not checkParms then -- messages, instead of SVG output return table.concat(r, "\n") end

if not outputDebugInfo then -- stop after debug output return table.concat(r, "\n") end

-- build the SVG

prelimsCommon prelimsAxes

commonTop

codeAxisGrids

stylesAreas defsAreas

stylesLines defsMarkers

elementsGraphs

codeAxes

commonBottom

return table.concat(r, "\n")end

function pieChart(frame)

Args = frame.args

getAllParms GroupsCount = 0 -- ensures any GroupNText parameters are ignored

-- fill internal tables

-- only transfer series 1 if SeriesCount > 0 then transfer(Parms["Series" .. 1 .. "Values"], SeriesData, 2) DataPointsCount = DataPointsCount + #SeriesData end copyTable(SeriesData, OriginalData) -- preserve original data

-- get segment parms for each element in the series -- we do this here because transfer (above) is where SeriesMaxLen is calculated for i = 1, SeriesMaxLen do Parms["Segment" .. i .. "Color"] = checkColor(getParm("Segment", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))

Parms["Segment" .. i .. "Pattern"] = getParm("Segment", i, "Pattern", "n") if Parms["Segment" .. i .. "Pattern"] ~= nil then Parms["Segment" .. i .. "PatternColor"] = checkColor(getParm("Segment", i, "PatternColor", "s", iif(Parms["GrayScale"] ~= nil, "white", "black"))) end end

build("Segment", "Color", SeriesMaxLen, Color, nil, nil, iif(Parms["GrayScale"] ~= nil, GrayColor, DefColor)) build("Segment", "Pattern", SeriesMaxLen, FillPattern, KeyWords["none"], nil, nil) build("Segment", "PatternColor", SeriesMaxLen, FillPatternColor, nil, nil, nil)

build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)

-- transfer the first value in each SeriesData pair to table SeriesText, for the legend for k, v in ipairs(SeriesData) do SeriesText[k] = v[1] end

SeriesCount = SeriesMaxLen

DoPie = true

if not checkParms then -- messages, instead of SVG output return table.concat(r, "\n") end

if not outputDebugInfo then -- stop after debug output return table.concat(r, "\n") end

-- build the SVG

prelimsCommon prelimsPie

commonTop

stylesAreas defsAreas elementsPie

commonBottom

return table.concat(r, "\n")end

function getAllParms -- get values for all parameters

-- Note that not all parameters have a default value, ie: it is valid for some parameters to be nil. These are generally switches for some behaviour.

-- SVG file metadata

Parms["FileTitle"] = getParm("FileTitle", nil, nil, "s", "SVG Chart") Parms["FileDesc"] = getParm("FileDesc", nil, nil, "s", "SVG chart generated by Charts SVG")

-- general image

Parms["ImagePadding"] = getParm("ImagePadding", nil, nil, "n") -- switch for replacing default value with user setting for the size of the padding for all 4 spaces Parms["ImagePaddingTop"] = getParm("ImagePaddingTop", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the top padding Parms["ImagePaddingBottom"] = getParm("ImagePaddingBottom", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the bottom padding Parms["ImagePaddingLeft"] = getParm("ImagePaddingLeft", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the left padding Parms["ImagePaddingRight"] = getParm("ImagePaddingRight", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the right padding

Parms["ImageBorder"] = checkColor(getParm("ImageBorder", nil, nil, "s", KeyWords["none"]))

Parms["ImageBackgroundColor"] = checkColor(getParm("ImageBackgroundColor", nil, nil, "s", "white"))

Parms["ImageBackgroundSVG"] = getParm("ImageBackgroundSVG", nil, nil, "s") -- switch for background SVG for the image

Parms["ImageForegroundSVG"] = getParm("ImageForegroundSVG", nil, nil, "s") -- switch for foreground SVG for the image

-- display title

Parms["Title"] = getParm("Title", nil, nil, "s") -- switch to show title text Parms["TitleX"] = getParm("TitleX", nil, nil, "n") -- switch to move title from default position Parms["TitleY"] = getParm("TitleY", nil, nil, "n", 0)

-- footnote

Parms["Footnote"] = getParm("Footnote", nil, nil, "s") -- switch for footnote text Parms["FootnoteX"] = getParm("FootnoteX", nil, nil, "n") -- switch to move footnote from default position Parms["FootnoteY"] = getParm("FootnoteY", nil, nil, "n", 0)

-- general chart

Parms["Area"] = getParm("Area", nil, nil, "s") -- switch for area graphs Parms["Stack"] = getParm("Stack", nil, nil, "s") -- switch for stacked graphs Parms["Stack100"] = getParm("Stack100", nil, nil, "s") -- switch for stacked-to-100% graphs

Parms["HorizontalBarGraph"] = getParm("HorizontalBarGraph", nil, nil, "s") -- switch for horizontal bar graphs Parms["GroupsTopDown"] = getParm("GroupsTopDown", nil, nil, "s") -- switch for showing groups (and series) in top-down order on horizontal bar graphs

Parms["ChartWidth"] = getParm("ChartWidth", nil, nil, "n", 500) Parms["ChartHeight"] = getParm("ChartHeight", nil, nil, "n", 350)

if Parms["ChartWidth"] < 200 then table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartWidth", Parms["ChartWidth"], 200)) end if Parms["ChartHeight"] < 200 then table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartHeight", Parms["ChartHeight"], 200)) end

Parms["ChartAdjust"] = getParm("ChartAdjust", nil, nil, "s") -- switch for automatic chart size adjustment

Parms["GreyScale"] = getParm("GreyScale", nil, nil, "s") Parms["GrayScale"] = getParm("GrayScale", nil, nil, "s", Parms["GreyScale"]) -- switch for grayscale instead of colours

Parms["LineWidth"] = getParm("LineWidth", nil, nil, "n", 100)

Parms["ChartBackgroundColor"] = checkColor(getParm("ChartBackgroundColor", nil, nil, "s")) -- switch for a background color for the chart

-- XAxis

Parms["XMin"] = getParm("XMin", nil, nil, "n", 0) Parms["XMax"] = getParm("XMax", nil, nil, "n", 100)

Parms["XAxisTitle"] = getParm("XAxisTitle", nil, nil, "s") -- switch to show X axis title

Parms["XAxisValueStep"] = getParm("XAxisValueStep", nil, nil, "n", 10)

Parms["XAxisValueMultiplier"] = getParm("XAxisValueMultiplier", nil, nil, "n", 1) Parms["XAxisValueRound"] = getParm("XAxisValueRound", nil, nil, "n", decPlaces(Parms["XAxisValueStep"])) Parms["XAxisValueAbsolute"] = getParm("XAxisValueAbsolute", nil, nil, "s") -- switch for absolute values display

Parms["XAxisValuePrefix"] = getParm("XAxisValuePrefix", nil, nil, "s", "", "_", " ") Parms["XAxisValueSuffix"] = getParm("XAxisValueSuffix", nil, nil, "s", "", "_", " ")

Parms["XAxisValueFormat"] = getParm("XAxisValueFormat", nil, nil, "s") -- switch to force values formatting on or off Parms["XAxisValueRotate"] = getParm("XAxisValueRotate", nil, nil, "n") -- switch to rotate x-axis values

Parms["XAxisValueSpace"] = getParm("XAxisValueSpace", nil, nil, "n") -- switch to set x-axis values space

Parms["XAxisMark2Step"] = getParm("XAxisMark2Step", nil, nil, "n") -- switch for showing x-axis secondary marks

Parms["XAxisArrows"] = getParm("XAxisArrows", nil, nil, "s") -- switch for showing x-axis arrows

-- YAxis

if Parms["Stack100"] ~= nil then Parms["YMin"] = 0 Parms["YMax"] = 100 else Parms["YMin"] = getParm("YMin", nil, nil, "n", 0) Parms["YMax"] = getParm("YMax", nil, nil, "n", 100) end

Parms["YAxisTitle"] = getParm("YAxisTitle", nil, nil, "s") -- switch to show Y axis title

Parms["YAxisValueStep"] = getParm("YAxisValueStep", nil, nil, "n", 10)

Parms["YAxisValueMultiplier"] = getParm("YAxisValueMultiplier", nil, nil, "n", 1) Parms["YAxisValueRound"] = getParm("YAxisValueRound", nil, nil, "n", decPlaces(Parms["YAxisValueStep"])) Parms["YAxisValueAbsolute"] = getParm("YAxisValueAbsolute", nil, nil, "s") -- switch for absolute values display

Parms["YAxisValuePrefix"] = getParm("YAxisValuePrefix", nil, nil, "s", "", "_", " ") if Parms["Stack100"] ~= nil then Parms["YAxisValueSuffix"] = getParm("YAxisValueSuffix", nil, nil, "s", "%", "_", " ") else Parms["YAxisValueSuffix"] = getParm("YAxisValueSuffix", nil, nil, "s", "", "_", " ") end

Parms["YAxisValueFormat"] = getParm("YAxisValueFormat", nil, nil, "s") -- switch to force values formatting on or off Parms["YAxisValueRotate"] = getParm("YAxisValueRotate", nil, nil, "n") -- switch to rotate y-axis values

Parms["YAxisValueSpace"] = getParm("YAxisValueSpace", nil, nil, "n") -- switch to set y-axis values space

Parms["YAxisMark2Step"] = getParm("YAxisMark2Step", nil, nil, "n") -- switch for showing y-axis secondary marks

Parms["YAxisColor"] = checkColor(getParm("YAxisColor", nil, nil, "s", "black"))

Parms["YAxisArrows"] = getParm("YAxisArrows", nil, nil, "s") -- switch for showing y-axis arrows

-- YAxis2

Parms["YAxis2Title"] = getParm("YAxis2Title", nil, nil, "s") -- switch to show y-axis-2 title

Parms["Y2Min"] = getParm("Y2Min", nil, nil, "n", 0) Parms["Y2Max"] = getParm("Y2Max", nil, nil, "n") -- switch to show y-axis-2

Parms["YAxis2ValueStep"] = getParm("YAxis2ValueStep", nil, nil, "n", 10)

Parms["YAxis2ValueMultiplier"] = getParm("YAxis2ValueMultiplier", nil, nil, "n", 1) Parms["YAxis2ValueRound"] = getParm("YAxis2ValueRound", nil, nil, "n", decPlaces(Parms["YAxis2ValueStep"])) Parms["YAxis2ValueAbsolute"] = getParm("YAxis2ValueAbsolute", nil, nil, "s") -- switch for absolute values display

Parms["YAxis2ValuePrefix"] = getParm("YAxis2ValuePrefix", nil, nil, "s", "", "_", " ") Parms["YAxis2ValueSuffix"] = getParm("YAxis2ValueSuffix", nil, nil, "s", "", "_", " ")

Parms["YAxis2ValueFormat"] = getParm("YAxis2ValueFormat", nil, nil, "s") -- switch to force values formatting on or off Parms["YAxis2ValueRotate"] = getParm("YAxis2ValueRotate", nil, nil, "n") -- switch to rotate y-axis-2 values

Parms["YAxis2ValueSpace"] = getParm("YAxis2ValueSpace", nil, nil, "n") -- switch to set y-axis-2 values space

Parms["YAxis2Mark2Step"] = getParm("YAxis2Mark2Step", nil, nil, "n") -- switch for showing y-axis-2 secondary marks

Parms["YAxis2Color"] = checkColor(getParm("YAxis2Color", nil, nil, "s", "black"))

-- grid lines

Parms["XGrid"] = getParm("XGrid", nil, nil, "s", Parms["XAxisValueStep"]) if Parms["XGrid"] ~= KeyWords["none"] then Parms["XGrid"] = tonumber(Parms["XGrid"]) end

Parms["YGrid"] = getParm("YGrid", nil, nil, "s", Parms["YAxisValueStep"]) if Parms["YGrid"] ~= KeyWords["none"] then Parms["YGrid"] = tonumber(Parms["YGrid"]) end

-- legend

Parms["LegendType"] = getParm("LegendType", nil, nil, "s", KeyWords["vertical"]) i = 1 Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N while Parms["Series" .. i .. "Text"] ~= nil do i = i + 1 Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N end

Parms["LegendX"] = getParm("LegendX", nil, nil, "n") -- switch for moving the legend from the default position Parms["LegendY"] = getParm("LegendY", nil, nil, "n", 0) Parms["LegendTextWidth"] = getParm("LegendTextWidth", nil, nil, "n", 100) Parms["LegendBorder"] = checkColor(getParm("LegendBorder", nil, nil, "s", "black")) Parms["LegendSVG"] = getParm("LegendSVG", nil, nil, "s") -- switch for replacing all legend code

-- font sizes

Parms["FontSize"] = getParm("FontSize", nil, nil, "n", 100) Parms["TitleFontSize"] = getParm("TitleFontSize", nil, nil, "n", Parms["FontSize"]) Parms["FootnoteFontSize"] = getParm("FootnoteFontSize", nil, nil, "n", Parms["FontSize"]) Parms["LegendFontSize"] = getParm("LegendFontSize", nil, nil, "n", Parms["FontSize"]) Parms["XAxisTitleFontSize"] = getParm("XAxisTitleFontSize", nil, nil, "n", Parms["FontSize"]) Parms["YAxisTitleFontSize"] = getParm("YAxisTitleFontSize", nil, nil, "n", Parms["FontSize"]) Parms["YAxis2TitleFontSize"] = getParm("YAxis2TitleFontSize", nil, nil, "n", Parms["FontSize"]) Parms["XAxisValuesFontSize"] = getParm("XAxisValuesFontSize", nil, nil, "n", Parms["FontSize"]) Parms["YAxisValuesFontSize"] = getParm("YAxisValuesFontSize", nil, nil, "n", Parms["FontSize"]) Parms["YAxis2ValuesFontSize"] = getParm("YAxis2ValuesFontSize", nil, nil, "n", Parms["FontSize"]) Parms["ChartTextFontSize"] = getParm("ChartTextFontSize", nil, nil, "n", Parms["FontSize"]) Parms["LabelsFontSize"] = getParm("LabelsFontSize", nil, nil, "n", Parms["FontSize"])

-- bar width & spacing

Parms["BarWidth"] = getParm("BarWidth", nil, nil, "n", 20) Parms["BarSpace"] = getParm("BarSpace", nil, nil, "n", 0) -- % of bar width

-- general graph line width Parms["GraphLineWidth"] = getParm("GraphLineWidth", nil, nil, "n", 100)

-- pie chart

Parms["PieRadius"] = getParm("PieRadius", nil, nil, "n", 200) Parms["Explode"] = getParm("Explode", nil, nil, "s") -- switch for exploding some or all pie segments if Parms["Explode"] ~= nil then if tonumber(Parms["Explode"]) ~= nil then Parms["Explode"] = tonumber(Parms["Explode"]) -- default explode radius is 20% Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 20) else -- any non-numeric value = all, default explode radius is 10% Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 10) end end -- Parms[] SegmentNColor, SegmentNPattern and SegmentNPatternColor are done in pieChart

Parms["SegmentText"] = getParm("SegmentText", nil, nil, "s") -- switch for showing series text and/or values on pie segments Parms["SegmentTextWidth"] = getParm("SegmentTextWidth", nil, nil, "n", 100) Parms["SegmentTextRadius"] = getParm("SegmentTextRadius", nil, nil, "n", 105)

Parms["DoughnutHole"] = getParm("DoughnutHole", nil, nil, "s") -- switch for a doughnut chart if Parms["DoughnutHole"] ~= nil then if tonumber(Parms["DoughnutHole"]) ~= nil then -- if DoughnutHole is a number, ensure it is of numeric type Parms["DoughnutHole"] = tonumber(Parms["DoughnutHole"]) else -- any other value, hole size is 50% Parms["DoughnutHole"] = 50 end end Parms["PieStartAngle"] = getParm("PieStartAngle", nil, nil, "n", 0) -- move the start angle

Parms["PieSweepDir"] = getParm("PieSweepDir", nil, nil, "s", "AntiClockwise") -- any value not "AntiClockwise" will switch the sweep direction

-- bar and pie-segment borders

Parms["BorderColor"] = checkColor(getParm("BorderColor", nil, nil, "s")) -- switch for showing borders on bars Parms["BorderWidth"] = getParm("BorderWidth", nil, nil, "n", 100) -- % of standard line width

-- series

i = 1 Parms["Series" .. i .. "Values"] = getParm("Series", i, "Values", "s") -- switch for showing a series while Parms["Series" .. i .. "Values"] ~= nil do Parms["Series" .. i .. "Type"] = getParm("Series", i, "Type", "s") Parms["Series" .. i .. "YAxis2"] = getParm("Series", i, "YAxis2") -- switch for series to use YAxis2 as scale for Y values Parms["Series" .. i .. "Labels"] = getParm("Series", i, "Labels") -- switch for series to show data labels Parms["Series" .. i .. "Color"] = checkColor(getParm("Series", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))

Parms["Series" .. i .. "Line"] = getParm("Series", i, "Line", "s") Parms["Series" .. i .. "Width"] = getParm("Series", i, "Width", "n", Parms["GraphLineWidth"]) Parms["Series" .. i .. "Dash"] = getParm("Series", i, "Dash", "s", KeyWords["none"]) t = tonumber(Parms["Series" .. i .. "Dash"]) if t ~= nil and t <= #DashPattern then -- if SeriesNDash is a number in the table of default dashes, use the dash pattern for that number Parms["Series" .. i .. "Dash"] = DashPattern[t] end

Parms["Series" .. i .. "Marker"] = getParm("Series", i, "Marker", "s") -- switch for markers on the graph if tonumber(Parms["Series" .. i .. "Marker"]) ~= nil then -- if SeriesNMarker is a number, ensure it is of numeric type Parms["Series" .. i .. "Marker"] = tonumber(Parms["Series" .. i .. "Marker"]) end Parms["Series" .. i .. "MarkerSize"] = getParm("Series", i, "MarkerSize", "n", 100) Parms["Series" .. i .. "MarkerFill"] = checkColor(getParm("Series", i, "MarkerFill", "s"))

Parms["Series" .. i .. "Pattern"] = getParm("Series", i, "Pattern", "n") if Parms["Series" .. i .. "Pattern"] ~= nil then Parms["Series" .. i .. "PatternColor"] = checkColor(getParm("Series", i, "PatternColor", "s", iif(Parms["GrayScale"] ~= nil, "white", "black"))) end SeriesCount = SeriesCount + 1

i = i + 1 Parms["Series" .. i .. "Values"] = getParm("Series", i, "Values", "s") -- switch for showing a series end

-- groups

i = 1 Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s") -- switch for grouping values while Parms["Group" .. i .. "Text"] ~= nil do GroupsCount = GroupsCount + 1

i = i + 1 Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s") end if GroupsCount > 0 then Parms["XMax"] = GroupsCount end -- chart texts

i = 1 Parms["ChartText" .. i] = getParm("ChartText", i, nil, "s") -- switch for showing chart text N while Parms["ChartText" .. i] ~= nil do Parms["ChartText" .. i .. "X"] = getParm("ChartText", i, "X", "n", 0) Parms["ChartText" .. i .. "Y"] = getParm("ChartText", i, "Y", "n", 0) ChartTextCount = ChartTextCount + 1

i = i + 1 Parms["ChartText" .. i] = getParm("ChartText", i, nil, "s") -- switch for showing chart text N end

Parms["IncludeOriginalData"] = getParm("IncludeOriginalData", nil, nil, "s", KeyWords["auto"])

-- debug

Parms["Debug"] = getParm("Debug", nil, nil, "s") -- switch to run debug code

end

function getParm(s1, num, s2, typ, def, subst, with) -- returns the value of a named parameter (of a specified type) or a default -- the name may be built from multiple parts (s1, num, s2)

local s = KeyWords[s1] if num ~= nil then s = s .. num end if s2 ~= nil then s = s .. KeyWords[s2] end

local result if Args[s] ~= nil then result = Args[s] if typ

"n" then result = tonumber(result) if result

nil then table.insert(Msgs, string.format(MessageTexts.NotNumeric, s, Args[s])) end end else result = def end -- optional substitution of characters within the parameter value if result ~= nil and subst and with then result = mw.ustring.gsub(result, subst, with) end return resultend

function checkColor(p) -- checks if p is numeric and in the colors table(s) -- if so returns the color from the table -- otherwise returns the original parameter value

if p

nil then return nil end local t = tonumber(p) if t ~= nil and t <= #DefColor then return iif(Parms["GrayScale"] ~= nil, GrayColor[t], DefColor[t]) else return p endend

function transfer(Parm, Tab, SetSize) -- transfer values from a space-delimited parameter to a table local i = 0 Parm = mw.text.trim(Parm) if SetSize > 1 then local x = SetSize + 1 -- to force new table for first time for s in mw.text.gsplit(Parm, "%s+") do if x > SetSize then i = i + 1 Tab[i] = x = 1 end Tab[i][x] = s x = x + 1 end else for s in mw.text.gsplit(Parm, "%s+") do i = i + 1 Tab[i] = s end end SeriesMaxLen = math.max(SeriesMaxLen, i)end

function build(ParmStart, ParmEnd, Size, Tab, DefaultValue, UseI, DefaultTable) -- builds table of specified size from a series of StartNEnd parameters -- any nil parameters in the sequence may be filled with a default value, or i (with a prefix if set), or a value from a default table for i = 1, Size do if Parms[ParmStart .. i .. ParmEnd] ~= nil then Tab[i] = Parms[ParmStart .. i .. ParmEnd] elseif DefaultValue ~= nil then Tab[i] = DefaultValue elseif type(UseI)

"boolean" then Tab[i] = i elseif type(UseI)

"string" then Tab[i] = UseI .. i elseif DefaultTable ~= nil then Tab[i] = DefaultTable[i] else -- nothing end endend

function calcStack -- calculate stacked totals for series

-- the current accumlated total overwrites the SeriesData Y value

local PosTotal, NegTotal, NegFlag =,, false

for j = 1, #GroupText do PosTotal[j] = 0 NegTotal[j] = 0 for i = 1, #SeriesData do if SeriesData[i][j] ~= nil and tonumber(SeriesData[i][j][1])

j then if tonumber(SeriesData[i][j][2]) < 0 then NegTotal[j] = NegTotal[j] - SeriesData[i][j][2] SeriesData[i][j][2] = -NegTotal[j] NegFlag = true else PosTotal[j] = PosTotal[j] + SeriesData[i][j][2] SeriesData[i][j][2] = PosTotal[j] end end end end if DoStack100 then for j = 1, #GroupText do for i = 1, #SeriesData do if SeriesData[i][j] ~= nil then SeriesData[i][j][2] = SeriesData[i][j][2] / iif(tonumber(SeriesData[i][j][2]) < 0, NegTotal[j], PosTotal[j]) * 100 end end end if NegFlag then Parms["YMin"] = -100 end endend

function checkParms -- check for parameter issues

-- unknown parameters local regexps =

-- add patterns to regexps table.insert(regexps, "Series[%d][%d]*Values") table.insert(regexps, "Series[%d][%d]*Type") table.insert(regexps, "Series[%d][%d]*YAxis2") table.insert(regexps, "Series[%d][%d]*Labels") table.insert(regexps, "Series[%d][%d]*Color") table.insert(regexps, "Series[%d][%d]*Line") table.insert(regexps, "Series[%d][%d]*Width") table.insert(regexps, "Series[%d][%d]*Dash") table.insert(regexps, "Series[%d][%d]*Marker") table.insert(regexps, "Series[%d][%d]*MarkerSize") table.insert(regexps, "Series[%d][%d]*MarkerFill") table.insert(regexps, "Series[%d][%d]*Pattern") table.insert(regexps, "Series[%d][%d]*PatternColor") table.insert(regexps, "Series[%d][%d]*Text")

table.insert(regexps, "Group[%d][%d]*Text")

table.insert(regexps, "Segment[%d][%d]*Color") table.insert(regexps, "Segment[%d][%d]*Pattern") table.insert(regexps, "Segment[%d][%d]*PatternColor")

table.insert(regexps, "ChartText[%d][%d]*") table.insert(regexps, "ChartText[%d][%d]*X") table.insert(regexps, "ChartText[%d][%d]*Y")

local unknowns = unknownParameters(KeyWords, regexps) if #unknowns > 0 then -- ensure parms-not-found are at the top of the list of messages local tmp = copyTable(Msgs, tmp) Msgs = table.insert(Msgs, MessageTexts.ParmsNotFound) for k, v in spairs(unknowns) do table.insert(Msgs, " " .. unknowns[k]) end for i = 1, #tmp do table.insert(Msgs, tmp[i]) end end

if #GroupText > 0 then for k1, v1 in ipairs(SeriesData) do for k2, v2 in ipairs(v1) do local t = tonumber(v2[1]) if t ~= math.floor(t) or t < 1 or t > #GroupText then table.insert(Msgs, string.format(MessageTexts.NotInGroup, k1, k2, v2[1])) end end end end if not DoYAxis2 then for k, v in pairs(YAxis2) do table.insert(Msgs, string.format(MessageTexts.NoYAxis2, k)) end end

for k, v in pairs(Marker) do local t = tonumber(v) if v

"none" then -- OK elseif t

nil then table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v)) elseif (t >= 1 and t <= 7) then -- OK else table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v)) end end

for k, v in pairs(LineDash) do local t = tonumber(v) if v

"none" then -- OK elseif t

nil then -- non-numerics are OK for LineDash elseif (t >= 1 and t <= #DashPattern) then -- OK else table.insert(Msgs, string.format(MessageTexts.NotInDashPatterns, k, v)) end end

for k, v in pairs(FillPattern) do local t = tonumber(v) if v

"none" then -- OK elseif t

nil then table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v)) elseif (t >= 1 and t <= 8) or (t >= 11 and t <= 18) or (t >= 21 and t <= 24) or (t >= 31 and t <= 34) or (t >= 41 and t <= 49) or (t >= 51 and t <= 56) or (t >= 61 and t <= 64) then -- OK else table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v)) end end

if #Msgs > 0 then -- add messages to output table.insert(r, " " .. MessageTexts.MsgHeading) for i = 1, math.min(#Msgs, 10) do table.insert(r, " " .. Msgs[i]) end return false end

return trueend

function prelimsCommon

Siz.ImagePadding = Siz.Legend = Siz.Text =

-- the base unit for font sizes can be changed by the user BaseFontSize = BaseFontSize * (Parms["FontSize"] / 100)

FontSiz.Title = 7 * BaseFontSize * Parms["TitleFontSize"] / 100

FontSiz.LegendText = 5 * BaseFontSize * Parms["LegendFontSize"] / 100

FontSiz.Labels = 4 * BaseFontSize * Parms["LabelsFontSize"] / 100

FontSiz.ChartText = 4 * BaseFontSize * Parms["ChartTextFontSize"] / 100

FontSiz.Footnote = 3 * BaseFontSize * Parms["FootnoteFontSize"] / 100

-- the base line width can be changed by the user BaseLineWidth = BaseLineWidth * Parms["LineWidth"] / 100

-- define sizes of other image components from base spacing units

Siz.ImagePadding.Top = 2 * BaseUnit if Parms["ImagePaddingTop"] ~= nil then Siz.ImagePadding.Top = Parms["ImagePaddingTop"] end Siz.ImagePadding.Bottom = 2 * BaseUnit if Parms["ImagePaddingBottom"] ~= nil then Siz.ImagePadding.Bottom = Parms["ImagePaddingBottom"] end Siz.ImagePadding.Left = 2 * BaseUnit if Parms["ImagePaddingLeft"] ~= nil then Siz.ImagePadding.Left = Parms["ImagePaddingLeft"] end Siz.ImagePadding.Right = 2 * BaseUnit if Parms["ImagePaddingRight"] ~= nil then Siz.ImagePadding.Right = Parms["ImagePaddingRight"] end

Siz.ChartMargin = 2 * BaseUnit

Siz.Text.Interline = 2 * BaseFontSize

Siz.Text.Labels = FontSiz.Labels

Siz.Text.Title = iif(Parms["Title"] ~= nil and Parms["TitleX"]

nil, FontSiz.Title + Siz.Text.Interline, 0)

Siz.Footnote = iif(Parms["Footnote"] ~= nil and Parms["FootnoteX"]

nil, FontSiz.Footnote, 0)

Siz.Legend.Text = FontSiz.LegendText Siz.Legend.TextWidth = 5 * FontSiz.LegendText * Parms["LegendTextWidth"] / 100 Siz.Legend.Offset = 3 * BaseUnit

Siz.Text.Chart = FontSiz.ChartTextend

function prelimsAxes

Pos.XAxis = Pos.YAxis = Pos.YAxis2 = Pos.Origin =

Siz.Space = Siz.XAxis = Siz.YAxis = Siz.YAxis2 =

Siz.ChartWidth = Parms["ChartWidth"] Siz.ChartHeight = Parms["ChartHeight"] if DoChartAdjust then allChartAdjust end if DoHorizontal then Siz.XAxis.Length = Siz.ChartHeight Siz.YAxis.Length = Siz.ChartWidth Siz.YAxis2.Length = Siz.ChartWidth else Siz.XAxis.Length = Siz.ChartWidth Siz.YAxis.Length = Siz.ChartHeight Siz.YAxis2.Length = Siz.ChartHeight end

prelimsLegend

BarWidth = Parms["BarWidth"] BarSpace = Parms["BarSpace"] / 100

-- The Mult values are the number of pixels per 1 unit on each axis

if #GroupText > 0 then if BarSeriesCount > 0 then GroupWidth = Siz.XAxis.Length / #GroupText if DoStack or DoStack100 then UnitWidth = GroupWidth / 2 else UnitWidth = GroupWidth / (BarSeriesCount + 1) end BarWidth = UnitWidth / (1 + BarSpace) BarSpace = UnitWidth - BarWidth Mult.x = Siz.XAxis.Length / #GroupText else GroupWidth = Siz.XAxis.Length / #GroupText Mult.x = GroupWidth end else Mult.x = Siz.XAxis.Length / (math.abs(Parms["XMax"] - Parms["XMin"])) end

Mult.y = Siz.YAxis.Length / (math.abs(Parms["YMax"] - Parms["YMin"]))

if DoYAxis2 then Mult.y2 = Siz.YAxis2.Length / (math.abs(Parms["Y2Max"] - Parms["Y2Min"])) else Mult.y2 = 1 end --

FontSiz.XAxisTitle = 5 * BaseFontSize * Parms["XAxisTitleFontSize"] / 100 FontSiz.XAxisValues = 4 * BaseFontSize * Parms["XAxisValuesFontSize"] / 100

FontSiz.YAxisTitle = 5 * BaseFontSize * Parms["YAxisTitleFontSize"] / 100 FontSiz.YAxisValues = 4 * BaseFontSize * Parms["YAxisValuesFontSize"] / 100

FontSiz.YAxis2Title = 5 * BaseFontSize * Parms["YAxis2TitleFontSize"] / 100 FontSiz.YAxis2Values = 4 * BaseFontSize * Parms["YAxis2ValuesFontSize"] / 100

Siz.AxisMark = 2 * BaseUnit Siz.AxisMark2 = 1 * BaseUnit

Siz.XAxis.Title = iif(Parms["XAxisTitle"] ~= nil, FontSiz.XAxisTitle + Siz.Text.Interline, 0) Siz.YAxis.Title = iif(Parms["YAxisTitle"] ~= nil, FontSiz.YAxisTitle + Siz.Text.Interline, 0)

if DoHorizontal then Siz.XAxis.Values = iif(Parms["XAxisValueSpace"] ~= nil, Parms["XAxisValueSpace"], 3 * FontSiz.XAxisValues) Siz.YAxis.Values = iif(Parms["YAxisValueSpace"] ~= nil, Parms["YAxisValueSpace"], FontSiz.YAxisValues + Siz.Text.Interline) else Siz.XAxis.Values = iif(Parms["XAxisValueSpace"] ~= nil, Parms["XAxisValueSpace"], FontSiz.XAxisValues + Siz.Text.Interline) Siz.YAxis.Values = iif(Parms["YAxisValueSpace"] ~= nil, Parms["YAxisValueSpace"], 3 * FontSiz.YAxisValues) end

Siz.YAxis2.Title = 0 Siz.YAxis2.Values = 0 if DoYAxis2 then if Parms["YAxis2Title"] ~= nil then Siz.YAxis2.Title = FontSiz.YAxis2Title + Siz.Text.Interline end if DoHorizontal then Siz.YAxis2.Values = iif(Parms["YAxis2ValueSpace"] ~= nil, Parms["YAxis2ValueSpace"], FontSiz.YAxis2Values + Siz.Text.Interline) else Siz.YAxis2.Values = iif(Parms["YAxis2ValueSpace"] ~= nil, Parms["YAxis2ValueSpace"], 3 * FontSiz.YAxis2Values) end end -- spaces around the chart (working out from the chart): -- AxisMarks -- ChartMargin -- AxisValues -- AxisTitle -- Title -- Legend -- Footnote -- ImagePadding -- Top Space Siz.Space.Top = Siz.ImagePadding.Top Pos.Title = Siz.ImagePadding.Top + FontSiz.Title Siz.Space.Top = Siz.Space.Top + Siz.Text.Title if DoHorizontal and DoYAxis2 then Pos.YAxis2.Title = Siz.Space.Top + FontSiz.YAxis2Title Siz.Space.Top = Siz.Space.Top + Siz.YAxis2.Title Siz.Space.Top = Siz.Space.Top + Siz.YAxis2.Values Siz.Space.Top = Siz.Space.Top + Siz.ChartMargin + Siz.AxisMark

Pos.YAxis2.Line = -Parms["XMax"] * Mult.x Pos.YAxis2.Values = Pos.YAxis2.Line - Siz.AxisMark - Siz.ChartMargin -- pos is bottom edge of values box else Siz.Space.Top = Siz.Space.Top + Siz.ChartMargin end

-- Bottom Space if DoHorizontal then if Parms["XMin"] < 0 and #GroupText

0 then -- Y axis line is within chart Pos.YAxis.Line = 0 Pos.YAxis.Values = Pos.YAxis.Line + Siz.AxisMark + Siz.ChartMargin + FontSiz.YAxisValues Siz.Space.Bottom = Siz.ChartMargin else -- Y axis line is at bottom of chart Pos.YAxis.Line = -Parms["XMin"] * Mult.x Siz.Space.Bottom = Siz.AxisMark Pos.YAxis.Values = Pos.YAxis.Line + Siz.Space.Bottom + Siz.ChartMargin + FontSiz.YAxisValues Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartMargin + Siz.YAxis.Values end if Parms["YAxisTitle"] ~= nil then Pos.YAxis.Title = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + FontSiz.YAxisTitle Siz.Space.Bottom = Siz.Space.Bottom + Siz.YAxis.Title end else if Parms["YMin"] < 0 then -- X axis line is within chart Pos.XAxis.Line = 0 Pos.XAxis.Values = Pos.XAxis.Line + Siz.AxisMark + Siz.ChartMargin + FontSiz.XAxisValues Siz.Space.Bottom = Siz.ChartMargin else -- X axis line is at bottom of chart Pos.XAxis.Line = -Parms["YMin"] * Mult.y Siz.Space.Bottom = iif(#GroupText > 0, 0, Siz.AxisMark) Pos.XAxis.Values = Pos.XAxis.Line + Siz.Space.Bottom + Siz.ChartMargin + FontSiz.XAxisValues Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartMargin + Siz.XAxis.Values end if Parms["XAxisTitle"] ~= nil then Pos.XAxis.Title = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + FontSiz.XAxisTitle Siz.Space.Bottom = Siz.Space.Bottom + Siz.XAxis.Title end end if Parms["LegendType"]

KeyWords["horizontal"] and Parms["LegendX"]

nil then Pos.Legend = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline end if Parms["Footnote"] ~= nil then Pos.Footnote = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Footnote Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote end Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom

-- Left Space Siz.Space.Left = Siz.ImagePadding.Left if DoHorizontal then if Parms["XAxisTitle"] ~= nil then Pos.XAxis.Title = Siz.Space.Left + FontSiz.XAxisTitle -- pos is right edge of title Siz.Space.Left = Siz.Space.Left + Siz.XAxis.Title end if Parms["YMin"] < 0 then -- X axis line is within chart Siz.Space.Left = Siz.Space.Left + Siz.ChartMargin Pos.XAxis.Line = 0 Pos.XAxis.Values = Pos.XAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box else -- X axis line is at left of chart Siz.Space.Left = Siz.Space.Left + Siz.XAxis.Values + Siz.ChartMargin + Siz.AxisMark Pos.XAxis.Line = Parms["YMin"] * Mult.y Pos.XAxis.Values = Pos.XAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box end else if Parms["YAxisTitle"] ~= nil then Pos.YAxis.Title = Siz.Space.Left + FontSiz.YAxisTitle -- pos is right edge of title Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Title end if Parms["XMin"] < 0 and #GroupText

0 then -- Y axis line is within chart Siz.Space.Left = Siz.Space.Left + Siz.ChartMargin Pos.YAxis.Line = 0 Pos.YAxis.Values = Pos.YAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box else -- Y axis line is at left of chart Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Values + Siz.ChartMargin + Siz.AxisMark Pos.YAxis.Line = Parms["XMin"] * Mult.x Pos.YAxis.Values = Pos.YAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box end end

-- Right Space Siz.Space.Right = 0 if DoHorizontal then Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin else if DoYAxis2 then Pos.YAxis2.Line = Parms["XMax"] * Mult.x Siz.Space.Right = Siz.Space.Right + Siz.AxisMark Pos.YAxis2.Values = Pos.YAxis2.Line + Siz.Space.Right + Siz.ChartMargin -- pos is left edge of values box Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin + Siz.YAxis2.Values if Parms["YAxis2Title"] ~= nil then Pos.YAxis2.Title = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + FontSiz.YAxis2Title Siz.Space.Right = Siz.Space.Right + Siz.YAxis2.Title end else Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin end end if Parms["LegendType"]

KeyWords["vertical"] and Parms["LegendX"]

nil then Pos.Legend = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + Siz.Legend.Offset Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width end Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right if DoHorizontal then Pos.XAxis.Zero = Siz.Space.Top + Siz.ChartHeight + (iif(#GroupText

0, Parms["XMin"], 0) * Mult.x) Pos.YAxis.Zero = Siz.Space.Left - (Parms["YMin"] * Mult.y) else Pos.XAxis.Zero = Siz.Space.Left - (iif(#GroupText

0, Parms["XMin"], 0) * Mult.x) Pos.YAxis.Zero = Siz.Space.Top + Siz.ChartHeight + (Parms["YMin"] * Mult.y) end

-- Chart Origin offsets if DoHorizontal then Pos.Origin.v = round(Siz.Space.Top + (Parms["XMax"] * Mult.x), 2) Pos.Origin.h = round(Siz.Space.Left + ((0 - Parms["YMin"]) * Mult.y), 2) else Pos.Origin.v = round(Siz.Space.Top + (Parms["YMax"] * Mult.y), 2) Pos.Origin.h = round(Siz.Space.Left + ((0 - Parms["XMin"]) * Mult.x), 2) end -- Image size ImageWidth = round(Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right, 0) ImageHeight = round(Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom, 0)end

function allChartAdjust

Adjusts.ChartWidth = Adjusts.ChartWidth.Old = Siz.ChartWidth Adjusts.ChartHeight = Adjusts.ChartHeight.Old = Siz.ChartHeight Adjusts.XMax = Adjusts.XMax.Old = Parms["XMax"] Adjusts.XMin = Adjusts.XMin.Old = Parms["XMin"] Adjusts.YMax = Adjusts.YMax.Old = Parms["YMax"] Adjusts.YMin = Adjusts.YMin.Old = Parms["YMin"] if DoHorizontal then Siz.ChartWidth = chartAdjust(Siz.ChartWidth, "YAxisValueStep", "YAxisMark2Step", "YMax", "YMin") if #GroupText

0 then Siz.ChartHeight = chartAdjust(Siz.ChartHeight, "XAxisValueStep", "XAxisMark2Step", "XMax", "XMin") end else if #GroupText

0 then Siz.ChartWidth = chartAdjust(Siz.ChartWidth, "XAxisValueStep", "XAxisMark2Step", "XMax", "XMin") end Siz.ChartHeight = chartAdjust(Siz.ChartHeight, "YAxisValueStep", "YAxisMark2Step", "YMax", "YMin") end Adjusts.ChartWidth.New = Siz.ChartWidth Adjusts.ChartHeight.New = Siz.ChartHeight Adjusts.XMax.New = Parms["XMax"] Adjusts.XMin.New = Parms["XMin"] Adjusts.YMax.New = Parms["YMax"] Adjusts.YMin.New = Parms["YMin"]end

function chartAdjust(userlength, mark, mark2, max, min)

local unit = Parms[mark] if Parms[mark2] ~= nil then unit = Parms[mark2] end Parms[max] = round((Parms[max] / unit) + 0.49, 0) * unit Parms[min] = round((Parms[min] / unit) - 0.49, 0) * unit local count = (Parms[max] - Parms[min]) / unit return round(userlength / count, 0) * countend function outputAdjusts

table.insert(r, " <!-- Adjustments made by Charts SVG:") table.insert(r, " ChartWidth: " .. Adjusts.ChartWidth.Old .. " : " .. Adjusts.ChartWidth.New) table.insert(r, " ChartHeight: " .. Adjusts.ChartHeight.Old .. " : " .. Adjusts.ChartHeight.New) table.insert(r, " XMax: " .. Adjusts.XMax.Old .. " : " .. Adjusts.XMax.New) table.insert(r, " XMin: " .. Adjusts.XMin.Old .. " : " .. Adjusts.XMin.New) table.insert(r, " YMax: " .. Adjusts.YMax.Old .. " : " .. Adjusts.YMax.New) table.insert(r, " YMin: " .. Adjusts.YMin.Old .. " : " .. Adjusts.YMin.New) table.insert(r, " -->") table.insert(r, " ")end

function prelimsPie

Siz.Space =

-- pie chart radius & origin PieRadius = Parms["PieRadius"] -- not local PieOriginX, PieOriginY = PieRadius, PieRadius -- not local

if Parms["Explode"] ~= nil then PieOriginX = PieOriginX + (PieRadius * Parms["ExplodeRadius"] / 100) PieOriginY = PieOriginY + (PieRadius * Parms["ExplodeRadius"] / 100) end if Parms["SegmentText"] ~= nil then -- possibly add for segment texts outside the pie local TextSpaceX = (PieRadius * Parms["SegmentTextRadius"] / 100) + (5 * Siz.Text.Chart * Parms["SegmentTextWidth"] / 100) if Parms["Explode"] ~= nil then TextSpaceX = TextSpaceX + (PieRadius * Parms["ExplodeRadius"] / 100) end PieOriginX = math.max(PieOriginX, TextSpaceX)

local TextSpaceY = (PieRadius * Parms["SegmentTextRadius"] / 100) + (Siz.Text.Chart) if Parms["Explode"] ~= nil then TextSpaceY = TextSpaceY + (PieRadius * Parms["ExplodeRadius"] / 100) end PieOriginY = math.max(PieOriginY, TextSpaceY) end

-- for pie charts, chart size is calculated, not user-defined Siz.ChartWidth = PieOriginX * 2 Siz.ChartHeight = PieOriginY * 2

prelimsLegend

-- Top Space Siz.Space.Top = Siz.ImagePadding.Top + Siz.Text.Title + Siz.ChartMargin Pos.Title = Siz.ImagePadding.Top + FontSiz.Title

-- Bottom Space Siz.Space.Bottom = Siz.ChartMargin

if Parms["LegendType"]

KeyWords["horizontal"] and Parms["LegendX"]

nil then Pos.Legend = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline end if Parms["Footnote"] ~= nil then Pos.Footnote = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Footnote Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote end Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom

-- Left Space Siz.Space.Left = Siz.ImagePadding.Left + Siz.ChartMargin

-- Right Space Siz.Space.Right = Siz.ChartMargin if Parms["LegendType"]

KeyWords["vertical"] and Parms["LegendX"]

nil then Pos.Legend = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + Siz.Legend.Offset Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width end Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right

-- Image size

ImageWidth = round(Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right, 0) ImageHeight = round(Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom, 0)

-- Mult.x, Mult.y and Pos.Axis.Zero are needed for positioning of Legends and Title, Footnote and Chart texts

Mult.x = Siz.ChartWidth / 100 Mult.y = Siz.ChartHeight / 100 Pos.XAxis = Pos.YAxis = Pos.XAxis.Zero = Siz.Space.Left Pos.YAxis.Zero = Siz.Space.Top + Siz.ChartHeightend

function prelimsLegend -- legend height and width

Siz.Legend.ElementWidth = Siz.ChartMargin + (2 * Siz.Legend.Text) + Siz.ChartMargin + Siz.Legend.TextWidth Siz.Legend.ElementHeight = Siz.Legend.Text + Siz.Text.Interline LegendElementsInWidth = 1 -- not local if #SeriesText < 1 then Parms["LegendType"] = KeyWords["none"] elseif Parms["LegendType"]

KeyWords["horizontal"] then LegendElementsInWidth = math.floor(Siz.ChartWidth / Siz.Legend.ElementWidth) LegendElementsInWidth = math.min(LegendElementsInWidth, #SeriesText) local LegendLines = math.ceil(#SeriesText / LegendElementsInWidth)

Siz.Legend.Width = (LegendElementsInWidth * Siz.Legend.ElementWidth) + Siz.ChartMargin Siz.Legend.Height = Siz.ChartMargin + (LegendLines * Siz.Legend.ElementHeight) + Siz.ChartMargin else Siz.Legend.Width = Siz.Legend.ElementWidth + Siz.ChartMargin Siz.Legend.Height = Siz.ChartMargin + (#SeriesText * Siz.Legend.ElementHeight) + Siz.ChartMargin endend

function commonTop

-- output header

table.insert(r, MessageTexts.CopyText) table.insert(r, " ")

-- SVG header stuff

table.insert(r, " ") table.insert(r, " <!-- Generator: commons.wikipedia.org/wiki/" .. mw.getCurrentFrame:getTitle .. " -->") table.insert(r, " <!-- Generator Version: 3.0 -->") table.insert(r, " ") table.insert(r, " ")

table.insert(r, " " .. Parms["FileTitle"] .. "") table.insert(r, " ") table.insert(r, " " .. Parms["FileDesc"] .. "") table.insert(r, " ") table.insert(r, " ")

local DT = os.date("*t") -- returns a table with the current date & time

table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ")

if not DoPie and DoChartAdjust then outputAdjusts end

table.insert(r, " <!--

Backgrounds

-->") table.insert(r, " ")

-- image background rectangle table.insert(r, " <!-- image background -->") table.insert(r, " ") table.insert(r, " ")

if Parms["ImageBackgroundSVG"] ~= nil then table.insert(r, " <!-- Image Background SVG -->") table.insert(r, " " .. Parms["ImageBackgroundSVG"] .. "") table.insert(r, " ") end

-- chart background

if Parms["ChartBackgroundColor"] ~= nil then table.insert(r, " <!-- chart background -->") table.insert(r, " ") table.insert(r, " ") endend

function codeAxisGrids

table.insert(r, " <!--

Axis Chart - Translate

-->") table.insert(r, " ") table.insert(r, " ")

-- the 'horizontal' grid is on the horizontal axis, and has vertical lines -- and vice versa

local HGridInterval, VGridInterval local HLabel = 'x' local VLabel = 'y' if DoHorizontal then HLabel = 'y' VLabel = 'x' end if (HLabel

'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"]

KeyWords["none"] then -- no horizontal grid else HGridInterval = Parms[string.upper(HLabel) .. "Grid"] * Mult[HLabel] end

if (VLabel

'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"]

KeyWords["none"] then -- no vertical grid else VGridInterval = Parms[string.upper(VLabel) .. "Grid"] * Mult[VLabel] end

if Parms["XGrid"] ~= KeyWords["none"] or Parms["YGrid"] ~= KeyWords["none"] then table.insert(r, " <!--

Axis Chart - Grids

-->") table.insert(r, " ")

table.insert(r, " ") table.insert(r, " ")

table.insert(r, " ")

if (HLabel

'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"]

KeyWords["none"] then -- no horizontal grid else table.insert(r, " <!-- " .. HLabel .. "-axis grid, vertical lines -->") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") end

if (VLabel

'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"]

KeyWords["none"] then -- no vertical grid else table.insert(r, " <!-- " .. VLabel .. "-axis grid, horizontal lines -->") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") end table.insert(r, " ") table.insert(r, " ") local XStart = Parms[string.upper(HLabel) .. "Min"] * Mult[HLabel] local YStart = -Parms[string.upper(VLabel) .. "Max"] * Mult[VLabel]

if (HLabel

'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"]

KeyWords["none"] then -- no horizontal grid else table.insert(r, " ") end

if (VLabel

'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"]

KeyWords["none"] then -- no vertical grid else table.insert(r, " ") end table.insert(r, " ") endend

function stylesAreas

table.insert(r, " <!--

Graph - Area Styles

-->") table.insert(r, " ")

table.insert(r, " ") table.insert(r, " ")end

function codeStyleArea(SeriesNumber, Color, Pattern) -- area style for a series

-- SeriesNumber, numeric -- Color, text -- Pattern, text: 'none' or nil means a pattern is not defined

table.insert(r, " /*-- series " .. SeriesNumber .. " --*/") table.insert(r, " .series" .. SeriesNumber .. " ")end

function defsAreas

local exists = false for i = 1, SeriesCount do if FillPattern[i] ~= nil and FillPattern[i] ~= KeyWords["none"] then exists = true end end

if exists then table.insert(r, " <!--

Fill Patterns

-->") table.insert(r, " ") table.insert(r, " ") for i = 1, SeriesCount do if FillPattern[i] ~= nil and FillPattern[i] ~= KeyWords["none"] then -- define the pattern codeDefFillPattern(i, FillPattern[i], Color[i], FillPatternColor[i]) end end table.insert(r, " ") table.insert(r, " ") endend

function codeDefFillPattern(SeriesNumber, PatternType, FillColor, PatternColor) -- definition of a fill pattern for a series

-- SeriesNumber, numeric -- PatternType, numeric -- FillColor, text -- PatternColor, text

local l, t = "", ""

if PatternType

nil then return end

table.insert(r, " <!-- Series " .. SeriesNumber .. "-->")

-- pattern groups: -- 1-8: line hatch, close, rotated 0 (horizontal), 90 (vertical), -45, 45, -22.5, 22,5, -67.5, 67.5 -- 11-18: line hatch, wide, rotated 0 (horizontal), 90 (vertical), -45, 45, -22.5, 22,5, -67.5, 67.5 -- 21-24: cross hatch, close, rotated 0, 45, -22.5, -67.5 -- 31-34: cross hatch, wide, rotated 0, 45, -22.5, -67.5

-- addition of fill patterns will require changes in checkParms to allow them

if PatternType >= 1 and PatternType <= 8 then -- line hatches, close l = "

1 then l = l .. "0" elseif PatternType

2 then l = l .. "90" elseif PatternType

3 then l = l .. "-45" elseif PatternType

4 then l = l .. "45" elseif PatternType

5 then l = l .. "-22.5" elseif PatternType

6 then l = l .. "22.5" elseif PatternType

7 then l = l .. "-67.5" elseif PatternType

8 then l = l .. "67.5" end table.insert(r, l .. ")\">") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType >= 11 and PatternType <= 18 then -- line hatches, wide l = "

11 then l = l .. "0" elseif PatternType

12 then l = l .. "90" elseif PatternType

13 then l = l .. "-45" elseif PatternType

14 then l = l .. "45" elseif PatternType

15 then l = l .. "-22.5" elseif PatternType

16 then l = l .. "22.5" elseif PatternType

17 then l = l .. "-67.5" elseif PatternType

18 then l = l .. "67.5" end table.insert(r, l .. ")\">") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType >= 21 and PatternType <= 24 then -- cross hatches, close l = "

21 then l = l .. "0" elseif PatternType

22 then l = l .. "45" elseif PatternType

23 then l = l .. "-22.5" elseif PatternType

24 then l = l .. "-67.5" end table.insert(r, l .. ")\">") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType >= 31 and PatternType <= 34 then -- cross hatches, wide l = "

31 then l = l .. "0" elseif PatternType

32 then l = l .. "45" elseif PatternType

33 then l = l .. "-22.5" elseif PatternType

34 then l = l .. "-67.5" end table.insert(r, l .. ")\">") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType >= 41 and PatternType <= 49 then -- stipples if PatternType

41 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

42 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

43 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

44 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

45 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

46 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

47 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

48 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

49 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") end table.insert(r, l .. " ") elseif PatternType >= 51 and PatternType <= 56 then -- checks if PatternType

51 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

52 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

53 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

54 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

55 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

56 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") end table.insert(r, " ") elseif PatternType >= 61 and PatternType <= 64 then -- circles if PatternType

61 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

62 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

63 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") elseif PatternType

64 then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") end table.insert(r, " ") endend

function stylesLines

table.insert(r, " <!--

Graph - Line Styles

-->") table.insert(r, " ")

table.insert(r, " ") table.insert(r, " ")end

function codeStyleLineMarker(SeriesNumber, LineRequired, LineColor, LineWidth, LineDash, MarkerRequired, MarkerFill) -- line and marker styles for a series

-- SeriesNumber, numeric -- LineRequired -- LineColor, text -- LineWidth, numeric -- LineDash, text -- MarkerRequired -- MarkerFill, text

if LineRequired ~= nil or MarkerRequired ~= nil then table.insert(r, " /*-- series " .. SeriesNumber .. " --*/") -- line defined if either line or marker required table.insert(r, " .series" .. SeriesNumber .. " ") else -- note: markers are set on the lines when they are created, not here in the line style -- this enables the line in the legend to have only a mid-marker table.insert(r, " }") -- define the marker stroke and fill table.insert(r, " .series" .. SeriesNumber .. "-marker ") end endend

function defsMarkers

local exists = false for i = 1, #SeriesData do if Marker[i] ~= nil and Marker[i] ~= KeyWords["none"] then exists = true end end

if exists then table.insert(r, " <!--

Graph - Markers

-->") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") for i = 1, #SeriesData do if Marker[i] ~= nil and Marker[i] ~= KeyWords["none"] then -- define the shape for the marker codeDefMarkerShape(i, Marker[i], BaseUnit * 2 * MarkerSize[i] / 100, MarkerFill[i]) -- and use the shape in the definition of the marker codeDefMarkerCreate(i, BaseUnit * 2 * MarkerSize[i] / 100) table.insert(r, " ") end end table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") endend

function codeDefMarkerShape(SeriesNumber, MarkerType, MarkerSize, MarkerFill) -- definition of a shape for a series

-- SeriesNumber, numeric -- MarkerType, numeric or text, if text (eg: 'yes') the default is SeriesNumber -- MarkerSize, numeric -- MarkerFill, text

-- addition of further markers will require changes in checkParms to allow them

local l, t = "", ""

table.insert(r, " ") if MarkerType ~= nil then -- MarkerType is a number, use it for the type else -- MarkerType is not a number, use the series number MarkerType = SeriesNumber end

-- all shapes are defined around a centre point of 0,0

if MarkerType

2 then -- circle table.insert(r, " <!-- circle -->") l = " ") elseif MarkerType

3 then -- triangle (point up) t = round(MarkerSize / 2, 2) table.insert(r, " <!-- triangle, point up -->") l = " ") elseif MarkerType

4 then -- tilted square (diamond) table.insert(r, " <!-- diamond -->") l = " ") elseif MarkerType

5 then -- tilted triangle (point down) t = round(MarkerSize / 2, 2) table.insert(r, " <!-- triangle, point down -->") l = " ") elseif MarkerType

6 then -- cross t = round(MarkerSize / 2, 2) table.insert(r, " <!-- cross -->") table.insert(r, " ") elseif MarkerType

7 then -- plus t = round(MarkerSize / 2, 2) table.insert(r, " <!-- plus -->") table.insert(r, " ") else -- default and 1 -- square table.insert(r, " <!-- square -->") l = " ") end table.insert(r, " ")end

function codeDefMarkerCreate(SeriesNumber, MarkerSize) -- definition of a marker for a series

-- SeriesNumber, numeric -- MarkerSize, numeric

local l = ""

l = " " table.insert(r, l) table.insert(r, " ") table.insert(r, " ")end

function elementsGraphs

local BarNumber = BarSeriesCount + 1 local DoLabels = false local LabelPos =

table.insert(r, " <!--

Graph - Bars and Lines

-->") table.insert(r, " ")

for i = #SeriesData, 1, -1 do if SeriesText[i]

nil then table.insert(r, " <!-- " .. "Series" .. i .. " -->") else table.insert(r, " <!-- " .. SeriesText[i] .. " -->") end if DoYAxis2 and YAxis2[i] ~= nil then table.insert(r, " <!-- Y values are on second Y axis -->") end if Parms["IncludeOriginalData"]

KeyWords["no"] or (Parms["IncludeOriginalData"]

KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then table.insert(r, " <!-- original data: not included -->") else table.insert(r, " <!-- original data:")

for k, v in ipairs(OriginalData[i]) do table.insert(r, " " .. v[1] .. " " .. v[2]) end table.insert(r, " -->") end LabelPos[i] = if SType[i]

KeyWords["bar"] then BarNumber = BarNumber - 1 for k, v in ipairs(SeriesData[i]) do if v[2] ~= nil then local px = tonumber(v[1]) if #GroupText > 0 then if DoStack or DoStack100 then px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth + UnitWidth / 2 + (BarSpace / 2) + iif(DoHorizontal, BarWidth, 0) else px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth + UnitWidth / 2 + UnitWidth * (iif(DoGroupsTopDown, BarSeriesCount - BarNumber + 1, BarNumber) - iif(DoHorizontal, 0, 1)) + (BarSpace / 2) end else px = (px * Mult.x) + (iif(DoHorizontal, 1, -1) * (BarWidth / 2)) end local Min, Multiply = Parms["YMin"], Mult.y if DoYAxis2 and YAxis2[i] ~= nil then Min = Parms["Y2Min"] Multiply = Mult.y2 end local val = tonumber(v[2]) local posn, size = 0, 0 if DoHorizontal then if Min >= 0 then posn = 0 size = val - Min elseif val >= 0 then posn = 0 size = val else posn = -val size = -val end posn = Pos.XAxis.Line - (posn * Multiply) size = size * Multiply

table.insert(r, " ") else if Min >= 0 then posn = val - Min size = val - Min elseif val >= 0 then posn = val size = val else posn = 0 size = -val end posn = Pos.XAxis.Line - (posn * Multiply) size = size * Multiply

table.insert(r, " ") end if Labels[i] ~= nil then LabelPos[i][k] = if DoHorizontal then LabelPos[i][k]["y"] = -(px - (BarWidth / 2)) if val <= 0 then LabelPos[i][k]["x"] = posn - Siz.Text.Interline else LabelPos[i][k]["x"] = posn + size + Siz.Text.Interline end else LabelPos[i][k]["x"] = px + (BarWidth / 2) if val <= 0 then LabelPos[i][k]["y"] = posn + size + Siz.Text.Interline + Siz.Text.Labels else LabelPos[i][k]["y"] = posn - Siz.Text.Interline end end DoLabels = true end end end else -- line tr = " 0 then px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth + (GroupWidth / 2) else px = px * Mult.x end

local py = tonumber(v[2]) if DoYAxis2 and YAxis2[i] ~= nil then py = py * Mult.y2 else py = py * Mult.y end if DoHorizontal then if DoArea and k

1 then table.insert(r, " " .. round(iif(Parms["YMin"] > 0, Parms["YMin"] * Mult.y, 0), 2) .. ", " .. round(-px, 2) .. "") end table.insert(r, " " .. round(py, 2) .. ", " .. round(-px, 2) .. "") lastx = px if Labels[i] ~= nil then LabelPos[i][k] = LabelPos[i][k]["x"] = px + Siz.Text.Interline LabelPos[i][k]["y"] = py + Siz.Text.Interline DoLabels = true end else if DoArea and k

1 then table.insert(r, " " .. round(px, 2) .. ", " .. round(iif(Parms["YMin"] > 0, -Parms["YMin"] * Mult.y, 0), 2) .. "") end table.insert(r, " " .. round(px, 2) .. ", " .. round(-py, 2) .. "") lastx = px if Labels[i] ~= nil then LabelPos[i][k] = LabelPos[i][k]["x"] = px + Siz.Text.Interline LabelPos[i][k]["y"] = py + Siz.Text.Interline DoLabels = true end end end if DoArea then if DoHorizontal then table.insert(r, " " .. round(iif(Parms["YMin"] > 0, Parms["YMin"] * Mult.y, 0), 2) .. ", " .. round(-lastx, 2) .. "") else table.insert(r, " " .. round(lastx, 2) .. ", " .. round(iif(Parms["YMin"] > 0, -Parms["YMin"] * Mult.y, 0), 2) .. "") end end table.insert(r, " \"/>") end table.insert(r, " ") end

if not DoStack100 and DoLabels then table.insert(r, " <!--

Data Labels

-->") table.insert(r, " ")

table.insert(r, " ") table.insert(r, " ")

for i = #SeriesData, 1, -1 do if Labels[i] ~= nil then for k, v in ipairs(SeriesData[i]) do if SType[i]

KeyWords["bar"] then l = " " .. v[2] .. "" table.insert(r, l) else if DoHorizontal then table.insert(r, " " .. v[2] .. "") else table.insert(r, " " .. v[2] .. "") end end end table.insert(r, " ") end end endend

function elementsPie

table.insert(r, " <!--

Pie Segments

-->") table.insert(r, " ")

table.insert(r, " ")

if Parms["IncludeOriginalData"]

KeyWords["no"] or (Parms["IncludeOriginalData"]

KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then table.insert(r, " <!-- original data: not included -->") else table.insert(r, " <!-- original data:")

for k, v in ipairs(OriginalData) do table.insert(r, " " .. v[1] .. " " .. v[2]) end table.insert(r, " -->") end

local Total = 0 for k, v in ipairs(SeriesData) do Total = Total + v[2] end

local TwoPi = math.pi * 2 local Sweep = 0 if Parms["PieSweepDir"] ~= "AntiClockwise" then -- change arcs to clockwise Sweep = 1 end

local Radian = 0

if Parms["PieStartAngle"] ~= 0 then Radian = math.rad(Parms["PieStartAngle"]) end

local InnerX, InnerY, InnerRadius = 0, 0, 0 if Parms["DoughnutHole"]

nil then -- no doughnut, segments start at pie origin (0, 0) else -- doughnut, segments start at the hole radius InnerRadius = Parms["DoughnutHole"] / 100 * PieRadius

InnerX = math.cos(Radian) * InnerRadius InnerY = math.sin(Radian) * InnerRadius end

-- outer arcs start at the pie radius local OuterX = math.cos(Radian) * PieRadius local OuterY = math.sin(Radian) * PieRadius local Mid =

for k, v in ipairs(SeriesData) do local Arc = 0 -- default is short arc (<= 180 degrees) if (v[2] / Total * TwoPi) > math.pi then Arc = 1 -- long arc end

Mid[k] = Radian + ((v[2] / Total * TwoPi) / 2 * iif(Sweep

0, 1, -1))

-- adjust X and Y for explode local DX, DY = 0, 0 if Parms["Explode"]

nil or (type(Parms["Explode"])

"number" and k > Parms["Explode"]) then -- no explode for this segment else DX = math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100) DY = math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100) end

Radian = Radian + (v[2] / Total * TwoPi * iif(Sweep

0, 1, -1)) local NextOuterX = math.cos(Radian) * PieRadius local NextOuterY = math.sin(Radian) * PieRadius

t = "

nil then t = t .. " z\" />" else local NextInnerX = math.cos(Radian) * InnerRadius local NextInnerY = math.sin(Radian) * InnerRadius

t = t .. " l " .. round(NextInnerX - NextOuterX, 2).. ", " .. -round(NextInnerY - NextOuterY, 2) .. " a " .. InnerRadius .. ", " .. InnerRadius .. " 0 " .. Arc .. " " .. iif(Sweep

0, 1, 0) .. " " .. round(InnerX - NextInnerX, 2) .. ", " .. -round(InnerY - NextInnerY, 2) .. "\" />"

InnerX = NextInnerX InnerY = NextInnerY end

table.insert(r, t)

OuterX = NextOuterX OuterY = NextOuterY end

table.insert(r, " ") table.insert(r, " ")

if Parms["SegmentText"] ~= nil then

-- pie segment texts

table.insert(r, " <!--

Pie Segment Texts

-->") table.insert(r, " ")

table.insert(r, " ") table.insert(r, " ")

table.insert(r, " ")

for k, v in ipairs(SeriesData) do -- adjust X and Y for explode local DX, DY = 0, 0 if Parms["Explode"]

nil or (type(Parms["Explode"])

"number" and k > Parms["Explode"]) then -- no explode for this segment else DX = math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100) DY = math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100) end

local TextX = math.cos(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100) local TextY = math.sin(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100) if TextY < 0 then TextY = TextY - FontSiz.LegendText end

t = " "

local tt = "" if string.find(Parms["SegmentText"], KeyWords["text"], 1, true) ~= nil then tt = SeriesText[k] end if string.find(Parms["SegmentText"], KeyWords["value"], 1, true) ~= nil then tt = tt .. iif(string.len(tt) > 0, " ", "") .. v[2] end if string.find(Parms["SegmentText"], KeyWords["percent"], 1, true) ~= nil then tt = tt .. iif(string.len(tt) > 0, " ", "") .. round(v[2] / Total * 100, 0) .. "%" end t = t .. tt table.insert(r, t .. "") end table.insert(r, " ") table.insert(r, " ") endend

function codeAxes

if Parms["Debug"] ~= nil and string.find(Parms["Debug"], "pos") ~= nil then -- show positions tables table.insert(r, "<!--") listTable(Mult, " ", "Mult") listTable(FontSiz, " ", "FontSiz") table.sort(Pos) listTable(Pos, " ", "Pos") table.sort(Siz) listTable(Siz, " ", "Siz") table.insert(r, "-->") end -- axis styles

table.insert(r, " <!--

Axis Styles

-->") table.insert(r, " ")

table.insert(r, " ") table.insert(r, " ")

table.insert(r, " <!--

Axis Marks

-->") table.insert(r, " ")

if DoHorizontal then codeAxisMarks('x', 'y', -1, Pos.XAxis.Line, -Parms["XMax"], Siz.ChartHeight)

codeAxisMarks('y', 'x', 0, Pos.YAxis.Line, Parms["YMin"], Siz.ChartWidth) if DoYAxis2 then codeAxisMarks('y2', 'x', -1, Pos.YAxis2.Line, Parms["Y2Min"], Siz.ChartWidth) end else codeAxisMarks('x', 'x', 0, Pos.XAxis.Line, Parms["XMin"], Siz.ChartWidth) codeAxisMarks('y', 'y', -1, Pos.YAxis.Line, -Parms["YMax"], Siz.ChartHeight) if DoYAxis2 then codeAxisMarks('y2', 'y', 0, Pos.YAxis2.Line, -Parms["Y2Max"], Siz.ChartHeight) end end

if Parms["XAxisArrows"] ~= nil or Parms["YAxisArrows"] ~= nil then table.insert(r, " <!--

Axis Arrows

-->") table.insert(r, " ") local ArrowSize = BaseLineWidth * 12 table.insert(r, " ") -- start arrow, for axes if Parms["XMin"] < 0 or Parms["YMin"] < 0 then table.insert(r, " ") t = round(ArrowSize / 2, 2) table.insert(r, " ") table.insert(r, " ") end

table.insert(r, " ") t = round(ArrowSize / 2, 2) table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") if Parms["XAxisArrows"] ~= nil then if Parms["XMin"] < 0 then l = " " table.insert(r, l) table.insert(r, " ") table.insert(r, " ") end

l = " " table.insert(r, l) table.insert(r, " ") table.insert(r, " ") end if Parms["YAxisArrows"] ~= nil then if Parms["YMin"] < 0 then l = " " table.insert(r, l) table.insert(r, " ") table.insert(r, " ") end

l = " " table.insert(r, l) table.insert(r, " ") table.insert(r, " ") end table.insert(r, " ") table.insert(r, " ") end table.insert(r, " <!--

Axis Lines

-->") table.insert(r, " ") if DoHorizontal then codeAxisLine('x', 'y', Pos.XAxis.Line, -Parms["XMin"], -Siz.ChartHeight) codeAxisLine('y', 'x', Pos.YAxis.Line, Parms["YMin"], Siz.ChartWidth)

if DoYAxis2 then codeAxisLine('y2', 'x', Pos.YAxis2.Line, Parms["Y2Min"], Siz.ChartWidth) end else codeAxisLine('x', 'x', Pos.XAxis.Line, Parms["XMin"], Siz.ChartWidth) codeAxisLine('y', 'y', Pos.YAxis.Line, -Parms["YMin"], -Siz.ChartHeight)

if DoYAxis2 then codeAxisLine('y2', 'y', Pos.YAxis2.Line, -Parms["Y2Min"], -Siz.ChartHeight) end end table.insert(r, " ") Format =

if #GroupText

0 then Format.x = (Parms["XMax"] >= 10000) if Parms["XAxisValueFormat"] ~= nil then Format.x = not (Parms["XAxisValueFormat"]

'none') end end Format.y = (Parms["YMax"] >= 10000) if Parms["YAxisValueFormat"] ~= nil then Format.y = not (Parms["YAxisValueFormat"]

'none') end if DoYAxis2 then Format.y2 = (Parms["Y2Max"] >= 10000) if Parms["YAxis2ValueFormat"] ~= nil then Format.y2 = not (Parms["YAxis2ValueFormat"]

'none') end end table.insert(r, " <!--

Axis Values

-->") table.insert(r, " ")

if DoHorizontal then codeAxisValues('x', 'y', Parms["XAxisValueStep"], -1, -1, Parms["XMin"], Parms["XMax"], FontSiz["XAxisValues"] / 3, Pos.XAxis.Values, Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"], Parms["XAxisValueAbsolute"], Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"], Format.x) codeAxisValues('y', 'x', Parms["YAxisValueStep"], 0, 1, Parms["YMin"], Parms["YMax"], 0, Pos.YAxis.Values, Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"], Parms["YAxisValueAbsolute"], Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"], Format.y)

if DoYAxis2 then codeAxisValues('y2', 'x', Parms["YAxis2ValueStep"], -1, 1, Parms["Y2Min"], Parms["Y2Max"], 0, Pos.YAxis2.Values, Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"], Parms["YAxis2ValueAbsolute"], Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"], Format.y2) end else codeAxisValues('x', 'x', Parms["XAxisValueStep"], 0, 1, Parms["XMin"], Parms["XMax"], 0, Pos.XAxis.Values, Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"], Parms["XAxisValueAbsolute"], Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"], Format.x) codeAxisValues('y', 'y', Parms["YAxisValueStep"], -1, -1, Parms["YMin"], Parms["YMax"], FontSiz["YAxisValues"] / 3, Pos.YAxis.Values, Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"], Parms["YAxisValueAbsolute"], Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"], Format.y)

if DoYAxis2 then codeAxisValues('y2', 'y', Parms["YAxis2ValueStep"], 0, -1, Parms["Y2Min"], Parms["Y2Max"], FontSiz["YAxis2Values"] / 3, Pos.YAxis2.Values, Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"], Parms["YAxis2ValueAbsolute"], Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"], Format.y2) end end

table.insert(r, " <!-- End Axis Chart Translate

-->") table.insert(r, " ") table.insert(r, " ") -- axis titles are always outside the chart, so are outside the chart translate if Parms["XAxisTitle"] ~= nil or Parms["YAxisTitle"] ~= nil or (DoYAxis2 and Parms["YAxis2Title"] ~= nil) then table.insert(r, " <!--

Axis Titles

-->") table.insert(r, " ")

if DoHorizontal then if Parms["XAxisTitle"] ~= nil then codeAxisTitle('x', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5)) end if Parms["YAxisTitle"] ~= nil then codeAxisTitle('y', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5)) end if DoYAxis2 and Parms["YAxis2Title"] ~= nil then codeAxisTitle('y2', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5)) end else if Parms["XAxisTitle"] ~= nil then codeAxisTitle('x', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5)) end if Parms["YAxisTitle"] ~= nil then codeAxisTitle('y', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5)) end if DoYAxis2 and Parms["YAxis2Title"] ~= nil then codeAxisTitle('y2', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5)) end end table.insert(r, " ") endend

function codeAxisMarks(AxisName, ChangeDim, MarkOffset, AxisLinePos, AxisStart, LineLength)

-- AxisName, text: the name of the axis being coded, "x", "y" or "y2" -- ChangeDim, text: 'x' or 'y' - the dimension the axis actually changes in -- MarkOffset, numeric: direction of mark start away from the line, -1 or 0 -- AxisLinePos, numeric: position of the axis, ie: distance of the line from the chart origin point -- AxisStart, numeric: position of the start of the axis line -- LineLength, numeric: the length of the axis line

local OtherDim = "y" if ChangeDim

"y" then OtherDim = "x" end

local AxisParmName = string.upper(AxisName) .. "Axis" if AxisName

"y2" then AxisParmName = "YAxis2" end local MarkGapDim = "width" local MarkSizDim = "height" if ChangeDim

"y" then MarkGapDim = "height" MarkSizDim = "width" end

if AxisName

"x" and #GroupText ~= 0 then -- no marks for x axis with groups else table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") table.insert(r, " ")

if Parms[AxisParmName .. "Mark2Step"] ~= nil then table.insert(r, " ") table.insert(r, " ") table.insert(r, " ") end table.insert(r, " ") table.insert(r, " ")

if Parms[AxisParmName .. "Mark2Step"] ~= nil then table.insert(r, " ") end

table.insert(r, " ")

table.insert(r, " ") endend

function codeAxisLine(AxisName, ChangeDim, AxisLinePos, AxisStart, LineLength)

local OtherDim = "y" if ChangeDim

"y" then OtherDim = "x" end local l = " "

table.insert(r, l)end

function codeAxisValues(AxisName, ChangeDim, ValueStep, MarkOffset, PosChangeSign, ValueMin, ValueMax, TextShift, OtherValuesPos, ValueMultiplier, ValueRound, ValueAbsolute, Prefix, Suffix, Format) -- AxisName, text: the name of the axis being coded, "x", "y" or "y2" -- Parms for location -- ChangeDim, text: 'x' or 'y' - the dimension the axis actually changes in -- ValueStep, numeric: the step between values (and major marks) -- MarkOffset, numeric: direction of mark start away from the line, -1 or 0 -- PosChangeSign, numeric: the position direction for +ve changes in value, 1 or -1 -- ValueMin, numeric: the minimum value shown -- ValueMax, numeric: the maximum value shown -- TextShift, numeric: amount to shift text, to get it centered with its marks -- OtherValuesPos, numeric: alignment position for all values -- Parms for values format -- ValueMultiplier, numeric -- ValueRound, numeric -- ValueAbsulute -- Prefix, text -- Suffix, text -- Format, boolean local AxisParmName = string.upper(AxisName) .. "Axis" if AxisName

"y2" then AxisParmName = "YAxis2" end local Anchor = "middle" local RotateText = "" if ChangeDim

"y" then if MarkOffset

-1 then -- text is left of line Anchor = "end" else -- text is right of line Anchor = "start" end end if Parms[AxisParmName .. "ValueRotate"] ~= nil then RotateText = " transform=\"rotate(" .. Parms[AxisParmName .. "ValueRotate"] .. ", " if ChangeDim

"x" then Anchor = "start" if Parms[AxisParmName .. "ValueRotate"] < 0 and MarkOffset

0 then Anchor = "end" elseif Parms[AxisParmName .. "ValueRotate"] >= 0 and MarkOffset

-1 then Anchor = "end" end end end local Format = (Parms[string.upper(AxisName) .. "Max"] >= 10000) if Parms[AxisParmName .. "ValueFormat"] ~= nil then Format = not (Parms[AxisParmName .. "ValueFormat"]

'none') end

local l = "" local Position = 0

if AxisName ~= 'x' or #GroupText

0 then -- numeric values table.insert(r, "

"x", 0, round(OtherValuesPos, 2)) .. ", " .. iif(ChangeDim

"x", round(OtherValuesPos, 2), TextShift) .. ")\"" .. ">")

local ValueStart = 0 if ValueMin ~= 0 then ValueStart = math.ceil(ValueMin / ValueStep) * ValueStep end local Value = ValueStart while Value <= ValueMax do Position = Value * Mult[AxisName] * PosChangeSign l = "

'x' then if string.len(RotateText) > 0 then l = l .. RotateText .. round(Position, 2) .. ", 0)\"" end else if string.len(RotateText) > 0 then l = l .. RotateText .. "0, " .. round(Position - TextShift, 2) .. ")\"" end end l = l .. ">" l = l .. Prefix local v = Value if ValueAbsolute ~= nil then v = math.abs(v) end if Format then l = l .. mw.getContentLanguage:formatNum(round(v * ValueMultiplier, ValueRound)) else l = l .. round(v * ValueMultiplier, ValueRound) end l = l .. Suffix .. "" table.insert(r, l) Value = Value + ValueStep end else -- group name values table.insert(r, "

"x", 0, round(OtherValuesPos, 2)) .. ", " .. iif(ChangeDim

"x", round(OtherValuesPos, 2), TextShift) .. ")\"" .. ">")

for k, v in ipairs(GroupText) do Position = ((iif(DoGroupsTopDown, #GroupText - k, k - 1) * GroupWidth) + (GroupWidth / 2)) * PosChangeSign l = "

'x' then if string.len(RotateText) > 0 then l = l .. RotateText .. round(Position, 2) .. ", 0)\"" end else if string.len(RotateText) > 0 then l = l .. RotateText .. "0, " .. round(Position - TextShift, 2) .. ")\"" end end l = l .. ">" .. v .. ""

table.insert(r, l) end end table.insert(r, " ") table.insert(r, " ")end

function codeAxisTitle(AxisName, ChangeDim, TitleCentre)

local OtherDim = "y" if ChangeDim

"y" then OtherDim = "x" end

local AxisParmName = string.upper(AxisName) .. "Axis" if AxisName

"y2" then AxisParmName = "YAxis2" end local l = "

'y' then l = l .. " transform = \"rotate(-90, " .. round(Pos[AxisParmName]["Title"], 2) .. ", " .. round(TitleCentre, 2) .. ")\"" end l = l .. " text-anchor=\"middle\">" .. Parms[AxisParmName .. "Title"] .. "" table.insert(r, l)end

function commonBottom

if Parms["LegendType"]

KeyWords["none"] and Parms["Title"]

nil and Parms["Footnote"]

nil and #ChartText <= 0 then -- no common-element styles else table.insert(r, " <!--

Common-element Styles

-->") table.insert(r, " ")

table.insert(r, " ") table.insert(r, " ") end

-- legend

if Parms["LegendType"]

KeyWords["none"] then -- no legend else table.insert(r, " <!--

Legend

-->") table.insert(r, " ") if Parms["LegendSVG"] ~= nil then -- replace all of legend with user-supplied SVG code table.insert(r, " " .. Parms["LegendSVG"]) table.insert(r, " ") else tr = "

KeyWords["horizontal"] then -- horizontal legend if Parms["LegendX"] ~= nil then if DoHorizontal then tr = tr .. round(Pos.YAxis.Zero + (Parms["LegendY"] * Mult.y), 2) .. "" .. ", " .. round(Pos.XAxis.Zero - (Parms["LegendX"] * Mult.x), 2) else tr = tr .. round(Pos.XAxis.Zero + (Parms["LegendX"] * Mult.x), 2) .. "" .. ", " .. round(Pos.YAxis.Zero - (Parms["LegendY"] * Mult.y), 2) end else tr = tr .. round(Siz.Space.Left, 2) .. ", " .. round(Pos.Legend, 2) end table.insert(r, tr .. ")\">")

table.insert(r, " ")

local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin for k, v in ipairs(SeriesText) do table.insert(r, " ") codeLegendElement((SType[k]

KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Marker[k]) PosX = PosX + Siz.Legend.ElementWidth if math.fmod(k, LegendElementsInWidth)

0 then -- new line of legend elements PosX = Siz.ChartMargin PosY = PosY + Siz.Legend.ElementHeight end end else -- vertical legend if Parms["LegendX"] ~= nil then if DoHorizontal then tr = tr .. round(Pos.YAxis.Zero + (Parms["LegendY"] * Mult.y), 2) .. ", " .. round(Pos.XAxis.Zero - (Parms["LegendX"] * Mult.x), 2) else tr = tr .. round(Pos.XAxis.Zero + (Parms["LegendX"] * Mult.x), 2) .. " " .. round(Pos.YAxis.Zero - (Parms["LegendY"] * Mult.y), 2) end else tr = tr .. round(Pos.Legend, 2) .. ", " .. round(Siz.Space.Top + (0.1 * Siz.ChartHeight), 2) end table.insert(r, tr .. ")\">")

table.insert(r, " ")

local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin for k, v in ipairs(SeriesText) do table.insert(r, " ") codeLegendElement((SType[k]

KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Marker[k]) PosY = PosY + Siz.Legend.ElementHeight end end table.insert(r, " ") table.insert(r, " ") end end

-- chart texts

if #ChartText > 0 then table.insert(r, " <!--

Chart Texts

-->") table.insert(r, " ")

table.insert(r, " ") for i = 1, #ChartText do if ChartText[i] ~= nil then if DoHorizontal then table.insert(r, " " .. ChartText[i] .. "") else table.insert(r, " " .. ChartText[i] .. "") end end end table.insert(r, " ") table.insert(r, " ") end -- title and footnote

if Parms["Title"] ~= nil then table.insert(r, " <!--

Title Text

-->") tr = " " else tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms["TitleX"] * Mult.x), 2) .. "\"" .. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["TitleY"] * Mult.y), 2) .. "\">" end else tr = tr .. " x=\"" .. round(Siz.Space.Left + Siz.ChartWidth * 0.5, 2) .. "\"" .. " y=\"" .. round(Pos.Title, 2) .. "\">" end table.insert(r, tr .. Parms["Title"] .. "") table.insert(r, " ") end

if Parms["Footnote"] ~= nil then table.insert(r, " <!--

Footnote Text

-->")

tr = " " else tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms["FootnoteX"] * Mult.x), 2) .. "\"" .. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["FootnoteY"] * Mult.y), 2) .. "\">" end else tr = tr .. " x=\"" .. round(ImageWidth - Siz.ImagePadding.Right, 2) .. "\"" .. " y=\"" .. round(Pos.Footnote, 2) .. "\">" end table.insert(r, tr .. Parms["Footnote"] .. "") table.insert(r, " ") end

if Parms["ImageForegroundSVG"] ~= nil then table.insert(r, " <!-- Image Foreground SVG -->") table.insert(r, " " .. Parms["ImageForegroundSVG"] .. "") table.insert(r, " ") end

table.insert(r, " ") table.insert(r, " ")end

function codeLegendElement(Lines, SeriesNumber, PosX, PosY, Padding, TextSize, Text, Marker) -- one entry in the legend

-- Lines, boolean -- SeriesNumber, numeric -- PosX, numeric -- PosY, numeric (= the top of the legend element) -- Padding, numeric -- TextSize, numeric -- Text, text -- Marker, text

local l = ""

if Lines then local lineY = round(PosY + TextSize / 2, 2)

l = " ") table.insert(r, " " .. Text .. "") else table.insert(r, " ") table.insert(r, " " .. Text .. "") endend

function outputDebugInfo

if Parms["Debug"] ~= nil and string.find(Parms["Debug"], KeyWords["parms"]) ~= nil then -- list all parameters table.insert(r, " All Parameters :") for k, v in pairs(Args) do table.insert(r, " " .. k .. "=" .. v) end table.insert(r, " ")

-- list all recognised parameters sorted by key table.insert(r, " Parameters :") for k, v in spairs(Parms) do table.insert(r, " " .. k .. "=" .. v .. " (" .. type(v) .. ")") end table.insert(r, " ")

if string.find(Parms["Debug"], "parmsstop") ~= nil then return false end end

if Parms["Debug"] ~= nil and string.find(Parms["Debug"], "tables") ~= nil then -- list contents of internal tables

if DoStack or DoStack100 then listTable(OriginalData, " ", "OriginalData") end listTable(SeriesData, " ", "SeriesData") listTable(SType, " ", "SType") listTable(YAxis2, " ", "YAxis2") listTable(Labels, " ", "Labels") listTable(Color, " ", "Color") listTable(LineShow, " ", "LineShow") listTable(LineWidth, " ", "LineWidth") listTable(LineDash, " ", "LineDash") listTable(Marker, " ", "Marker") listTable(MarkerFill, " ", "MarkerFill") listTable(MarkerSize, " ", "MarkerSize") listTable(FillPattern, " ", "FillPattern") listTable(FillPatternColor, " ", "FillPatternColor") listTable(SeriesText, " ", "SeriesText") listTable(GroupText, " ", "GroupText") listTable(ChartText, " ", "ChartText") -- listTable(FontSiz, " ", "FontSiz") -- listTable(Siz, " ", "Siz") -- listTable(Pos, " ", "Pos") -- listTable(Mult, " ", "Mult") -- listTable(Adjusts, " ", "Adjusts") table.insert(r, " ")

if string.find(Parms["Debug"], "tablesstop") ~= nil then return false end end

table.insert(r, "----------") table.insert(r, " ") return trueend

function listTable(Tab, Indent, Title) -- lists all contents of a table, iterating over all sub-tables

if Title ~= nil then table.insert(r, " " .. Title .. ":") end for k, v in pairs(Tab) do if type(v)

"table" then table.insert(r, Indent .. k .. ":") listTable(v, Indent .. " ", nil) else table.insert(r, Indent .. k .. ": " .. v .. " (" .. type(v) .. ")") end endend

function unknownParameters(knowns, regexps) -- outputs a table of parameters either not in knowns, or not matching a pattern in regexps

-- knowns - a table where the values are the known parameters -- regexps - a table where the values are lua patterns that parameters may match

-- modified from module at en.wikipedia.org/wiki/Module:Check_for_unknown_parameters

local knownparms =

local output =

-- create the lists of known parms and regular expressions for k, v in spairs(knowns) do v = trim(v) knownparms[v] = 1 end for k, v in pairs(regexps) do regexps[k] = '^' .. v .. '$' end -- check each entry in Args for k, v in pairs(Args) do if type(k)

'string' and knownparms[k]

nil then local knownflag = false for i, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag then local vlen = mw.ustring.len(v) v = mw.ustring.sub(v, 1, (vlen < 25) and vlen or 25) table.insert(output, k .. "=" .. v .. ((vlen >= 25) and ' ...' or )) end elseif type(k)

'number' and knownparms[tostring(k)]

nil then local vlen = mw.ustring.len(v) v = mw.ustring.sub(v, 1, (vlen < 25) and vlen or 25) table.insert(output, k .. "=" .. v .. ((vlen >= 25) and ' ...' or )) end end return outputend

-- utility functions

function iif(test, tret, fret) -- if test is true, returns tret if defined, otherwise returns true -- if test is false, returns fret if defined, otherwise returns false

-- note that this function cannot be used to avoid evaluating either tret or fret, as they are both evaluated in the function call

if test then if tret

nil then return true else return tret end end if fret

nil then return false end return fretend

function trim(s) -- the match this pattern returns is the string with any spaces (%s) at the start(^) and end ($) not included return s:match('^%s*(.-)%s*$') end function round(x, p) -- round number x to precision p

-- p > 0 rounds to p decimal places, eg: round(4.57, 1) = 4.6 -- p = 0 (or not given) rounds to nearest integer, eg: round(6.6) = 7, round(6.5) = 6 -- p < 0 rounds to p places above zero, eg: round(147, -1) = 150

local res

if type(x) ~= "number" then return nil end if type(p)

"number" then -- any decimal places in p are ignored p = math.floor(p) else p = nil end if p

nil or p

0 then return math.floor(x + 0.5) else res = x * 10^p res = math.floor(res + 0.5) res = res * 10^-p return res endend

function decPlaces(val) -- returns the number of decimal places in a numeric value, or a string convertible to a number val = tonumber(val) if val

nil then return 0 end val = tostring(val) local point = string.find(val, ".") if point

0 then return 0 end return string.len(val) - pointend

function copyTable(from, to) -- copies a table to another table local k, v for k, v in ipairs(from) do if type(v)

"table" then to[k] = copyTable(v, to[k]) else to[k] = v end endend

function spairs(t, order) -- returns an iterator function that in turn returns table t in the order of the keys -- sort is done using an optional order function

-- collect the keys local keys = for k in pairs(t) do keys[#keys+1] = k end

-- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if order then table.sort(keys, function(a,b) return order(t, a, b) end) else table.sort(keys) end

-- return the iterator function local i = 0 return function i = i + 1 if keys[i] then return keys[i], t[keys[i]] end endend

return