Module:Charts SVG

-- 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, 0 local AutoDataPointsLimit, DataPointsCount = 100, 0 local DoPie, DoArea, DoStack, DoStack100, DoYAxis2 = false, false, false, false, false local Msgs = {}

local FontSiz, Siz, Pos, Mult = {}, {}, {}, {} local ChartWidth, ChartHeight, ImageWidth, ImageHeight local 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 = { barChart = 'barChart', lineChart = 'lineChart', scatterChart = 'scatterChart', mixedChart = 'mixedChart', pieChart = 'pieChart',

FileTitle = 'FileTitle', FileDesc = 'FileDesc',

ImagePadding = 'ImagePadding', ImagePaddingTop = 'ImagePaddingTop', ImagePaddingBottom = 'ImagePaddingBottom', ImagePaddingLeft = 'ImagePaddingLeft', ImagePaddingRight = 'ImagePaddingRight',

ImageBackgroundColor = 'ImageBackgroundColor', ImageBackgroundSVG = 'ImageBackgroundSVG', ImageBorder = 'ImageBorder', ImageForegroundSVG = 'ImageForegroundSVG',

Title = 'Title', TitleX = 'TitleX', TitleY = 'TitleY',

Footnote = 'Footnote', FootnoteX = 'FootnoteX', FootnoteY = 'FootnoteY',

Area = 'Area', Stack = 'Stack', Stack100 = 'Stack100',

ChartWidth = 'ChartWidth', ChartHeight = 'ChartHeight',

GreyScale = 'GreyScale', GrayScale = 'GrayScale', LineWidth = 'LineWidth', ChartBackgroundColor = 'ChartBackgroundColor',

XMin = 'XMin', XMax = 'XMax', XAxisTitle = 'XAxisTitle', XAxisValueStep = 'XAxisValueStep', XAxisValueMultiplier = 'XAxisValueMultiplier', XAxisValueRound = 'XAxisValueRound', XAxisValuePrefix = 'XAxisValuePrefix', XAxisValueSuffix = 'XAxisValueSuffix', XAxisValueFormat = 'XAxisValueFormat', XAxisValueRotate = 'XAxisValueRotate', XAxisMark2Step = 'XAxisMark2Step',

YMin = 'YMin', YMax = 'YMax', YAxisTitle = 'YAxisTitle', YAxisValueStep = 'YAxisValueStep', YAxisValueMultiplier = 'YAxisValueMultiplier', YAxisValueRound = 'YAxisValueRound', YAxisValuePrefix = 'YAxisValuePrefix', YAxisValueSuffix = 'YAxisValueSuffix', YAxisValueFormat = 'YAxisValueFormat', YAxisMark2Step = 'YAxisMark2Step', YAxisColor = 'YAxisColor',

Y2Min = 'Y2Min', Y2Max = 'Y2Max', YAxis2Title = 'YAxis2Title', YAxis2ValueStep = 'YAxis2ValueStep', XAxis2ValueMultiplier = 'XAxis2ValueMultiplier', YAxis2ValueRound = 'YAxis2ValueRound', YAxis2ValuePrefix = 'YAxis2ValuePrefix', YAxis2ValueSuffix = 'YAxis2ValueSuffix', YAxis2ValueFormat = 'YAxis2ValueFormat', YAxis2Mark2Step = 'YAxis2Mark2Step', YAxis2Color = 'YAxis2Color',

XGrid = 'XGrid', YGrid = 'YGrid',

LegendType = 'LegendType', LegendX = 'LegendX', LegendY = 'LegendY', LegendTextWidth = 'LegendTextWidth', LegendBorder = 'LegendBorder', LegendSVG = 'LegendSVG',

FontSize = 'FontSize', LabelsFontSize = 'LabelsFontSize', TitleFontSize = 'TitleFontSize', FootnoteFontSize = 'FootnoteFontSize', LegendFontSize = 'LegendFontSize', XAxisTitleFontSize = 'XAxisTitleFontSize', YAxisTitleFontSize = 'YAxisTitleFontSize', YAxis2TitleFontSize = 'YAxis2TitleFontSize', XAxisValuesFontSize = 'XAxisValuesFontSize', YAxisValuesFontSize = 'YAxisValuesFontSize', YAxis2ValuesFontSize = 'YAxis2ValuesFontSize', ChartTextFontSize = 'ChartTextFontSize',

GraphLineWidth = 'GraphLineWidth',

BarWidth = 'BarWidth', BarSpace = 'BarSpace', PieRadius = 'PieRadius', Explode = 'Explode', ExplodeRadius = 'ExplodeRadius', DoughnutHole = 'DoughnutHole', PieStartAngle = 'PieStartAngle', PieSweepDir = 'PieSweepDir',

SegmentText = 'SegmentText', SegmentTextWidth = 'SegmentTextWidth', SegmentTextRadius = 'SegmentTextRadius',

BorderColor = 'BorderColor', BorderWidth = 'BorderWidth',

Series = 'Series', Segment = 'Segment',

Type = 'Type', Values = 'Values', YAxis2 = 'YAxis2', Labels = 'Labels', Color = 'Color', Line = 'Line', Width = 'Width', Dash = 'Dash', Marker = 'Marker', MarkerSize = 'MarkerSize', MarkerFill = 'MarkerFill', Pattern = 'Pattern', PatternColor = 'PatternColor',

Text = 'Text',

Group = 'Group', ChartText = 'ChartText',

IncludeOriginalData = 'IncludeOriginalData',

Debug = 'Debug',

-- these are values that some parameters recognise, and can be translated none = 'none', -- series types bar = 'bar', line = 'line', -- legend types vertical = 'vertical', horizontal = 'horizontal', -- pie segment texts text = 'text', value = 'value', percent = 'percent', -- original data auto = 'auto', -- debug parms = 'parms', --   yes = 'yes', no = 'no',

-- these are special values that make the code simpler if they exist in the KeyWords table, -- but they *must not* be translated X = 'X', Y = 'Y',

}

-- To translate messages, translate the values in the MessageTexts table. Do not translate the keys. local MessageTexts = { NotNumeric = "%s, value \"%s\" is not numeric.", BelowChartMinSize = "%s, value \"%d\" is below the minimum chart size of %d.", NotInGroup = "Series%dValues, pair %d: X value \"%s\" is not for a defined group.", NotInMarkers = "Series%dMarker, value \"%s\" is not in the markers table.", NotInDashPatterns = "Series%dDash, value \"%s\" is not in the dash patterns table.", NotInFillPatterns = "Series%dPattern, value \"%s\" is not in the fill patterns table.", NoYAxis2 = "Series%dYAxis2 is set, but no 2nd Y axis is shown - which may be because Stack or Stack100 are set.", }

local DefColor = { 'rgb(0, 0, 255)',    --  1 = blue 'rgb(0, 192, 0)',    --  2 = mid green 'rgb(255, 255, 0)',  --  3 = yellow 'rgb(255, 0, 0)',    --  4 = red 'rgb(192, 192, 0)',  --  5 = light olive 'rgb(255, 0, 255)',  --  6 = magenta 'rgb(0, 255, 0)',    --  7 = lime 'rgb(128, 128, 128)', -- 8 = grey 'rgb(0, 255, 255)',  --  9 = cyan 'rgb(255, 165, 0)',  -- 10 = orange 'rgb(0, 0, 192)',    -- 11 = light navy 'rgb(128, 255, 128)', -- 12 = light green 'rgb(255, 255, 128)', -- 13 = sand yellow 'rgb(192, 0, 0)',    -- 14 = red brown 'rgb(240, 240, 240)', -- 15 = pale grey 'rgb(255, 128, 255)', -- 16 = light magenta 'rgb(192, 255, 192)', -- 17 = pale green 'rgb(192, 0, 192)',  -- 18 = dark mauve 'rgb(192, 192, 255)', -- 19 = blue-grey 'rgb(0, 192, 192)',  -- 20 = dark cyan 'rgb(192, 192, 192)', -- 21 = light grey 'rgb(128, 128, 255)', -- 22 = dark blue-grey 'rgb(0, 128, 0)',    -- 23 = green 'rgb(255, 255, 192)', -- 24 = light sand 'rgb(255, 192, 192)', -- 25 = pale red 'rgb(128, 128, 0)',  -- 26 = olive 'rgb(255, 192, 255)', -- 27 = pale magenta 'rgb(255, 96, 0)',   -- 28 = dark orange 'rgb(192, 255, 255)', -- 29 = light cyan 'rgb(255, 128, 128)', -- 30 = light red }

-- grayscale equivalents of the above colours local GrayColor = { 'rgb(18, 18, 18)',   --  1 = blue-g 'rgb(137, 137, 137)', -- 2 = midgreen-g 'rgb(237, 237, 237)', -- 3 = yellow-g 'rgb(54, 54, 54)',   --  4 = red-g 'rgb(178, 178, 178)', -- 5 = lightolive-g 'rgb(73, 73, 73)',   --  6 = magenta-g 'rgb(182, 182 ,182)', -- 7 = lime-g 'rgb(128, 128, 128)', -- 8 = grey-g 'rgb(201, 201 ,201)', -- 9 = cyan-g 'rgb(172, 172 172)', -- 10 = orange-g 'rgb(14, 14 ,14)',   -- 11 = lightnavy-g 'rgb(219, 219, 219)', -- 12 = lightgreen-g 'rgb(246, 246, 246)', -- 13 = sandyellow-g 'rgb(41, 41, 41)',   -- 14 = redbrown-g 'rgb(240, 240, 240)', -- 15 = palegrey-g 'rgb(164, 164, 164)', -- 16 = lightmagenta-g 'rgb(237, 237, 237)', -- 17 = palegreen-g 'rgb(55, 55, 55)',   -- 18 = darkmauve-g 'rgb(197, 197, 197)', -- 19 = bluegrey-g 'rgb(151, 151, 151)', -- 20 = darkcyan-g 'rgb(192, 192, 192)', -- 21 = lightgrey-g 'rgb(137, 137, 137)', -- 22 = darkbluegrey-g 'rgb(92, 92, 92)',   -- 23 = green-g 'rgb(250, 250, 250)', -- 24 = lightsand-g 'rgb(205, 205, 205)', -- 25 = palered-g 'rgb(119, 119, 119)', -- 26 = olive-g 'rgb(210, 210, 210)', -- 27 = palemagenta-g 'rgb(123, 123, 123)', -- 28 = darkorange-g 'rgb(242, 242, 242)', -- 29 = lightcyan-g 'rgb(155, 155, 155)' -- 30 = lightred-g }

local DashPattern = { "8,8",           -- 1 = dashed, long "4,4",           -- 2 = dashed, short "8,2",           -- 3 = broken, long "4,2",           -- 4 = broken, short "2,2",           -- 5 = dots "6,2,2,2",       -- 6 = dash-dot "6,2,2,2,2,2",   -- 7 = dash-dot-dot "6,2,2,2,2,2,2,2" -- 8 = dash-dot-dot-dot }

--[[ The differences between the defaults for the 4 'graph' chart methods are:                 barChart     lineChart     scatterChart     mixedChart series type       bar*         line*         line*            must be specified line visibility   -*           yes           none             yes marker            -*           nil           series no.       nil

group            allowed      allowed       -*               allowed

area             -*           nil           -*               -* stack            nil          nil           -*               -* stack100         nil          nil           -*               -* (* = cannot be changed by parameters) ]]

function barChart(frame)

Args = frame.args

getAllParms

DoStack = (Parms["Stack"] ~= nil) DoStack100 = (Parms["Stack100"] ~= nil) DoYAxis2 = (Parms["Y2Max"] ~= 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)

-- 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)

-- 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)

-- 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

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

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 have a default value, ie: it is valid for some parameters to be nil. These are genarally 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 original X position Parms["TitleY"] = getParm("TitleY", nil, nil, "n") -- switch to move title from original Y position

-- footnote

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

-- 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["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["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["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["XAxisMark2Step"] = getParm("XAxisMark2Step", nil, nil, "n") -- switch for showing x-axis secondary marks

-- YAxis (Left)

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["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["YAxisMark2Step"] = getParm("YAxisMark2Step", nil, nil, "n") -- switch for showing y-axis secondary marks

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

-- YAxis2 (Right)

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 the second Y axis

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["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["YAxis2Mark2Step"] = getParm("YAxis2Mark2Step", nil, nil, "n") -- switch for showing y-axis2 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, and for not adding space for it to right space Parms["LegendY"] = getParm("LegendY", nil, nil, "n") -- switch for moving the legend from the default position 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["ChartTextFontSize"])

-- 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 -- if Explode is a number, ensure it is of numeric type Parms["Explode"] = tonumber(Parms["Explode"]) -- default explode is 20% Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 20) else -- any other value = all, default explode radius is 10% Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 10) end end

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 -- 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 a named argument (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 result end

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   end end

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 end end

function calcStack -- calculate stacked totals for series

-- the current accumlated total overwrites the SeriesData Y value

local Total = {}

for j = 1, #GroupText do       Total[j] = 0 local PosTotal, NegTotal = 0, 0 for i = 1, #SeriesData do           if SeriesData[i][j] ~= nil and tonumber(SeriesData[i][j][1]) == j then Total[j] = Total[j] + SeriesData[i][j][2] if tonumber(SeriesData[i][j][2]) < 0 then NegTotal = NegTotal + SeriesData[i][j][2] SeriesData[i][j][2] = NegTotal else PosTotal = PosTotal + SeriesData[i][j][2] SeriesData[i][j][2] = PosTotal 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] / Total[j] * 100 end end end end end

function checkParms -- check for parameter issues

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, " ===== Charts SVG messages =====") for i = 1, math.min(#Msgs, 10) do           table.insert(r, "   " .. Msgs[i]) end return false end

return true end

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.Labels = 5 * BaseFontSize * Parms["LabelsFontSize"] / 100

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

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

FontSiz.ChartText = 5 * BaseFontSize * Parms["ChartTextFontSize"] / 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.ChartPadding = 2 * BaseUnit

Siz.Text.Interline = 2 * BaseFontSize

Siz.Text.Labels = FontSiz.Labels

Siz.Text.Title = iif(Parms["Title"] ~= nil and Parms["TitleY"] == nil, FontSiz.Title + Siz.Text.Interline, 0)

Siz.Footnote = iif(Parms["Footnote"] ~= nil and Parms["FootnoteY"] == 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.ChartText

-- chart size

ChartWidth = Parms["ChartWidth"] ChartHeight = Parms["ChartHeight"]

-- legend height and width

Siz.Legend.ElementWidth = Siz.ChartPadding + (2 * Siz.Legend.Text) + Siz.ChartPadding + Siz.Legend.TextWidth Siz.Legend.ElementHeight = Siz.Legend.Text + Siz.Text.Interline local LegendElementsInWidth = 1 local LegendLines = #SeriesText if Parms["LegendType"] == KeyWords["horizontal"] then LegendElementsInWidth = math.floor(ChartWidth / Siz.Legend.ElementWidth) LegendElementsInWidth = math.min(LegendElementsInWidth, #SeriesText) LegendLines = math.ceil(#SeriesText / LegendElementsInWidth)

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

end

function prelimsAxes

Pos.XAxis = {} Pos.YAxis = {} Pos.YAxis2 = {}

Siz.Space = {} Siz.XAxis = {} Siz.YAxis = {} Siz.YAxis2 = {}

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 = ChartWidth / #GroupText if DoStack or DoStack100 then UnitWidth = round(GroupWidth / 2, 0) else UnitWidth = round(GroupWidth / (BarSeriesCount + 1), 0) end BarWidth = round(UnitWidth / (1 + BarSpace), 0) BarSpace = round(UnitWidth - BarWidth, 0) Mult.X = ChartWidth / #GroupText else GroupWidth = ChartWidth / #GroupText Mult.X = GroupWidth end else Mult.X = ChartWidth / (math.abs(Parms["XMax"] - Parms["XMin"])) end

Mult.Y = ChartHeight / math.abs(Parms["YMax"] - Parms["YMin"])

if DoYAxis2 then Mult.Y2 = ChartHeight / math.abs(Parms["Y2Max"] - Parms["Y2Min"]) else Mult.Y2 = 1 end

--

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

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

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

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

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

Siz.YAxis.Values = (2 * FontSiz.YAxisValues) + Siz.Text.Interline Siz.YAxis.Title = iif(Parms["YAxisTitle"] ~= nil, FontSiz.YAxisTitle + Siz.Text.Interline, 0)

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

-- Bottom Space if Parms["YMin"] < 0 then -- X axis line is within chart Pos.XAxis.Line = Siz.Space.Top + ChartHeight - round(((0 - Parms["YMin"]) * Mult.Y), 1) Pos.XAxis.Marks = Pos.XAxis.Line Pos.XAxis.Values = Pos.XAxis.Line + Siz.AxisMark + FontSiz.XAxisValues Siz.Space.Bottom = Siz.ChartPadding else -- X axis line is at bottom of chart Pos.XAxis.Line = Siz.Space.Top + ChartHeight Pos.XAxis.Marks = Pos.XAxis.Line Siz.Space.Bottom = iif(#GroupText > 0, 0, Siz.AxisMark) Pos.XAxis.Values = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + Siz.ChartPadding + FontSiz.XAxisValues Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartPadding + Siz.XAxis.Values end if Parms["XAxisTitle"] ~= nil then Pos.XAxis.Title = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + FontSiz.XAxisTitle Siz.Space.Bottom = Siz.Space.Bottom + Siz.XAxis.Title end if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then Pos.Legend = Siz.Space.Top + 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 + 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 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.ChartPadding Pos.YAxis.Line = Siz.Space.Left + round(((0 - Parms["XMin"]) * Mult.X), 1) Pos.YAxis.Marks = Pos.YAxis.Line - Siz.AxisMark -- pos is left edge of marks box Pos.YAxis.Values = Pos.YAxis.Marks - Siz.ChartPadding -- 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.ChartPadding + Siz.AxisMark Pos.YAxis.Line = Siz.Space.Left Pos.YAxis.Marks = Pos.YAxis.Line - Siz.AxisMark -- pos is left edge of marks box Pos.YAxis.Values = Pos.YAxis.Marks - Siz.ChartPadding -- pos is right edge of values box end

-- Right Space Pos.YAxis2.Line = Siz.Space.Left + ChartWidth Pos.YAxis2.Marks = Pos.YAxis2.Line Siz.Space.Right = Siz.AxisMark Pos.YAxis2.Values = Pos.YAxis2.Line + Siz.Space.Right + Siz.ChartPadding -- pos is left edge of values box Siz.Space.Right = Siz.Space.Right + Siz.ChartPadding + Siz.YAxis2.Values Pos.YAxis2.Title = Siz.Space.Left + ChartWidth + Siz.Space.Right + FontSiz.YAxis2Title Siz.Space.Right = Siz.Space.Right + Siz.YAxis2.Title if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then Pos.Legend = Siz.Space.Left + 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

Pos.XAxis.Zero = Siz.Space.Left - (iif(#GroupText == 0, Parms["XMin"], 0) * Mult.X)   Pos.YAxis.Zero = Siz.Space.Top + ChartHeight + (Parms["YMin"] * Mult.Y)    Pos.YAxis2.Zero = Siz.Space.Top + ChartHeight + (Parms["Y2Min"] * Mult.Y2)

-- Image size

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

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 = iif(Parms["Explode"] ~= nil, (PieRadius * Parms["ExplodeRadius"] / 100), 0) + (PieRadius * Parms["SegmentTextRadius"] / 100) + (5 * Siz.Text.Chart * Parms["SegmentTextWidth"] / 100) PieOriginX = math.max(PieOriginX, TextSpaceX)

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

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

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

-- Bottom Space Siz.Space.Bottom = Siz.ChartPadding

if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then Pos.Legend = Siz.Space.Top + 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 + 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.ChartPadding

-- Right Space Siz.Space.Right = Siz.ChartPadding if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then Pos.Legend = Siz.Space.Left + 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 + ChartWidth + Siz.Space.Right, 0) ImageHeight = round(Siz.Space.Top + ChartHeight + Siz.Space.Bottom, 0)

-- Mult.X and Mult.Y are needed for positioning of Title, Footnote and Chart texts

Mult.X = round(ChartWidth / 100, 2) Mult.Y = round(ChartHeight / 100, 2) end

function commonTop

-- output header

table.insert(r, "Select and copy the following text. Paste it into a plain text file. The text file should have an svg extension, for example mychart.svg.") table.insert(r, " ")

-- SVG header stuff

table.insert(r, " &lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?>") table.insert(r, " &lt;!-- Generator: en.wikipedia.org/wiki/Module:Charts SVG -->") table.insert(r, " &lt;svg id=\"head\"") table.insert(r, "  xmlns=\"http://www.w3.org/2000/svg\"") table.insert(r, "  xmlns:xlink=\"http://www.w3.org/1999/xlink\"") table.insert(r, "  version=\"1.1\"") table.insert(r, "  font-family=\"Liberation Sans, Arial, sans-serif\"") table.insert(r, "  width=\"" .. ImageWidth .. "\"") table.insert(r, "  height=\"" .. ImageHeight .. "\"") 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, " ")

table.insert(r, " &lt;!-- == Backgrounds == -->") table.insert(r, " ")

-- image background rectangle table.insert(r, " &lt;!-- image background -->") table.insert(r, " ") table.insert(r, " ")

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

-- chart background

if Parms["ChartBackgroundColor"] ~= nil then table.insert(r, " &lt;!-- chart background -->") table.insert(r, " ") table.insert(r, " ") end end

function codeAxisGrids

local XAxisGridStepStretch if #GroupText == 0 and tonumber(Parms["XGrid"]) ~= nil then XAxisGridStepStretch = round(Parms["XGrid"] * Mult.X, 2) end

local YAxisGridStepStretch if tonumber(Parms["YGrid"]) ~= nil then YAxisGridStepStretch = round(Parms["YGrid"] * Mult.Y, 2) end

if Parms["XGrid"] ~= KeyWords["none"] or Parms["YGrid"] ~= KeyWords["none"] then table.insert(r, " &lt;!-- == Axis Grids == -->") table.insert(r, " ")

table.insert(r, "  <![CDATA[")

table.insert(r, "  .gridline {") table.insert(r, "    stroke:       lightgrey;") table.insert(r, "    stroke-width: " .. BaseLineWidth .. ";") table.insert(r, "  }")

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

table.insert(r, " ")

if #GroupText == 0 and Parms["XGrid"] ~= KeyWords["none"] then table.insert(r, "  &lt;!-- x-axis grid lines, vertical -->") table.insert(r, "  ") table.insert(r, "    ") table.insert(r, "  ") end

if Parms["YGrid"] ~= KeyWords["none"] then table.insert(r, "  &lt;!-- y-axis grid lines, horizontal -->") table.insert(r, "  ") table.insert(r, "    ") table.insert(r, "  ") end table.insert(r, " ") table.insert(r, " ")

if #GroupText == 0 and Parms["XGrid"] ~= KeyWords["none"] then table.insert(r, " ") end

if Parms["YGrid"] ~= KeyWords["none"] then table.insert(r, " ") end table.insert(r, " ") end end

function stylesAreas

table.insert(r, " &lt;!-- == Graph Area Styles == -->") table.insert(r, " ")

table.insert(r, "  <![CDATA[")

table.insert(r, "  /*-- general style of areas --*/") table.insert(r, "  .series-areas-general {") if Parms["BorderColor"] ~= nil then table.insert(r, "    stroke:       " .. Parms["BorderColor"] .. ";") table.insert(r, "    stroke-width: " .. BaseLineWidth * Parms["BorderWidth"] / 100 .. ";") else table.insert(r, "    stroke-width: " .. 0 .. ";") end table.insert(r, "  }")

if DoPie then for k, v in ipairs(SeriesText) do           codeStyleArea(k, Color[k], FillPattern[k]) end else for i = 1, #SeriesData do           if SeriesData[i] ~= nil and (SType[i] == KeyWords["bar"] or DoArea) then codeStyleArea(i, Color[i], FillPattern[i]) end end end

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 .. " {") -- fill for the area if Pattern == nil or Pattern == KeyWords["none"] then table.insert(r, "    fill:   " .. Color .. ";") else table.insert(r, "    fill:   url(#series" .. SeriesNumber .. "pattern);") end table.insert(r, "  }") 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, " &lt;!-- == 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, " ") end end

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, "  &lt;!-- 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 = "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("        if PatternType == 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 = "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("        if PatternType == 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, "    <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <line x1=\"0\" y1=\"8\" x2=\"12\" y2=\"8\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>") table.insert(r, "  ") elseif PatternType >= 21 and PatternType <= 24 then -- cross hatches, close l = "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("        if PatternType == 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, "    <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <line x1=\"0\" y1=\"4\" x2=\"6\" y2=\"4\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>") table.insert(r, "    <line x1=\"4\" y1=\"0\" x2=\"4\" y2=\"6\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>") table.insert(r, "  ") elseif PatternType >= 31 and PatternType <= 34 then -- cross hatches, wide l = "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("        if PatternType == 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, "    <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <line x1=\"0\" y1=\"2\" x2=\"12\" y2=\"2\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>") table.insert(r, "    <line x1=\"4\" y1=\"0\" x2=\"4\" y2=\"12\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>") table.insert(r, "  ") elseif PatternType >= 41 and PatternType <= 49 then -- stipples if PatternType == 41 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"6\" y=\"6\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 42 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"4\" y=\"4\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 43 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"3\" y=\"3\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 44 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"4\" height=\"4\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"2\" y=\"2\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 45 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"6\" y=\"6\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 46 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"4\" y=\"4\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 47 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"3\" y=\"3\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 48 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"4\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"8\" y=\"12\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 49 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"6\" y=\"6\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") end table.insert(r, l .. "  ") elseif PatternType >= 51 and PatternType <= 56 then -- checks if PatternType == 51 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"4\" height=\"4\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "     <rect x=\"2\" y=\"2\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 52 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"3\" height=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"3\" y=\"3\" width=\"3\" height=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 53 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "     <rect x=\"4\" y=\"4\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 54 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"10\" height=\"10\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"5\" height=\"5\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"5\" y=\"5\" width=\"5\" height=\"5\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 55 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"6\" y=\"6\" width=\"6\" height=\"6\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 56 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <rect x=\"8\" y=\"8\" width=\"8\" height=\"8\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") end table.insert(r, "  ") elseif PatternType >= 61 and PatternType <= 64 then -- circles if PatternType == 61 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <circle cx=\"4\" cy=\"4\" r=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "     <circle cx=\"12\" cy=\"12\" r=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 62 then table.insert(r, "   <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "     <rect x=\"0\" y=\"0\" width=\"20\" height=\"20\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "     <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") table.insert(r, "    <circle cx=\"15\" cy=\"15\" r=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>") elseif PatternType == 63 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <circle cx=\"4\" cy=\"4\" r=\"3\" fill=\"none\" stroke=\"" .. PatternColor .. "\"/>") table.insert(r, "    <circle cx=\"12\" cy=\"12\" r=\"3\" fill=\"none\" stroke=\"" .. PatternColor .. "\"/>") elseif PatternType == 64 then table.insert(r, "  <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "     <rect x=\"0\" y=\"0\" width=\"20\" height=\"20\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>") table.insert(r, "    <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"none\" stroke=\"" .. PatternColor .. "\" stroke-width=\"1\"/>") table.insert(r, "     <circle cx=\"15\" cy=\"15\" r=\"4\" fill=\"none\" stroke=\"" .. PatternColor .. "\" stroke-width=\"1\"/>") end table.insert(r, "  ") end end

function stylesLines

table.insert(r, " &lt;!-- == Graph Line Styles == -->") table.insert(r, " ")

table.insert(r, " <style type=\"text/css\"> <![CDATA[")

table.insert(r, "  /*-- general style of graph lines --*/") table.insert(r, "  .series-lines-general {") table.insert(r, "    stroke-width:    " .. BaseLineWidth * Parms["GraphLineWidth"] / 100 .. ";") table.insert(r, "    stroke-linejoin: round;") table.insert(r, "    stroke-linecap:  round;") table.insert(r, "    fill:            none;") table.insert(r, "  }")

if #Marker > 0 then table.insert(r, "  /*-- general style of markers --*/") table.insert(r, "  .graph-marker {") table.insert(r, "    stroke-width:    " .. BaseLineWidth .. ";") table.insert(r, "    fill:            white;") table.insert(r, "    stroke-linejoin: round;") table.insert(r, "  }") end

for i = 1, #SeriesData do       if SeriesData[i] ~= nil and SType[i] == KeyWords["line"] then local lw = 0 if LineWidth[i] ~= nil then lw = BaseLineWidth * LineWidth[i] / 100 end codeStyleLineMarker(i, LineShow[i], Color[i], lw, LineDash[i], Marker[i], MarkerFill[i]) end end

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 .. " {") -- stroke for the line if LineRequired == KeyWords["none"] then table.insert(r, "    stroke:           none;") else table.insert(r, "    stroke:           " .. LineColor .. ";") table.insert(r, "    stroke-width:     " .. LineWidth .. ";") -- dash array for the line if LineDash ~= nil and LineDash ~= KeyWords["none"] then table.insert(r, "    stroke-dasharray: " .. LineDash .. ";") table.insert(r, "    stroke-linecap:   butt;") end end if MarkerRequired == nil or MarkerRequired == KeyWords["none"] then close the line style table.insert(r, "  }") 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 {") -- marker stroke color is always the same as the line table.insert(r, "    stroke: " .. LineColor .. ";") table.insert(r, "    fill:   " .. MarkerFill .. ";") table.insert(r, "  }") end end end

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, " &lt;!-- == Graph Markers == -->") table.insert(r, " ") table.insert(r, " ") table.insert(r, "  <g class=\"graph-marker\">") 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, "  </g>") table.insert(r, " ") table.insert(r, " ") end end

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, "    <g id=\"series" .. SeriesNumber .. "markershape\">") 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, "      &lt;!-- circle -->") l = "      <circle cx=\"0\" dx=\"0\" r=\"" .. round(MarkerSize / 2, 2) .. "\"" if MarkerFill ~= nil then l = l .. " fill=\"" .. MarkerFill .. "\"" end table.insert(r, l .. "/>") elseif MarkerType == 3 then -- triangle (point up) t = round(MarkerSize / 2, 2) table.insert(r, "      &lt;!-- triangle, point up -->") l = "      <polygon points=\"" .. -t .. "," .. t .. " " .. t .. "," .. t .. ", " .. 0 .. "," .. -t .."\"" if MarkerFill ~= nil then l = l .. " fill=\"" .. MarkerFill .. "\"" end table.insert(r, l .. "/>") elseif MarkerType == 4 then -- tilted square (diamond) table.insert(r, "      &lt;!-- diamond -->") l = "      <rect transform=\"rotate(45)\"" .. " x=\"" .. round(-MarkerSize / 2, 2) .. "\"" .. " y=\"" .. round(-MarkerSize / 2, 2) .. "\"" .. " width=\"" .. round(MarkerSize, 2) .. "\"" .. " height=\"" .. round(MarkerSize, 2) .. "\"" if MarkerFill ~= nil then l = l .. " fill=\"" .. MarkerFill .. "\"" end table.insert(r, l .. "/>") elseif MarkerType == 5 then -- tilted triangle (point down) t = round(MarkerSize / 2, 2) table.insert(r, "      &lt;!-- triangle, point down -->") l = "      <polygon points=\"" .. 0 .. "," .. t .. " " .. -t .. "," .. -t .. ", " .. t .. "," .. -t .. "\"" if MarkerFill ~= nil then l = l .. " fill=\"" .. MarkerFill .. "\"" end table.insert(r, l .. "/>") elseif MarkerType == 6 then -- cross t = round(MarkerSize / 2, 2) table.insert(r, "      &lt;!-- cross -->") table.insert(r, "      <path d=\"M " .. -t .. "," .. t .. " L " .. t .. "," .. -t .. " z M " .. t .. "," .. t .. " L " .. -t .. "," .. -t .. " z\"/>") elseif MarkerType == 7 then -- plus t = round(MarkerSize / 2, 2) table.insert(r, "      &lt;!-- plus -->") table.insert(r, "      <path d=\"M " .. 0 .. "," .. t .. " L " .. 0 .. "," .. -t .. " z M " .. -t .. "," .. 0 .. " L " .. t .. "," .. 0 .. " z\"/>") else -- default and 1 -- square table.insert(r, "      &lt;!-- square -->") l = "      <rect x=\"" .. round(-MarkerSize / 2, 2) .. "\"" .. " y=\"" .. round(-MarkerSize / 2, 2) .. "\"" .. " width=\"" .. round(MarkerSize, 2) .. "\"" .. " height=\"" .. round(MarkerSize, 2) .. "\"" if MarkerFill ~= nil then l = l .. " fill=\"" .. MarkerFill .. "\"" end table.insert(r, l .. "/>") end table.insert(r, "    </g>") end

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

-- SeriesNumber, numeric -- MarkerSize, numeric

local l = ""

l = "    <marker id=\"series" .. SeriesNumber .. "marker\"" l = l .. " class=\"series" .. SeriesNumber .. "-marker\"" l = l .. " viewBox=\"0 0 " .. round(MarkerSize, 2) .. " " .. round(MarkerSize, 2) .. "\"" .. " markerWidth=\"" .. round(MarkerSize, 2) .. "\"" .. " markerHeight=\"" .. round(MarkerSize, 2) .. "\"" .. " overflow=\"visible\"" .. " markerUnits=\"userSpaceOnUse\">" table.insert(r, l)   table.insert(r, "       <use xlink:href=\"#series" .. SeriesNumber .. "markershape\"/>") table.insert(r, "    ") end

function elementsGraphs

local BarNumber = BarSeriesCount + 1 local LabelPos = {}

table.insert(r, " &lt;!-- == Graph Bars and Lines == -->") table.insert(r, " ")

table.insert(r, " <g id=\"graphs\" transform=\"translate(" .. Siz.Space.Left .. ", " .. Siz.Space.Top + ChartHeight .. ")\">") for i = #SeriesData, 1, -1 do       if SeriesText[i]  == nil then table.insert(r, "  &lt;!-- " .. "Series" .. i .. " -->") else table.insert(r, "  &lt;!-- " .. SeriesText[i] .. " -->") end if DoYAxis2 and YAxis2[i] ~= nil then table.insert(r, "  &lt;!-- Y values are on right Y axis -->") end if Parms["IncludeOriginalData"] == KeyWords["no"] or (Parms["IncludeOriginalData"] == KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then table.insert(r, "  &lt;!-- original data: not included -->") else table.insert(r, "  &lt;!-- 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 = round((px - 1) * GroupWidth                               + UnitWidth / 2                                + (BarSpace / 2), 1) else px = round((px - 1) * GroupWidth                               + UnitWidth / 2                                + UnitWidth * (BarNumber - 1)                                + (BarSpace / 2), 1) end else px = round(((px - Parms["XMin"]) * Mult.X) - (BarWidth / 2), 1) end local val = tonumber(v[2]) local top, ht = 0, 0 local Min, Multiply = Parms["YMin"], Mult.Y                   if DoYAxis2 and YAxis2[i] ~= nil then Min = Parms["Y2Min"] Multiply = Mult.Y2                   end if Min >= 0 then top = Min - val ht = val - Min elseif val >= 0 then top = Min - val ht = val else top = Min ht = -val end top = round(top * Multiply, 1) ht = round(ht * Multiply, 1) table.insert(r, "  <rect id=\"series" .. i .. "-" .. k .. "\" class=\"series-areas-general series" .. i .. "\""                        .. " x=\"" .. px .. "\" y=\"" .. top .. "\""                        .. " width=\"" .. BarWidth .. "\" height=\"" .. ht .. "\" />") if Labels[i] ~= nil then LabelPos[i][k] = {} LabelPos[i][k]["x"] = px + (BarWidth / 2) if val <= 0 then LabelPos[i][k]["y"] = top + ht + Siz.Text.Interline + Siz.Text.Labels else LabelPos[i][k]["y"] = top - Siz.Text.Interline end end end end else -- line tr = "  <polyline id=\"graph" .. i .. "\" class=\"series-"            if DoArea then                tr = tr .. "areas"            else                tr = tr .. "lines"            end            tr = tr .. "-general series" .. i .. "\"" table.insert(r, tr) if Marker[i] ~= nil and Marker[i] ~= KeyWords["none"] then -- set the line to have markers table.insert(r, "    marker-start=\"url(#series" .. i .. "marker)\" marker-mid=\"url(#series" .. i .. "marker)\" marker-end=\"url(#series" .. i .. "marker)\"") end table.insert(r, "    points=\"")            local lastx = 0            -- multiply numeric values as necessary            for k, v in ipairs(SeriesData[i]) do                local px = tonumber(v[1])                if #GroupText > 0 then                    px = round((px - 1) * GroupWidth                        + (GroupWidth / 2), 1)                else                    px = round((px - Parms["XMin"]) * Mult.X, 1)                end

local py = tonumber(v[2]) if DoYAxis2 and YAxis2[i] ~= nil then py = round((Parms["Y2Min"] - py) * Mult.Y2, 1) else py = round((Parms["YMin"] - py) * Mult.Y, 1) end

if DoArea and k == 1 then table.insert(r, "    " .. px .. ", " .. iif(Parms["YMin"] < 0, Parms["YMin"] * Mult.Y, 0) .. "") end table.insert(r, "    " .. px .. ", " .. py .. "") 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 end end if DoArea then table.insert(r, "    " .. lastx .. ", " .. iif(Parms["YMin"] < 0, Parms["YMin"] * Mult.Y, 0) .. "") end table.insert(r, "  \"/>")        end        table.insert(r, " ")    end

if not DoStack100 and #Labels > 0 then table.insert(r, "  &lt;!-- == Data Labels == -->") table.insert(r, " ")

table.insert(r, "  <style type=\"text/css\"> <![CDATA[")

table.insert(r, "    .labeltext {") table.insert(r, "      font-size:   " .. Siz.Text.Labels .. "px;") table.insert(r, "    }")

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 table.insert(r, "  &lt;text id=\"series" .. i .. "-" .. k .. "-label\" class=\"labelstext\""                            .. " x=\"" .. LabelPos[i][k]["x"] .. "\""                            .. " y=\"" .. LabelPos[i][k]["y"] .. "\""                            .. " text-anchor=\"middle\">"                            .. v[2]                            .. " ") else table.insert(r, "  &lt;text id=\"series" .. i .. "-" .. k .. "-label\" class=\"labelstext\""                            .. " x=\"" .. LabelPos[i][k]["x"] .. "\""                            .. " y=\"" .. LabelPos[i][k]["y"] .. "\""                            .. " text-anchor=\"left\">"                            .. v[2]                            .. " ") end end end end end

table.insert(r, " </g>") table.insert(r, " ") end

function elementsPie

-- pie segments

table.insert(r, " &lt;!-- == Pie Segments == -->") table.insert(r, " ")

table.insert(r, " <g id=\"segments\" class=\"series-areas-general\" transform=\"translate(" .. Siz.Space.Left + PieOriginX .. ", " .. Siz.Space.Top + PieOriginY .. ")\">")

if Parms["IncludeOriginalData"] == KeyWords["no"] or (Parms["IncludeOriginalData"] == KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then table.insert(r, "  &lt;!-- original data: not included -->") else table.insert(r, "  &lt;!-- 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 = round(math.cos(Radian) * InnerRadius, 1) InnerY = round(math.sin(Radian) * InnerRadius, 1) end

-- outer arcs start at the pie radius local OuterX = round(math.cos(Radian) * PieRadius, 1) local OuterY = round(math.sin(Radian) * PieRadius, 1) 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 = round(math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1) DY = round(math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1) end

Radian = Radian + (v[2] / Total * TwoPi * iif(Sweep == 0, 1, -1)) local NextOuterX = round(math.cos(Radian) * PieRadius, 1) local NextOuterY = round(math.sin(Radian) * PieRadius, 1)

t = "  &lt;path id=\"segment" .. k .. "\" class=\"series" .. k .. "\" d=\"M " .. (0 + InnerX + DX) .. ", " .. -(0 + InnerY + DY)            .. " l " .. (OuterX - InnerX) .. ", " .. -(OuterY - InnerY)            .. " a " .. PieRadius .. ", " .. PieRadius .. " 0 " .. Arc .. " " .. Sweep  .. " " .. (NextOuterX - OuterX) .. ", " .. -(NextOuterY - OuterY)        if Parms["DoughnutHole"] == nil then            t = t .. " z\" />" else local NextInnerX = round(math.cos(Radian) * InnerRadius, 1) local NextInnerY = round(math.sin(Radian) * InnerRadius, 1)

t = t .. " l " .. (NextInnerX - NextOuterX).. ", " .. -(NextInnerY - NextOuterY) .. " a " .. InnerRadius .. ", " .. InnerRadius .. " 0 " .. Arc .. " " .. iif(Sweep == 0, 1, 0) .. " " .. (InnerX - NextInnerX) .. ", " .. -(InnerY - NextInnerY) .. "\" />"

InnerX = NextInnerX InnerY = NextInnerY end

table.insert(r, t)

OuterX = NextOuterX OuterY = NextOuterY end

table.insert(r, " </g>") table.insert(r, " ")

if Parms["SegmentText"] ~= nil then

-- pie segment texts

table.insert(r, " &lt;!-- == Pie Segment Texts == -->") table.insert(r, " ")

table.insert(r, " <style type=\"text/css\"> <![CDATA[")

table.insert(r, "  .segmenttext {") table.insert(r, "    font-size:   " .. FontSiz.LegendText .. "px;") table.insert(r, "  }")

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

table.insert(r, " <g id=\"segmenttexts\" class=\"segmenttext\" transform=\"translate(" .. Siz.Space.Left + PieOriginX .. ", " .. Siz.Space.Top + PieOriginY .. ")\">")

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 = round(math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1) DY = round(math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1) end

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

t = "  &lt;text id=\"segment" .. k .. "text\"" .. " x=\"" .. (TextX + DX) .. "\" y=\"" .. -(TextY + DY) .. "\"" t = t .. " text-anchor=\"" .. iif(TextX < 0, "end", "start") .. "\">"

local tt = "" if string.find(Parms["SegmentText"], KeyWords["text"], 1, true) > 0 then tt = SeriesText[k] end if string.find(Parms["SegmentText"], KeyWords["value"], 1, true) > 0 then tt = tt .. iif(string.len(tt) > 0, " ", "") .. v[2] end if string.find(Parms["SegmentText"], KeyWords["percent"], 1, true) > 0 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, " </g>") table.insert(r, " ") end end

function codeAxes

--[=[ to display positioning tables table.insert(r, "&lt;!--") listTable(Mult, " ", "Mult") listTable(FontSiz, " ", "FontSiz") listTable(Pos, " ", "Pos") listTable(Siz, " ", "Siz") table.insert(r, "-->") ]=]   -- axis styles

table.insert(r, " &lt;!-- == Axis Styles == -->") table.insert(r, " ")

table.insert(r, " <style type=\"text/css\"> <![CDATA[")

table.insert(r, "  .axisline-x {") table.insert(r, "    stroke:         black;") table.insert(r, "    stroke-width:   " .. BaseLineWidth * 2 .. ";") table.insert(r, "    stroke-linecap: butt;") table.insert(r, "  }")

table.insert(r, "  .axisline-y {") table.insert(r, "    stroke:         " .. Parms["YAxisColor"] .. ";") table.insert(r, "    stroke-width:   " .. BaseLineWidth * 2 .. ";") table.insert(r, "    stroke-linecap: butt;") table.insert(r, "  }")

table.insert(r, "  .axisline-y2 {") table.insert(r, "    stroke:         " .. Parms["YAxis2Color"] .. ";") table.insert(r, "    stroke-width:   " .. BaseLineWidth * 2 .. ";") table.insert(r, "    stroke-linecap: butt;") table.insert(r, "  }")

table.insert(r, "  .axismark-main {") table.insert(r, "    stroke:       black;") table.insert(r, "    stroke-width: " .. BaseLineWidth .. ";") table.insert(r, "  }") table.insert(r, "  .axismark-second {") table.insert(r, "    stroke:       black;") table.insert(r, "    stroke-width: " .. BaseLineWidth .. ";") table.insert(r, "  }") table.insert(r, "  .axistitle-x {") table.insert(r, "    font-size: " .. FontSiz.XAxisTitle .. "px;") table.insert(r, "  }")

table.insert(r, "  .axisnumber-x {") table.insert(r, "    font-size: " .. FontSiz.XAxisValues .. "px;") table.insert(r, "  }")

table.insert(r, "  .axistitle-y {") table.insert(r, "    font-size: " .. FontSiz.YAxisTitle .. "px;") table.insert(r, "  }")

table.insert(r, "  .axisnumber-y {") table.insert(r, "    font-size: " .. FontSiz.YAxisValues .. "px;") table.insert(r, "  }")

if DoYAxis2 then table.insert(r, "  .axistitle-y2 {") table.insert(r, "    font-size: " .. FontSiz.YAxis2Title .. "px;") table.insert(r, "  }")

table.insert(r, "  .axisnumber-y2 {") table.insert(r, "    font-size: " .. FontSiz.YAxis2Values .. "px;") table.insert(r, "  }")

end

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

-- calculate stretched values for grid & mark spacings

local XAxisMarkStepStretch = round(Parms["XAxisValueStep"] * Mult.X, 3) local XAxisMark2StepStretch = 0 if Parms["XAxisMark2Step"] ~= nil then XAxisMark2StepStretch = round(Parms["XAxisMark2Step"] * Mult.X, 3) end

local YAxisMarkStepStretch = round(Parms["YAxisValueStep"] * Mult.Y, 3) local YAxisMark2StepStretch = 0 if Parms["YAxisMark2Step"] ~= nil then YAxisMark2StepStretch = round(Parms["YAxisMark2Step"] * Mult.Y, 3) end

local YAxis2MarkStepStretch = round(Parms["YAxis2ValueStep"] * Mult.Y2, 3) local YAxis2Mark2StepStretch = 0 if Parms["YAxis2Mark2Step"] ~= nil then YAxis2Mark2StepStretch = round(Parms["YAxis2Mark2Step"] * Mult.Y2, 3) end

table.insert(r, " &lt;!-- == Axis Marks == -->") table.insert(r, " ")

-- X Axis Marks if #GroupText == 0 then table.insert(r, " ") table.insert(r, "  <pattern id=\"x-axismark-main\""            .. " x=\"" .. Pos.XAxis.Zero .. "\""            .. " width=\"" .. XAxisMarkStepStretch .. "\""            .. " height=\"" .. Siz.AxisMark .. "\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"" .. Siz.AxisMark .. "\" class=\"axismark-main\"/>") table.insert(r, "  ")

if Parms["XAxisMark2Step"] ~= nil then table.insert(r, "  <pattern id=\"x-axismark-second\""                .. " x=\"" .. Pos.XAxis.Zero .. "\""                .. " width=\"" .. XAxisMark2StepStretch .. "\""                .. " height=\"" .. Siz.AxisMark2 .. "\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"" .. Siz.AxisMark2 .. "\" class=\"axismark-second\"/>") table.insert(r, "  ") end table.insert(r, " ") table.insert(r, " ")

if Parms["XAxisMark2Step"] ~= nil then table.insert(r, " <rect id=\"x-axismark2\""               .. " x=\"" .. Siz.Space.Left .. "\""                .. " y=\"" .. Pos.XAxis.Marks .. "\""                .. " width=\"" .. round(ChartWidth * 1.01, 1) .. "\""                .. " height=\"" .. Siz.AxisMark2 .. "\""                .. " fill=\"url(#x-axismark-second)\"/>") end

table.insert(r, " <rect id=\"x-axismark\""           .. " x=\"" .. Siz.Space.Left .. "\""            .. " y=\"" .. Pos.XAxis.Marks .. "\""            .. " width=\"" .. round(ChartWidth * 1.01, 1) .. "\""            .. " height=\"" .. Siz.AxisMark .. "\""            .. " fill=\"url(#x-axismark-main)\"/>") table.insert(r, " ") end

-- Y Axis Marks table.insert(r, " ") table.insert(r, "  <pattern id=\"y-axismark-main\""        .. " y=\"" .. Pos.YAxis.Zero .. "\""        .. " width=\"" .. Siz.AxisMark .. "\""        .. " height=\"" .. YAxisMarkStepStretch .. "\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark .. "\" y2=\"0\" class=\"axismark-main\"/>") table.insert(r, "  ")

if Parms["YAxisMark2Step"] ~= nil then table.insert(r, "  <pattern id=\"y-axismark-second\""            .. " y=\"" .. Pos.YAxis.Zero .. "\""            .. " width=\"" .. Siz.AxisMark2 .. "\""            .. " height=\"" .. YAxisMark2StepStretch .. "\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark2 .. "\" y2=\"0\" class=\"axismark-second\"/>") table.insert(r, "  ") end table.insert(r, " ") table.insert(r, " ")

if Parms["YAxisMark2Step"] ~= nil then table.insert(r, " <rect id=\"y-axismark2\""           .. " x=\"" .. Pos.YAxis.Marks + Siz.AxisMark - Siz.AxisMark2 .. "\""            .. " y=\"" .. Siz.Space.Top .. "\""            .. " width=\"" .. Siz.AxisMark2 .. "\""            .. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""            .. " fill=\"url(#y-axismark-second)\"/>")

end table.insert(r, " <rect id=\"y-axismark\""       .. " x=\"" .. Pos.YAxis.Marks .. "\""        .. " y=\"" .. Siz.Space.Top .. "\""        .. " width=\"" .. Siz.AxisMark .. "\""        .. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""        .. " fill=\"url(#y-axismark-main)\"/>") table.insert(r, " ")

-- Y Axis 2 Marks if DoYAxis2 then table.insert(r, " ") table.insert(r, "  <pattern id=\"y-axis2mark-main\""            .. " y=\"" .. Pos.YAxis2.Zero .. "\""            .. " width=\"" .. Siz.AxisMark .. "\""            .. " height=\"" .. YAxis2MarkStepStretch .. "\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark .. "\" y2=\"0\" class=\"axismark-main\"/>") table.insert(r, "  ")

if Parms["YAxis2Mark2Step"] ~= nil then table.insert(r, "  <pattern id=\"y-axis2mark-second\""                .. " y=\"" .. Pos.YAxis2.Zero.. "\""                .. " width=\"" .. Siz.AxisMark2 .. "\""                .. " height=\"" .. YAxis2Mark2StepStretch .. "\" patternUnits=\"userSpaceOnUse\">") table.insert(r, "    <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark2 .. "\" y2=\"0\" class=\"axismark-second\"/>") table.insert(r, "  ") end table.insert(r, " ") table.insert(r, " ")

if Parms["YAxis2Mark2Step"] ~= nil then table.insert(r, " <rect id=\"y-axis2mark2\""               .. " x=\"" .. Pos.YAxis2.Marks .. "\""                .. " y=\"" .. Siz.Space.Top .. "\""                .. " width=\"" .. Siz.AxisMark2 .. "\""                .. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""                .. " fill=\"url(#y-axis2mark-second)\"/>") end table.insert(r, " <rect id=\"y-axis2mark\""           .. " x=\"" .. Pos.YAxis2.Marks .. "\""            .. " y=\"" .. Siz.Space.Top .. "\""            .. " width=\"" .. Siz.AxisMark .. "\""            .. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""            .. " fill=\"url(#y-axis2mark-main)\"/>") table.insert(r, " ") end

table.insert(r, " &lt;!-- == Axis Lines == -->") table.insert(r, " ")

table.insert(r, " <line id=\"x-axis\""       .. " x1=\"" .. Siz.Space.Left .. "\""        .. " y1=\"" .. Pos.XAxis.Line .. "\""        .. " x2=\"" .. Siz.Space.Left + ChartWidth .. "\""        .. " y2=\"" .. Pos.XAxis.Line .. "\" class=\"axisline-x\"/>")

table.insert(r, " <line id=\"y-axis\""       .. " x1=\"" .. Pos.YAxis.Line .. "\""        .. " y1=\"" .. Siz.Space.Top .. "\""        .. " x2=\"" .. Pos.YAxis.Line .. "\""        .. " y2=\"" .. Siz.Space.Top + ChartHeight .. "\" class=\"axisline-y\"/>")

if DoYAxis2 then table.insert(r, " <line id=\"y-axis2\""          .. " x1=\"" .. Pos.YAxis2.Line .. "\""           .. " y1=\"" .. Siz.Space.Top .. "\""           .. " x2=\"" .. Pos.YAxis2.Line .. "\""           .. " y2=\"" .. Siz.Space.Top + ChartHeight .. "\" class=\"axisline-y2\" />") end table.insert(r, " ") table.insert(r, " &lt;!-- == Axis Values == -->") table.insert(r, " ")

local Anchor = "middle" local RotateText = "" if Parms["XAxisValueRotate"] ~= nil then if Parms["XAxisValueRotate"] < 0 then Anchor = "end" else Anchor = "start" end RotateText = " transform=\"rotate(" .. Parms["XAxisValueRotate"] .. ", "   end

if #GroupText == 0 then t = (Parms["XMax"] >= 10000) if Parms["XAxisValueFormat"] ~= nil then t = not (Parms["XAxisValueFormat"] == 'none') end table.insert(r, " <g id=\"x-axis-values\" class=\"axisnumber-x\" text-anchor=\"" .. Anchor .. "\">") codeAxisValues("x",           Pos.XAxis.Zero,            Parms["XAxisValueStep"],            Parms["XMin"], Parms["XMax"],            Mult.X, 0, round(Pos.XAxis.Values, 1),             Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"],            Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"],            t, RotateText) table.insert(r, " </g>") table.insert(r, " ") else table.insert(r, " <g id=\"x-axis-values\" class=\"axisnumber-x\" transform=\"translate("           .. Siz.Space.Left .. ", "            .. Pos.XAxis.Values            .. ")\" text-anchor=\"" .. Anchor .. "\">") for k, v in ipairs(GroupText) do           local bx = ((k - 1) * GroupWidth) + (GroupWidth / 2) table.insert(r, "  <text x=\"" .. round(bx, 1) .. "\""                .. iif(string.len(RotateText) > 0, RotateText .. round(bx, 1) .. ", " .. -round(FontSiz.XAxisValues / 3, 1) .. ")\"", "")               .. ">" .. v .. " ")        end        table.insert(r, " </g>")        table.insert(r, " ")    end

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

table.insert(r, " <g id=\"y-axis-values\" class=\"axisnumber-y\" text-anchor=\"end\">") codeAxisValues("y",       Pos.YAxis.Zero,        Parms["YAxisValueStep"],        Parms["YMin"], Parms["YMax"],        Mult.Y, FontSiz["YAxisValues"] / 3, round(Pos.YAxis.Values, 1),         Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"],        Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"],        t, "") table.insert(r, " </g>") table.insert(r, " ")

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

table.insert(r, " <g id=\"y-axis2-values\" class=\"axisnumber-y2\" text-anchor=\"start\">") codeAxisValues("y",           Pos.YAxis2.Zero,            Parms["YAxis2ValueStep"],            Parms["Y2Min"], Parms["Y2Max"],            Mult.Y2, FontSiz["YAxis2Values"] / 3, round(Pos.YAxis2.Values, 1),            Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"],            Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"],            t, "") table.insert(r, " </g>") table.insert(r, " ") end if Parms["XAxisTitle"] ~= nil or Parms["YAxisTitle"] ~= nil or (DoYAxis2 and Parms["YAxis2Title"] ~= nil) then table.insert(r, " &lt;!-- == Axis Titles == -->") table.insert(r, " ") end if Parms["XAxisTitle"] ~= nil then table.insert(r, " <text id=\"title-x\" class=\"axistitle-x\""           .. " x=\"" .. Siz.Space.Left + (ChartWidth * 0.5) .. "\""            .. " y=\"" .. round(Pos.XAxis.Title, 1) .. "\" text-anchor=\"middle\">"            .. Parms["XAxisTitle"]            .. " ") table.insert(r, " ") end

if Parms["YAxisTitle"] ~= nil then table.insert(r, " <text id=\"title-y\" class=\"axistitle-y\""           .. " x=\"-" .. Siz.Space.Top + (ChartHeight * 0.5) .. "\""            .. " y=\"" .. round(Pos.YAxis.Title, 1) .. "\""            .. " transform=\"rotate(-90)\" text-anchor=\"middle\">"            .. Parms["YAxisTitle"]            .. " ") table.insert(r, " ") end

if DoYAxis2 and Parms["YAxis2Title"] ~= nil then table.insert(r, " <text id=\"title-y2\" class=\"axistitle-y2\""           .. " x=\"-" .. Siz.Space.Top + (ChartHeight * 0.5) .. "\""            .. " y=\"" .. round(Pos.YAxis2.Title, 1) .. "\""            .. " transform=\"rotate(-90)\" text-anchor=\"middle\">"            .. Parms["YAxis2Title"]            .. " ") table.insert(r, " ") end end

function codeAxisValues(Axis, PosAxisZero, ValueStep, AxisMin, AxisMax, Stretch, YShift, OtherPos, ValueMultiplier, ValueRound, Prefix, Suffix, Format, XRotateText)

-- Parms for location -- Axis, text: 'x' or 'y'       -- PosAxisZero, numeric -- ValueStep, numeric -- AxisMin, numeric -- AxisMax, numeric -- Stretch, numeric -- YShift, numeric -- OtherPos, numeric -- Parms for format -- ValueMultiplier, numeric -- ValueRound, numeric -- Prefix, text -- Suffix, text -- Format, boolean -- XRotateText, text

local l = ""

local ValueStart = 0 if AxisMin ~= 0 then ValueStart = math.ceil(AxisMin / ValueStep) * ValueStep end local Value = ValueStart while Value <= AxisMax do       if Axis == 'x' then Position = PosAxisZero + (Value * Stretch) l = "  <text x=\"" .. round(Position, 1) .. "\" y=\"" .. OtherPos .. "\"" .. iif(string.len(XRotateText) > 0, XRotateText .. round(Position, 1) .. ", " .. OtherPos-round(FontSiz.XAxisValues / 3, 1) .. ")\"", "")               .. ">"        else            Position = PosAxisZero - (Value * Stretch) + YShift            l = "   <text x=\"" .. OtherPos .. "\" y=\"" .. round(Position, 1) .. "\">"        end

if Format then l = l .. Prefix .. mw.getContentLanguage:formatNum(round(Value * ValueMultiplier, ValueRound)) .. Suffix .. " "       else l = l .. Prefix .. round(Value * ValueMultiplier, ValueRound) .. Suffix .. " "       end table.insert(r, l)       Value = Value + ValueStep end end

function commonBottom

if (#SeriesText < 1 or Parms["LegendType"] == KeyWords["none"]) and Parms["Title"] == nil and Parms["Footnote"] == nil and #ChartText <= 0 then -- no common-element styles else table.insert(r, " &lt;!-- == Common-element Styles == -->") table.insert(r, " ")

table.insert(r, " <style type=\"text/css\"> <![CDATA[")

if #SeriesText < 1 or Parms["LegendType"] == KeyWords["none"] then -- no legend else table.insert(r, "  .legendbox {") table.insert(r, "    stroke:       " .. Parms["LegendBorder"] .. ";") table.insert(r, "    stroke-width: " .. BaseLineWidth .. ";") table.insert(r, "    fill:         white;") table.insert(r, "  }") table.insert(r, "  .legendtext {") table.insert(r, "    font-size:   " .. FontSiz.LegendText .. "px;") table.insert(r, "    text-anchor: start;") table.insert(r, "  }") end if Parms["Title"] ~= nil then table.insert(r, "  .titletext {") table.insert(r, "    font-size: " .. FontSiz.Title .. "px;") table.insert(r, "  }") end if Parms["Footnote"] ~= nil then table.insert(r, "  .footnotetext {") table.insert(r, "    font-size: " .. FontSiz.Footnote .. "px;") table.insert(r, "  }") end if #ChartText > 0 then table.insert(r, "  .charttext {") table.insert(r, "    font-size: " .. FontSiz.ChartText .. "px;") table.insert(r, "  }") end table.insert(r, " ]]>") table.insert(r, " ") table.insert(r, " ") end

-- legend

if #SeriesText < 1 or Parms["LegendType"] == KeyWords["none"] then -- no legend else table.insert(r, " &lt;!-- == 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 = " <g id=\"legend\" transform=\"translate("           if Parms["LegendType"] == KeyWords["horizontal"] then                -- horizontal legend                if Parms["LegendX"] ~= nil then                    tr = tr .. round(Siz.Space.Left + (Parms["LegendX"] * Mult.X), 1)                else                    tr = tr .. round(Siz.Space.Left, 1)                end                tr = tr .. ", "                if Parms["LegendY"] ~= nil then                    tr = tr .. round(Siz.Space.Top + ChartHeight - (Parms["LegendY"] * Mult.Y), 1)                else                    tr = tr .. round(Pos.Legend, 1)                end                table.insert(r, tr .. ")\">")

table.insert(r, "  <rect id=\"legend-background\" class=\"legendbox\" x=\"0\" y=\"0\""                    .. " width=\"" .. Siz.Legend.Width .. "\""                    .. " height=\"" .. Siz.Legend.Height .. "\"/>")

local PosX, PosY = Siz.ChartPadding, Siz.ChartPadding for k, v in ipairs(SeriesText) do                   table.insert(r, " ") codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartPadding, Siz.Legend.Text, v, Marker[k]) PosX = PosX + Siz.Legend.ElementWidth if k == LegendElementsInWidth then -- new line of legend elements PosX = Siz.ChartPadding PosY = PosY + Siz.Legend.ElementHeight end end else -- vertical legend if Parms["LegendX"] ~= nil then tr = tr .. round(Siz.Space.Left + (Parms["LegendX"] * Mult.X), 1) else tr = tr .. round(Pos.Legend, 1) end tr = tr .. ", "               if Parms["LegendY"] ~= nil then t = Parms["LegendY"] else t = 0.9 * math.abs(Parms["YMax"] - Parms["YMin"]) end table.insert(r, tr .. round(Siz.Space.Top + ChartHeight - (t * Mult.Y), 1) .. ")\">")

table.insert(r, "  <rect id=\"legend-background\" class=\"legendbox\" x=\"0\" y=\"0\""                    .. " width=\"" .. Siz.Legend.Width .. "\""                    .. " height=\"" .. Siz.Legend.Height .. "\"/>")

local PosX, PosY = Siz.ChartPadding, Siz.ChartPadding for k, v in ipairs(SeriesText) do                   table.insert(r, " ") codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartPadding, Siz.Legend.Text, v, Marker[k]) PosY = PosY + Siz.Legend.ElementHeight end end table.insert(r, " </g>") table.insert(r, " ") end end

-- title and footnote

if Parms["Title"] ~= nil then table.insert(r, " &lt;!-- == Title Text == -->") tr = " <text id=\"title\" class=\"titletext\" text-anchor=\"" .. iif(Parms["TitleX"] ~= nil or Parms["TitleY"] ~= nil, "left", "middle") .. "\"" .. " x=\""       if Parms["TitleX"] ~= nil then            tr = tr .. round(Siz.Space.Left + (Parms["TitleX"] * Mult.X), 1) .. "\"" else tr = tr .. round(Siz.Space.Left + ChartWidth * 0.5, 1) .. "\""       end        tr = tr .. " y=\"" if Parms["TitleY"] ~= nil then tr = tr .. round(Siz.Space.Top + ChartHeight - (Parms["TitleY"] * Mult.Y), 1) .. "\">"       else            tr = tr .. round(Pos.Title, 1) .. "\">" end table.insert(r, tr .. Parms["Title"] .. " ") table.insert(r, " ") end

if Parms["Footnote"] ~= nil then table.insert(r, " &lt;!-- == Footnote Text == -->")

tr = " <text id=\"footnote\" class=\"footnotetext\" text-anchor=\"" .. iif(Parms["FootnoteX"] ~= nil or Parms["FootnoteY"] ~= nil, "left", "end") .. "\"" .. " x=\""       if Parms["FootnoteX"] ~= nil then            tr = tr .. round(Siz.Space.Left + (Parms["FootnoteX"] * Mult.X), 1) .. "\"" else tr = tr .. round(ImageWidth - Siz.ImagePadding.Right, 1) .. "\""       end        tr = tr .. " y=\"" if Parms["FootnoteY"] ~= nil then tr = tr .. round(Siz.Space.Top + ChartHeight - (Parms["FootnoteY"] * Mult.Y), 1) .. "\">"       else            tr = tr .. round(Pos.Footnote, 1) .. "\">" end table.insert(r, tr .. Parms["Footnote"] .. " ") table.insert(r, " ") end

-- chart texts

if #ChartText > 0 then table.insert(r, " &lt;!-- == Chart Texts == -->") table.insert(r, " ")

table.insert(r, " <g id=\"charttexts\" class=\"charttext\" transform=\"translate(" .. Siz.Space.Left .. ", " .. Siz.Space.Top + ChartHeight .. ")\">") for i = 1, #ChartText do           if ChartText[i] ~= nil then table.insert(r, "  <text x=\"" .. round((Parms["ChartText" .. i .. "X"] - iif(#GroupText == 0, Parms["XMin"], 0)) * Mult.X, 1) .. "\""                    .. " y=\"" .. -round((Parms["ChartText" .. i .. "Y"] - Parms["YMin"]) * Mult.Y, 1) .. "\">"                    .. ChartText[i] .. " ") end end table.insert(r, " </g>") table.insert(r, " ") end if Parms["ImageForegroundSVG"] ~= nil then table.insert(r, " &lt;!-- 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, 1)

l = "  <polyline id=\"legend-line" .. SeriesNumber .. "\" class=\"series-lines-general series" .. SeriesNumber .. "\"" .. " points=\""           .. round(PosX, 1) .. "," .. lineY .. " "            .. round(PosX + (TextSize * 1), 1) .. "," .. lineY .. " "            .. round(PosX + (TextSize * 2), 1) .. "," .. lineY .. "\"" if Marker ~= nil and Marker ~= 0 then l = l .. " marker-mid=\"url(#series" .. SeriesNumber .. "marker)\"" end table.insert(r, l .. "/>") table.insert(r, "  <text id=\"legend-text" .. SeriesNumber .. "\" class=\"legendtext\""            .. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 1) .. "\""            .. " y=\"" .. round(PosY + TextSize, 1) .. "\">"            .. Text .. " ") else table.insert(r, "  <rect id=\"legend-area" .. SeriesNumber .. "\" class=\"series-areas-general series" .. SeriesNumber .. "\""            .. " x=\"" .. round(PosX, 1) .. "\""            .. " y=\"" .. round(PosY, 1) .. "\""            .. " width=\"" .. (TextSize * 2) .. "\""            .. " height=\"" .. TextSize .. "\"/>") table.insert(r, "  <text id=\"legend-text" .. SeriesNumber .. "\" class=\"legendtext\""            .. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 1) .. "\""            .. " y=\"" .. round(PosY + TextSize, 1) .. "\">"            .. Text .. " ") end end

function outputDebugInfo

if Parms["Debug"] ~= nil and string.find(Parms["Debug"], KeyWords["parms"]) ~= nil then -- list all arguments table.insert(r, " All Arguments :") 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") table.insert(r, " ")

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

table.insert(r, "--") table.insert(r, " ") return true end

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 end end

-- 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 fret end

function round(x, p)   -- round number x to precision p

-- p > 0 rounds to p decimal places, eg: round(4.57, 1) = 4.5 -- p = 0 (or not given) rounds to nearest integer -- 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 end end

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) - point end

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 end end

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 end end

return { [KeyWords.barChart] = barChart, [KeyWords.lineChart] = lineChart, [KeyWords.scatterChart] = scatterChart, [KeyWords.mixedChart] = mixedChart, [KeyWords.pieChart] = pieChart, }