Module:Sandbox/Innesw/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, DoHorizontal, DoGroupsTopDown, DoArea, DoStack, DoStack100, DoYAxis2, DoChartAdjust = false, false, false, false, false, false, false, false

local Msgs = {}

local FontSiz, Siz, Pos, Mult, Adjusts = {}, {}, {}, {}, {}

local 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',

HorizontalBarGraph = 'HorizontalBarGraph',

GroupsTopDown = 'GroupsTopDown',

ChartWidth = 'ChartWidth',

ChartHeight = 'ChartHeight',

ChartAdjust = 'ChartAdjust',

GreyScale = 'GreyScale',

GrayScale = 'GrayScale',

LineWidth = 'LineWidth',

ChartBackgroundColor = 'ChartBackgroundColor',

XMin = 'XMin',

XMax = 'XMax',

XAxisTitle = 'XAxisTitle',

XAxisValueStep = 'XAxisValueStep',

XAxisValueMultiplier = 'XAxisValueMultiplier',

XAxisValueRound = 'XAxisValueRound',

XAxisValueAbsolute = 'XAxisValueAbsolute',

XAxisValuePrefix = 'XAxisValuePrefix',

XAxisValueSuffix = 'XAxisValueSuffix',

XAxisValueFormat = 'XAxisValueFormat',

XAxisValueRotate = 'XAxisValueRotate',

XAxisValueSpace = 'XAxisValueSpace',

XAxisMark2Step = 'XAxisMark2Step',

XAxisArrows = 'XAxisArrows',

YMin = 'YMin',

YMax = 'YMax',

YAxisTitle = 'YAxisTitle',

YAxisValueStep = 'YAxisValueStep',

YAxisValueMultiplier = 'YAxisValueMultiplier',

YAxisValueRound = 'YAxisValueRound',

YAxisValueAbsolute = 'YAxisValueAbsolute',

YAxisValuePrefix = 'YAxisValuePrefix',

YAxisValueSuffix = 'YAxisValueSuffix',

YAxisValueFormat = 'YAxisValueFormat',

YAxisValueRotate = 'YAxisValueRotate',

YAxisValueSpace = 'YAxisValueSpace',

YAxisMark2Step = 'YAxisMark2Step',

YAxisColor = 'YAxisColor',

YAxisArrows = 'YAxisArrows',

Y2Min = 'Y2Min',

Y2Max = 'Y2Max',

YAxis2Title = 'YAxis2Title',

YAxis2ValueStep = 'YAxis2ValueStep',

YAxis2ValueMultiplier = 'YAxis2ValueMultiplier',

YAxis2ValueRound = 'YAxis2ValueRound',

YAxis2ValueAbsolute = 'YAxis2ValueAbsolute',

YAxis2ValuePrefix = 'YAxis2ValuePrefix',

YAxis2ValueSuffix = 'YAxis2ValueSuffix',

YAxis2ValueFormat = 'YAxis2ValueFormat',

YAxis2ValueRotate = 'YAxis2ValueRotate',

YAxis2ValueSpace = 'YAxis2ValueSpace',

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

-- rem parameter is accepted but ignored, allows user to rem out a parameter

rem = 'rem',

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

MsgHeading = "===== Charts SVG - Messages =====",

ParmsNotFound = "Unknown Parameters:",

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.",

CopyText = "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.",

}

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)

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

DoHorizontal = (Parms["HorizontalBarGraph"] ~= nil)

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

-- fill internal tables

for i = 1, SeriesCount do

SeriesData[i] = {}

transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)

DataPointsCount = DataPointsCount + #SeriesData[i]

SType[i] = KeyWords["bar"]

end

copyTable(SeriesData, OriginalData) -- preserve original data

BarSeriesCount = SeriesCount

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

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

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

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

build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)

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

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

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

if DoStack or DoStack100 then

calcStack()

DoYAxis2 = false

end

if not checkParms() then

-- messages, instead of SVG output

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

end

if not outputDebugInfo() then

-- stop after debug output

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

end

-- build the SVG

prelimsCommon()

prelimsAxes()

commonTop()

codeAxisGrids()

stylesAreas()

defsAreas()

elementsGraphs()

codeAxes()

commonBottom()

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

end

function lineChart(frame)

Args = frame.args

getAllParms()

DoArea = (Parms["Area"] ~= nil)

DoStack = (Parms["Stack"] ~= nil)

DoStack100 = (Parms["Stack100"] ~= nil)

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

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

-- fill internal tables

for i = 1, SeriesCount do

SeriesData[i] = {}

transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)

DataPointsCount = DataPointsCount + #SeriesData[i]

SType[i] = KeyWords["line"]

end

copyTable(SeriesData, OriginalData) -- preserve original data

BarSeriesCount = 0

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

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

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

build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes'

build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)

build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)

build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil

build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)

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

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

build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)

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

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

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

if DoStack or DoStack100 then

calcStack()

DoArea = true

DoYAxis2 = false

end

if not checkParms() then

-- messages, instead of SVG output

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

end

if not outputDebugInfo() then

-- stop after debug output

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

end

-- build the SVG

prelimsCommon()

prelimsAxes()

commonTop()

codeAxisGrids()

if DoArea then

stylesAreas()

defsAreas()

else

stylesLines()

defsMarkers()

end

elementsGraphs()

codeAxes()

commonBottom()

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

end

function scatterChart(frame)

Args = frame.args

getAllParms()

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

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

-- fill internal tables

for i = 1, SeriesCount do

SeriesData[i] = {}

transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)

DataPointsCount = DataPointsCount + #SeriesData[i]

SType[i] = KeyWords["line"]

end

copyTable(SeriesData, OriginalData) -- preserve original data

BarSeriesCount = 0

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

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

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

build("Series", "Line", SeriesCount, LineShow, KeyWords["none"], nil, nil) -- the default line visibility is 'none'

build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)

build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)

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

build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)

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

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

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

if not checkParms() then

-- messages, instead of SVG output

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

end

if not outputDebugInfo() then

-- stop after debug output

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

end

-- build the SVG

prelimsCommon()

prelimsAxes()

commonTop()

codeAxisGrids()

stylesLines()

defsMarkers()

elementsGraphs()

codeAxes()

commonBottom()

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

end

function mixedChart(frame)

Args = frame.args

getAllParms()

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

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

-- fill internal tables

build("Series", "Type", SeriesCount, SType, "line", nil, nil)

for i = 1, SeriesCount do

if SType[i] == KeyWords["bar"] then

BarSeriesCount = BarSeriesCount + 1

end

end

for i = 1, SeriesCount do

SeriesData[i] = {}

transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)

DataPointsCount = DataPointsCount + #SeriesData[i]

end

copyTable(SeriesData, OriginalData) -- preserve original data

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

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

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

build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes'

build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)

build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)

build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil

build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)

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

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

build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)

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

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

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

if not checkParms() then

-- messages, instead of SVG output

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

end

if not outputDebugInfo() then

-- stop after debug output

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

end

-- build the SVG

prelimsCommon()

prelimsAxes()

commonTop()

codeAxisGrids()

stylesAreas()

defsAreas()

stylesLines()

defsMarkers()

elementsGraphs()

codeAxes()

commonBottom()

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

end

function pieChart(frame)

Args = frame.args

getAllParms()

GroupsCount = 0 -- ensures any GroupNText parameters are ignored

-- fill internal tables

-- only transfer series 1

if SeriesCount > 0 then

transfer(Parms["Series" .. 1 .. "Values"], SeriesData, 2)

DataPointsCount = DataPointsCount + #SeriesData

end

copyTable(SeriesData, OriginalData) -- preserve original data

-- get segment parms for each element in the series

-- we do this here because transfer() (above) is where SeriesMaxLen is calculated

for i = 1, SeriesMaxLen do

Parms["Segment" .. i .. "Color"] = checkColor(getParm("Segment", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))

Parms["Segment" .. i .. "Pattern"] = getParm("Segment", i, "Pattern", "n")

if Parms["Segment" .. i .. "Pattern"] ~= nil then

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

end

end

build("Segment", "Color", SeriesMaxLen, Color, nil, nil, iif(Parms["GrayScale"] ~= nil, GrayColor, DefColor))

build("Segment", "Pattern", SeriesMaxLen, FillPattern, KeyWords["none"], nil, nil)

build("Segment", "PatternColor", SeriesMaxLen, FillPatternColor, nil, nil, nil)

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

-- transfer the first value in each SeriesData pair to table SeriesText, for the legend

for k, v in ipairs(SeriesData) do

SeriesText[k] = v[1]

end

SeriesCount = SeriesMaxLen

DoPie = true

if not checkParms() then

-- messages, instead of SVG output

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

end

if not outputDebugInfo() then

-- stop after debug output

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

end

-- build the SVG

prelimsCommon()

prelimsPie()

commonTop()

stylesAreas()

defsAreas()

elementsPie()

commonBottom()

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

end

function getAllParms()

-- get values for all parameters

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

-- SVG file metadata

Parms["FileTitle"] = getParm("FileTitle", nil, nil, "s", "SVG Chart")

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

-- general image

Parms["ImagePadding"] = getParm("ImagePadding", nil, nil, "n") -- switch for replacing default value with user setting for the size of the padding for all 4 spaces

Parms["ImagePaddingTop"] = getParm("ImagePaddingTop", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the top padding

Parms["ImagePaddingBottom"] = getParm("ImagePaddingBottom", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the bottom padding

Parms["ImagePaddingLeft"] = getParm("ImagePaddingLeft", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the left padding

Parms["ImagePaddingRight"] = getParm("ImagePaddingRight", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the right padding

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

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

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

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

-- display title

Parms["Title"] = getParm("Title", nil, nil, "s") -- switch to show title text

Parms["TitleX"] = getParm("TitleX", nil, nil, "n") -- switch to move title from default position

Parms["TitleY"] = getParm("TitleY", nil, nil, "n", 0)

-- footnote

Parms["Footnote"] = getParm("Footnote", nil, nil, "s") -- switch for footnote text

Parms["FootnoteX"] = getParm("FootnoteX", nil, nil, "n") -- switch to move footnote from default position

Parms["FootnoteY"] = getParm("FootnoteY", nil, nil, "n", 0)

-- general chart

Parms["Area"] = getParm("Area", nil, nil, "s") -- switch for area graphs

Parms["Stack"] = getParm("Stack", nil, nil, "s") -- switch for stacked graphs

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

Parms["HorizontalBarGraph"] = getParm("HorizontalBarGraph", nil, nil, "s") -- switch for horizontal bar graphs

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

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

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

if Parms["ChartWidth"] < 200 then

table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartWidth", Parms["ChartWidth"], 200))

end

if Parms["ChartHeight"] < 200 then

table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartHeight", Parms["ChartHeight"], 200))

end

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

Parms["GreyScale"] = getParm("GreyScale", nil, nil, "s")

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

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

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

-- XAxis

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

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

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

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

Parms["XAxisValueMultiplier"] = getParm("XAxisValueMultiplier", nil, nil, "n", 1)

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

Parms["XAxisValueAbsolute"] = getParm("XAxisValueAbsolute", nil, nil, "s") -- switch for absolute values display

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

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

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

Parms["XAxisValueRotate"] = getParm("XAxisValueRotate", nil, nil, "n") -- switch to rotate x-axis values

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

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

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

-- YAxis

if Parms["Stack100"] ~= nil then

Parms["YMin"] = 0

Parms["YMax"] = 100

else

Parms["YMin"] = getParm("YMin", nil, nil, "n", 0)

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

end

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

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

Parms["YAxisValueMultiplier"] = getParm("YAxisValueMultiplier", nil, nil, "n", 1)

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

Parms["YAxisValueAbsolute"] = getParm("YAxisValueAbsolute", nil, nil, "s") -- switch for absolute values display

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

if Parms["Stack100"] ~= nil then

Parms["YAxisValueSuffix"] = getParm("YAxisValueSuffix", nil, nil, "s", "%", "_", " ")

else

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

end

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

Parms["YAxisValueRotate"] = getParm("YAxisValueRotate", nil, nil, "n") -- switch to rotate y-axis values

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

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

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

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

-- YAxis2

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

Parms["Y2Min"] = getParm("Y2Min", nil, nil, "n", 0)

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

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

Parms["YAxis2ValueMultiplier"] = getParm("YAxis2ValueMultiplier", nil, nil, "n", 1)

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

Parms["YAxis2ValueAbsolute"] = getParm("YAxis2ValueAbsolute", nil, nil, "s") -- switch for absolute values display

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

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

Parms["YAxis2ValueFormat"] = getParm("YAxis2ValueFormat", nil, nil, "s") -- switch to force values formatting on or off

Parms["YAxis2ValueRotate"] = getParm("YAxis2ValueRotate", nil, nil, "n") -- switch to rotate y-axis-2 values

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

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

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

-- grid lines

Parms["XGrid"] = getParm("XGrid", nil, nil, "s", Parms["XAxisValueStep"])

if Parms["XGrid"] ~= KeyWords["none"] then

Parms["XGrid"] = tonumber(Parms["XGrid"])

end

Parms["YGrid"] = getParm("YGrid", nil, nil, "s", Parms["YAxisValueStep"])

if Parms["YGrid"] ~= KeyWords["none"] then

Parms["YGrid"] = tonumber(Parms["YGrid"])

end

-- legend

Parms["LegendType"] = getParm("LegendType", nil, nil, "s", KeyWords["vertical"])

i = 1

Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N

while Parms["Series" .. i .. "Text"] ~= nil do

i = i + 1

Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N

end

Parms["LegendX"] = getParm("LegendX", nil, nil, "n") -- switch for moving the legend from the default position

Parms["LegendY"] = getParm("LegendY", nil, nil, "n", 0)

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

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

Parms["LegendSVG"] = getParm("LegendSVG", nil, nil, "s") -- switch for replacing all legend code

-- font sizes

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

Parms["TitleFontSize"] = getParm("TitleFontSize", nil, nil, "n", Parms["FontSize"])

Parms["FootnoteFontSize"] = getParm("FootnoteFontSize", nil, nil, "n", Parms["FontSize"])

Parms["LegendFontSize"] = getParm("LegendFontSize", nil, nil, "n", Parms["FontSize"])

Parms["XAxisTitleFontSize"] = getParm("XAxisTitleFontSize", nil, nil, "n", Parms["FontSize"])

Parms["YAxisTitleFontSize"] = getParm("YAxisTitleFontSize", nil, nil, "n", Parms["FontSize"])

Parms["YAxis2TitleFontSize"] = getParm("YAxis2TitleFontSize", nil, nil, "n", Parms["FontSize"])

Parms["XAxisValuesFontSize"] = getParm("XAxisValuesFontSize", nil, nil, "n", Parms["FontSize"])

Parms["YAxisValuesFontSize"] = getParm("YAxisValuesFontSize", nil, nil, "n", Parms["FontSize"])

Parms["YAxis2ValuesFontSize"] = getParm("YAxis2ValuesFontSize", nil, nil, "n", Parms["FontSize"])

Parms["ChartTextFontSize"] = getParm("ChartTextFontSize", nil, nil, "n", Parms["FontSize"])

Parms["LabelsFontSize"] = getParm("LabelsFontSize", nil, nil, "n", Parms["FontSize"])

-- bar width & spacing

Parms["BarWidth"] = getParm("BarWidth", nil, nil, "n", 20)

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

-- general graph line width

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

-- pie chart

Parms["PieRadius"] = getParm("PieRadius", nil, nil, "n", 200)

Parms["Explode"] = getParm("Explode", nil, nil, "s") -- switch for exploding some or all pie segments

if Parms["Explode"] ~= nil then

if tonumber(Parms["Explode"]) ~= nil then

Parms["Explode"] = tonumber(Parms["Explode"])

-- default explode radius is 20%

Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 20)

else

-- any non-numeric value = all, default explode radius is 10%

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

end

end

-- Parms[] SegmentNColor, SegmentNPattern and SegmentNPatternColor are done in pieChart()

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

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

Parms["SegmentTextRadius"] = getParm("SegmentTextRadius", nil, nil, "n", 105)

Parms["DoughnutHole"] = getParm("DoughnutHole", nil, nil, "s") -- switch for a doughnut chart

if Parms["DoughnutHole"] ~= nil then

if tonumber(Parms["DoughnutHole"]) ~= nil then

-- if DoughnutHole is a number, ensure it is of numeric type

Parms["DoughnutHole"] = tonumber(Parms["DoughnutHole"])

else

-- any other value, hole size is 50%

Parms["DoughnutHole"] = 50

end

end

Parms["PieStartAngle"] = getParm("PieStartAngle", nil, nil, "n", 0) -- move the start angle

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

-- bar and pie-segment borders

Parms["BorderColor"] = checkColor(getParm("BorderColor", nil, nil, "s")) -- switch for showing borders on bars

Parms["BorderWidth"] = getParm("BorderWidth", nil, nil, "n", 100) -- % of standard line width

-- series

i = 1

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

while Parms["Series" .. i .. "Values"] ~= nil do

Parms["Series" .. i .. "Type"] = getParm("Series", i, "Type", "s")

Parms["Series" .. i .. "YAxis2"] = getParm("Series", i, "YAxis2") -- switch for series to use YAxis2 as scale for Y values

Parms["Series" .. i .. "Labels"] = getParm("Series", i, "Labels") -- switch for series to show data labels

Parms["Series" .. i .. "Color"] = checkColor(getParm("Series", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))

Parms["Series" .. i .. "Line"] = getParm("Series", i, "Line", "s")

Parms["Series" .. i .. "Width"] = getParm("Series", i, "Width", "n", Parms["GraphLineWidth"])

Parms["Series" .. i .. "Dash"] = getParm("Series", i, "Dash", "s", KeyWords["none"])

t = tonumber(Parms["Series" .. i .. "Dash"])

if t ~= nil and t <= #DashPattern then

-- if SeriesNDash is a number in the table of default dashes, use the dash pattern for that number

Parms["Series" .. i .. "Dash"] = DashPattern[t]

end

Parms["Series" .. i .. "Marker"] = getParm("Series", i, "Marker", "s") -- switch for markers on the graph

if tonumber(Parms["Series" .. i .. "Marker"]) ~= nil then

-- if SeriesNMarker is a number, ensure it is of numeric type

Parms["Series" .. i .. "Marker"] = tonumber(Parms["Series" .. i .. "Marker"])

end

Parms["Series" .. i .. "MarkerSize"] = getParm("Series", i, "MarkerSize", "n", 100)

Parms["Series" .. i .. "MarkerFill"] = checkColor(getParm("Series", i, "MarkerFill", "s"))

Parms["Series" .. i .. "Pattern"] = getParm("Series", i, "Pattern", "n")

if Parms["Series" .. i .. "Pattern"] ~= nil then

Parms["Series" .. i .. "PatternColor"] = checkColor(getParm("Series", i, "PatternColor", "s", iif(Parms["GrayScale"] ~= nil, "white", "black")))

end

SeriesCount = SeriesCount + 1

i = i + 1

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

end

-- groups

i = 1

Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s") -- switch for grouping values

while Parms["Group" .. i .. "Text"] ~= nil do

GroupsCount = GroupsCount + 1

i = i + 1

Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s")

end

if GroupsCount > 0 then

Parms["XMax"] = GroupsCount

end

-- chart texts

i = 1

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

while Parms["ChartText" .. i] ~= nil do

Parms["ChartText" .. i .. "X"] = getParm("ChartText", i, "X", "n", 0)

Parms["ChartText" .. i .. "Y"] = getParm("ChartText", i, "Y", "n", 0)

ChartTextCount = ChartTextCount + 1

i = i + 1

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

end

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

-- debug

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

end

function getParm(s1, num, s2, typ, def, subst, with)

-- returns the value of a named parameter (of a specified type) or a default

-- the name may be built from multiple parts (s1, num, s2)

local s = KeyWords[s1]

if num ~= nil then

s = s .. num

end

if s2 ~= nil then

s = s .. KeyWords[s2]

end

local result

if Args[s] ~= nil then

result = Args[s]

if typ == "n" then

result = tonumber(result)

if result == nil then

table.insert(Msgs, string.format(MessageTexts.NotNumeric, s, Args[s]))

end

end

else

result = def

end

-- optional substitution of characters within the parameter value

if result ~= nil and subst and with then

result = mw.ustring.gsub(result, subst, with)

end

return 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 PosTotal, NegTotal, NegFlag = {}, {}, false

for j = 1, #GroupText do

PosTotal[j] = 0

NegTotal[j] = 0

for i = 1, #SeriesData do

if SeriesData[i][j] ~= nil and tonumber(SeriesData[i][j][1]) == j then

if tonumber(SeriesData[i][j][2]) < 0 then

NegTotal[j] = NegTotal[j] - SeriesData[i][j][2]

SeriesData[i][j][2] = -NegTotal[j]

NegFlag = true

else

PosTotal[j] = PosTotal[j] + SeriesData[i][j][2]

SeriesData[i][j][2] = PosTotal[j]

end

end

end

end

if DoStack100 then

for j = 1, #GroupText do

for i = 1, #SeriesData do

if SeriesData[i][j] ~= nil then

SeriesData[i][j][2] = SeriesData[i][j][2] / iif(tonumber(SeriesData[i][j][2]) < 0, NegTotal[j], PosTotal[j]) * 100

end

end

end

if NegFlag then

Parms["YMin"] = -100

end

end

end

function checkParms()

-- check for parameter issues

-- unknown parameters

local regexps = {}

-- add patterns to regexps

table.insert(regexps, "Series[%d][%d]*Values")

table.insert(regexps, "Series[%d][%d]*Type")

table.insert(regexps, "Series[%d][%d]*YAxis2")

table.insert(regexps, "Series[%d][%d]*Labels")

table.insert(regexps, "Series[%d][%d]*Color")

table.insert(regexps, "Series[%d][%d]*Line")

table.insert(regexps, "Series[%d][%d]*Width")

table.insert(regexps, "Series[%d][%d]*Dash")

table.insert(regexps, "Series[%d][%d]*Marker")

table.insert(regexps, "Series[%d][%d]*MarkerSize")

table.insert(regexps, "Series[%d][%d]*MarkerFill")

table.insert(regexps, "Series[%d][%d]*Pattern")

table.insert(regexps, "Series[%d][%d]*PatternColor")

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

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

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

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

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

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

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

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

local unknowns = unknownParameters(KeyWords, regexps)

if #unknowns > 0 then

-- ensure parms-not-found are at the top of the list of messages

local tmp = {}

copyTable(Msgs, tmp)

Msgs = {}

table.insert(Msgs, MessageTexts.ParmsNotFound)

for k, v in spairs(unknowns) do

table.insert(Msgs, " " .. unknowns[k])

end

for i = 1, #tmp do

table.insert(Msgs, tmp[i])

end

end

if #GroupText > 0 then

for k1, v1 in ipairs(SeriesData) do

for k2, v2 in ipairs(v1) do

local t = tonumber(v2[1])

if t ~= math.floor(t)

or t < 1

or t > #GroupText then

table.insert(Msgs, string.format(MessageTexts.NotInGroup, k1, k2, v2[1]))

end

end

end

end

if not DoYAxis2 then

for k, v in pairs(YAxis2) do

table.insert(Msgs, string.format(MessageTexts.NoYAxis2, k))

end

end

for k, v in pairs(Marker) do

local t = tonumber(v)

if v == "none" then

-- OK

elseif t == nil then

table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v))

elseif (t >= 1 and t <= 7) then

-- OK

else

table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v))

end

end

for k, v in pairs(LineDash) do

local t = tonumber(v)

if v == "none" then

-- OK

elseif t == nil then

-- non-numerics are OK for LineDash

elseif (t >= 1 and t <= #DashPattern) then

-- OK

else

table.insert(Msgs, string.format(MessageTexts.NotInDashPatterns, k, v))

end

end

for k, v in pairs(FillPattern) do

local t = tonumber(v)

if v == "none" then

-- OK

elseif t == nil then

table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v))

elseif (t >= 1 and t <= 8)

or (t >= 11 and t <= 18)

or (t >= 21 and t <= 24)

or (t >= 31 and t <= 34)

or (t >= 41 and t <= 49)

or (t >= 51 and t <= 56)

or (t >= 61 and t <= 64) then

-- OK

else

table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v))

end

end

if #Msgs > 0 then

-- add messages to output

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

for i = 1, math.min(#Msgs, 10) do

table.insert(r, " " .. Msgs[i])

end

return false

end

return 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.Title = 7 * BaseFontSize * Parms["TitleFontSize"] / 100

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

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

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

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

-- the base line width can be changed by the user

BaseLineWidth = BaseLineWidth * Parms["LineWidth"] / 100

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

Siz.ImagePadding.Top = 2 * BaseUnit

if Parms["ImagePaddingTop"] ~= nil then

Siz.ImagePadding.Top = Parms["ImagePaddingTop"]

end

Siz.ImagePadding.Bottom = 2 * BaseUnit

if Parms["ImagePaddingBottom"] ~= nil then

Siz.ImagePadding.Bottom = Parms["ImagePaddingBottom"]

end

Siz.ImagePadding.Left = 2 * BaseUnit

if Parms["ImagePaddingLeft"] ~= nil then

Siz.ImagePadding.Left = Parms["ImagePaddingLeft"]

end

Siz.ImagePadding.Right = 2 * BaseUnit

if Parms["ImagePaddingRight"] ~= nil then

Siz.ImagePadding.Right = Parms["ImagePaddingRight"]

end

Siz.ChartMargin = 2 * BaseUnit

Siz.Text.Interline = 2 * BaseFontSize

Siz.Text.Labels = FontSiz.Labels

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

Siz.Footnote = iif(Parms["Footnote"] ~= nil and Parms["FootnoteX"] == nil, FontSiz.Footnote, 0)

Siz.Legend.Text = FontSiz.LegendText

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

Siz.Legend.Offset = 3 * BaseUnit

Siz.Text.Chart = FontSiz.ChartText

end

function prelimsAxes()

Pos.XAxis = {}

Pos.YAxis = {}

Pos.YAxis2 = {}

Pos.Origin = {}

Siz.Space = {}

Siz.XAxis = {}

Siz.YAxis = {}

Siz.YAxis2 = {}

Siz.ChartWidth = Parms["ChartWidth"]

Siz.ChartHeight = Parms["ChartHeight"]

if DoChartAdjust then

allChartAdjust()

end

if DoHorizontal then

Siz.XAxis.Length = Siz.ChartHeight

Siz.YAxis.Length = Siz.ChartWidth

Siz.YAxis2.Length = Siz.ChartWidth

else

Siz.XAxis.Length = Siz.ChartWidth

Siz.YAxis.Length = Siz.ChartHeight

Siz.YAxis2.Length = Siz.ChartHeight

end

prelimsLegend()

BarWidth = Parms["BarWidth"]

BarSpace = Parms["BarSpace"] / 100

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

if #GroupText > 0 then

if BarSeriesCount > 0 then

GroupWidth = Siz.XAxis.Length / #GroupText

if DoStack or DoStack100 then

UnitWidth = GroupWidth / 2

else

UnitWidth = GroupWidth / (BarSeriesCount + 1)

end

BarWidth = UnitWidth / (1 + BarSpace)

BarSpace = UnitWidth - BarWidth

Mult.x = Siz.XAxis.Length / #GroupText

else

GroupWidth = Siz.XAxis.Length / #GroupText

Mult.x = GroupWidth

end

else

Mult.x = Siz.XAxis.Length / (math.abs(Parms["XMax"] - Parms["XMin"]))

end

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

if DoYAxis2 then

Mult.y2 = Siz.YAxis2.Length / (math.abs(Parms["Y2Max"] - Parms["Y2Min"]))

else

Mult.y2 = 1

end

--

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

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

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

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

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

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

Siz.AxisMark = 2 * BaseUnit

Siz.AxisMark2 = 1 * BaseUnit

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

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

if DoHorizontal then

Siz.XAxis.Values = iif(Parms["XAxisValueSpace"] ~= nil, Parms["XAxisValueSpace"], 3 * FontSiz.XAxisValues)

Siz.YAxis.Values = iif(Parms["YAxisValueSpace"] ~= nil, Parms["YAxisValueSpace"], FontSiz.YAxisValues + Siz.Text.Interline)

else

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

Siz.YAxis.Values = iif(Parms["YAxisValueSpace"] ~= nil, Parms["YAxisValueSpace"], 3 * FontSiz.YAxisValues)

end

Siz.YAxis2.Title = 0

Siz.YAxis2.Values = 0

if DoYAxis2 then

if Parms["YAxis2Title"] ~= nil then

Siz.YAxis2.Title = FontSiz.YAxis2Title + Siz.Text.Interline

end

if DoHorizontal then

Siz.YAxis2.Values = iif(Parms["YAxis2ValueSpace"] ~= nil, Parms["YAxis2ValueSpace"], FontSiz.YAxis2Values + Siz.Text.Interline)

else

Siz.YAxis2.Values = iif(Parms["YAxis2ValueSpace"] ~= nil, Parms["YAxis2ValueSpace"], 3 * FontSiz.YAxis2Values)

end

end

-- spaces around the chart (working out from the chart):

-- AxisMarks

-- ChartMargin

-- AxisValues

-- AxisTitle

-- Title

-- Legend

-- Footnote

-- ImagePadding

-- Top Space

Siz.Space.Top = Siz.ImagePadding.Top

Pos.Title = Siz.ImagePadding.Top + FontSiz.Title

Siz.Space.Top = Siz.Space.Top + Siz.Text.Title

if DoHorizontal and DoYAxis2 then

Pos.YAxis2.Title = Siz.Space.Top + FontSiz.YAxis2Title

Siz.Space.Top = Siz.Space.Top + Siz.YAxis2.Title

Siz.Space.Top = Siz.Space.Top + Siz.YAxis2.Values

Siz.Space.Top = Siz.Space.Top + Siz.ChartMargin + Siz.AxisMark

Pos.YAxis2.Line = -Parms["XMax"] * Mult.x

Pos.YAxis2.Values = Pos.YAxis2.Line - Siz.AxisMark - Siz.ChartMargin -- pos is bottom edge of values box

else

Siz.Space.Top = Siz.Space.Top + Siz.ChartMargin

end

-- Bottom Space

if DoHorizontal then

if Parms["XMin"] < 0 and #GroupText == 0 then

-- Y axis line is within chart

Pos.YAxis.Line = 0

Pos.YAxis.Values = Pos.YAxis.Line + Siz.AxisMark + Siz.ChartMargin + FontSiz.YAxisValues

Siz.Space.Bottom = Siz.ChartMargin

else

-- Y axis line is at bottom of chart

Pos.YAxis.Line = -Parms["XMin"] * Mult.x

Siz.Space.Bottom = Siz.AxisMark

Pos.YAxis.Values = Pos.YAxis.Line + Siz.Space.Bottom + Siz.ChartMargin + FontSiz.YAxisValues

Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartMargin + Siz.YAxis.Values

end

if Parms["YAxisTitle"] ~= nil then

Pos.YAxis.Title = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + FontSiz.YAxisTitle

Siz.Space.Bottom = Siz.Space.Bottom + Siz.YAxis.Title

end

else

if Parms["YMin"] < 0 then

-- X axis line is within chart

Pos.XAxis.Line = 0

Pos.XAxis.Values = Pos.XAxis.Line + Siz.AxisMark + Siz.ChartMargin + FontSiz.XAxisValues

Siz.Space.Bottom = Siz.ChartMargin

else

-- X axis line is at bottom of chart

Pos.XAxis.Line = -Parms["YMin"] * Mult.y

Siz.Space.Bottom = iif(#GroupText > 0, 0, Siz.AxisMark)

Pos.XAxis.Values = Pos.XAxis.Line + Siz.Space.Bottom + Siz.ChartMargin + FontSiz.XAxisValues

Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartMargin + Siz.XAxis.Values

end

if Parms["XAxisTitle"] ~= nil then

Pos.XAxis.Title = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + FontSiz.XAxisTitle

Siz.Space.Bottom = Siz.Space.Bottom + Siz.XAxis.Title

end

end

if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then

Pos.Legend = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset

Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline

end

if Parms["Footnote"] ~= nil then

Pos.Footnote = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Footnote

Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote

end

Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom

-- Left Space

Siz.Space.Left = Siz.ImagePadding.Left

if DoHorizontal then

if Parms["XAxisTitle"] ~= nil then

Pos.XAxis.Title = Siz.Space.Left + FontSiz.XAxisTitle -- pos is right edge of title

Siz.Space.Left = Siz.Space.Left + Siz.XAxis.Title

end

if Parms["YMin"] < 0 then

-- X axis line is within chart

Siz.Space.Left = Siz.Space.Left + Siz.ChartMargin

Pos.XAxis.Line = 0

Pos.XAxis.Values = Pos.XAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box

else

-- X axis line is at left of chart

Siz.Space.Left = Siz.Space.Left + Siz.XAxis.Values + Siz.ChartMargin + Siz.AxisMark

Pos.XAxis.Line = Parms["YMin"] * Mult.y

Pos.XAxis.Values = Pos.XAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box

end

else

if Parms["YAxisTitle"] ~= nil then

Pos.YAxis.Title = Siz.Space.Left + FontSiz.YAxisTitle -- pos is right edge of title

Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Title

end

if Parms["XMin"] < 0 and #GroupText == 0 then

-- Y axis line is within chart

Siz.Space.Left = Siz.Space.Left + Siz.ChartMargin

Pos.YAxis.Line = 0

Pos.YAxis.Values = Pos.YAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box

else

-- Y axis line is at left of chart

Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Values + Siz.ChartMargin + Siz.AxisMark

Pos.YAxis.Line = Parms["XMin"] * Mult.x

Pos.YAxis.Values = Pos.YAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box

end

end

-- Right Space

Siz.Space.Right = 0

if DoHorizontal then

Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin

else

if DoYAxis2 then

Pos.YAxis2.Line = Parms["XMax"] * Mult.x

Siz.Space.Right = Siz.Space.Right + Siz.AxisMark

Pos.YAxis2.Values = Pos.YAxis2.Line + Siz.Space.Right + Siz.ChartMargin -- pos is left edge of values box

Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin + Siz.YAxis2.Values

if Parms["YAxis2Title"] ~= nil then

Pos.YAxis2.Title = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + FontSiz.YAxis2Title

Siz.Space.Right = Siz.Space.Right + Siz.YAxis2.Title

end

else

Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin

end

end

if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then

Pos.Legend = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + Siz.Legend.Offset

Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width

end

Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right

if DoHorizontal then

Pos.XAxis.Zero = Siz.Space.Top + Siz.ChartHeight + (iif(#GroupText == 0, Parms["XMin"], 0) * Mult.x)

Pos.YAxis.Zero = Siz.Space.Left - (Parms["YMin"] * Mult.y)

else

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

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

end

-- Chart Origin offsets

if DoHorizontal then

Pos.Origin.v = round(Siz.Space.Top

+ (Parms["XMax"] * Mult.x), 2)

Pos.Origin.h = round(Siz.Space.Left

+ ((0 - Parms["YMin"]) * Mult.y), 2)

else

Pos.Origin.v = round(Siz.Space.Top

+ (Parms["YMax"] * Mult.y), 2)

Pos.Origin.h = round(Siz.Space.Left

+ ((0 - Parms["XMin"]) * Mult.x), 2)

end

-- Image size

ImageWidth = round(Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right, 0)

ImageHeight = round(Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom, 0)

end

function allChartAdjust()

Adjusts.ChartWidth = {}

Adjusts.ChartWidth.Old = Siz.ChartWidth

Adjusts.ChartHeight = {}

Adjusts.ChartHeight.Old = Siz.ChartHeight

Adjusts.XMax = {}

Adjusts.XMax.Old = Parms["XMax"]

Adjusts.XMin = {}

Adjusts.XMin.Old = Parms["XMin"]

Adjusts.YMax = {}

Adjusts.YMax.Old = Parms["YMax"]

Adjusts.YMin = {}

Adjusts.YMin.Old = Parms["YMin"]

if DoHorizontal then

Siz.ChartWidth = chartAdjust(Siz.ChartWidth,

"YAxisValueStep", "YAxisMark2Step",

"YMax", "YMin")

if #GroupText == 0 then

Siz.ChartHeight = chartAdjust(Siz.ChartHeight,

"XAxisValueStep", "XAxisMark2Step",

"XMax", "XMin")

end

else

if #GroupText == 0 then

Siz.ChartWidth = chartAdjust(Siz.ChartWidth,

"XAxisValueStep", "XAxisMark2Step",

"XMax", "XMin")

end

Siz.ChartHeight = chartAdjust(Siz.ChartHeight,

"YAxisValueStep", "YAxisMark2Step",

"YMax", "YMin")

end

Adjusts.ChartWidth.New = Siz.ChartWidth

Adjusts.ChartHeight.New = Siz.ChartHeight

Adjusts.XMax.New = Parms["XMax"]

Adjusts.XMin.New = Parms["XMin"]

Adjusts.YMax.New = Parms["YMax"]

Adjusts.YMin.New = Parms["YMin"]

end

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

local unit = Parms[mark]

if Parms[mark2] ~= nil then

unit = Parms[mark2]

end

Parms[max] = round((Parms[max] / unit) + 0.49, 0) * unit

Parms[min] = round((Parms[min] / unit) - 0.49, 0) * unit

local count = (Parms[max] - Parms[min]) / unit

return round(userlength / count, 0) * count

end

function outputAdjusts()

table.insert(r, " <!-- Adjustments made by Charts SVG:")

table.insert(r, " ChartWidth: " .. Adjusts.ChartWidth.Old .. " : " .. Adjusts.ChartWidth.New)

table.insert(r, " ChartHeight: " .. Adjusts.ChartHeight.Old .. " : " .. Adjusts.ChartHeight.New)

table.insert(r, " XMax: " .. Adjusts.XMax.Old .. " : " .. Adjusts.XMax.New)

table.insert(r, " XMin: " .. Adjusts.XMin.Old .. " : " .. Adjusts.XMin.New)

table.insert(r, " YMax: " .. Adjusts.YMax.Old .. " : " .. Adjusts.YMax.New)

table.insert(r, " YMin: " .. Adjusts.YMin.Old .. " : " .. Adjusts.YMin.New)

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

table.insert(r, " ")

end

function prelimsPie()

Siz.Space = {}

-- pie chart radius & origin

PieRadius = Parms["PieRadius"] -- not local

PieOriginX, PieOriginY = PieRadius, PieRadius -- not local

if Parms["Explode"] ~= nil then

PieOriginX = PieOriginX + (PieRadius * Parms["ExplodeRadius"] / 100)

PieOriginY = PieOriginY + (PieRadius * Parms["ExplodeRadius"] / 100)

end

if Parms["SegmentText"] ~= nil then

-- possibly add for segment texts outside the pie

local TextSpaceX = (PieRadius * Parms["SegmentTextRadius"] / 100)

+ (5 * Siz.Text.Chart * Parms["SegmentTextWidth"] / 100)

if Parms["Explode"] ~= nil then

TextSpaceX = TextSpaceX + (PieRadius * Parms["ExplodeRadius"] / 100)

end

PieOriginX = math.max(PieOriginX, TextSpaceX)

local TextSpaceY = (PieRadius * Parms["SegmentTextRadius"] / 100)

+ (Siz.Text.Chart)

if Parms["Explode"] ~= nil then

TextSpaceY = TextSpaceY + (PieRadius * Parms["ExplodeRadius"] / 100)

end

PieOriginY = math.max(PieOriginY, TextSpaceY)

end

-- for pie charts, chart size is calculated, not user-defined

Siz.ChartWidth = PieOriginX * 2

Siz.ChartHeight = PieOriginY * 2

prelimsLegend()

-- Top Space

Siz.Space.Top = Siz.ImagePadding.Top + Siz.Text.Title + Siz.ChartMargin

Pos.Title = Siz.ImagePadding.Top + FontSiz.Title

-- Bottom Space

Siz.Space.Bottom = Siz.ChartMargin

if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then

Pos.Legend = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset

Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline

end

if Parms["Footnote"] ~= nil then

Pos.Footnote = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Footnote

Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote

end

Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom

-- Left Space

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

-- Right Space

Siz.Space.Right = Siz.ChartMargin

if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then

Pos.Legend = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + Siz.Legend.Offset

Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width

end

Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right

-- Image size

ImageWidth = round(Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right, 0)

ImageHeight = round(Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom, 0)

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

Mult.x = Siz.ChartWidth / 100

Mult.y = Siz.ChartHeight / 100

Pos.XAxis = {}

Pos.YAxis = {}

Pos.XAxis.Zero = Siz.Space.Left

Pos.YAxis.Zero = Siz.Space.Top + Siz.ChartHeight

end

function prelimsLegend()

-- legend height and width

Siz.Legend.ElementWidth = Siz.ChartMargin + (2 * Siz.Legend.Text) + Siz.ChartMargin + Siz.Legend.TextWidth

Siz.Legend.ElementHeight = Siz.Legend.Text + Siz.Text.Interline

LegendElementsInWidth = 1 -- not local

if #SeriesText < 1 then

Parms["LegendType"] = KeyWords["none"]

elseif Parms["LegendType"] == KeyWords["horizontal"] then

LegendElementsInWidth = math.floor(Siz.ChartWidth / Siz.Legend.ElementWidth)

LegendElementsInWidth = math.min(LegendElementsInWidth, #SeriesText)

local LegendLines = math.ceil(#SeriesText / LegendElementsInWidth)

Siz.Legend.Width = (LegendElementsInWidth * Siz.Legend.ElementWidth) + Siz.ChartMargin

Siz.Legend.Height = Siz.ChartMargin + (LegendLines * Siz.Legend.ElementHeight) + Siz.ChartMargin

else

Siz.Legend.Width = Siz.Legend.ElementWidth + Siz.ChartMargin

Siz.Legend.Height = Siz.ChartMargin + (#SeriesText * Siz.Legend.ElementHeight) + Siz.ChartMargin

end

end

function commonTop()

-- output header

table.insert(r, MessageTexts.CopyText)

table.insert(r, " ")

-- SVG header stuff

table.insert(r, " ")

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

table.insert(r, " <!-- Generator Version: 3.0 -->")

table.insert(r, "

table.insert(r, " 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, " xmlns:rdf = \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"")

table.insert(r, " xmlns:rdfs = \"http://www.w3.org/2000/01/rdf-schema#\"")

table.insert(r, " xmlns:dc = \"http://purl.org/dc/elements/1.1/\" >")

table.insert(r, "

table.insert(r, " dc:title=\"" .. Parms["FileTitle"] .. "\"")

table.insert(r, " dc:description=\"" .. Parms["FileDesc"] .. "\"")

table.insert(r, " dc:date=\"" .. DT.year .. "-" .. DT.month .. "-" .. DT.day .. "\"")

table.insert(r, " dc:format=\"image/svg+xml\"")

table.insert(r, " dc:language=\"en\" >")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

if not DoPie and DoChartAdjust then

outputAdjusts()

end

table.insert(r, " <!-- == Backgrounds == -->")

table.insert(r, " ")

-- image background rectangle

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

table.insert(r, "

.. " stroke-width=\"1\""

.. " stroke=\"" .. Parms["ImageBorder"] .. "\""

.. " fill=\"" .. Parms["ImageBackgroundColor"] .. "\""

.. "/>")

table.insert(r, " ")

if Parms["ImageBackgroundSVG"] ~= nil then

table.insert(r, " <!-- Image Background SVG -->")

table.insert(r, " " .. Parms["ImageBackgroundSVG"] .. "")

table.insert(r, " ")

end

-- chart background

if Parms["ChartBackgroundColor"] ~= nil then

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

table.insert(r, "

.. " width=\"" .. Siz.ChartWidth .. "\""

.. " height=\"" .. Siz.ChartHeight .. "\""

.. " fill=\"" .. Parms["ChartBackgroundColor"] .. "\"/>")

table.insert(r, " ")

end

end

function codeAxisGrids()

table.insert(r, " <!-- == Axis Chart - Translate == -->")

table.insert(r, " ")

table.insert(r, " ")

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

-- and vice versa

local HGridInterval, VGridInterval

local HLabel = 'x'

local VLabel = 'y'

if DoHorizontal then

HLabel = 'y'

VLabel = 'x'

end

if (HLabel == 'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"] == KeyWords["none"] then

-- no horizontal grid

else

HGridInterval = Parms[string.upper(HLabel) .. "Grid"] * Mult[HLabel]

end

if (VLabel == 'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"] == KeyWords["none"] then

-- no vertical grid

else

VGridInterval = Parms[string.upper(VLabel) .. "Grid"] * Mult[VLabel]

end

if Parms["XGrid"] ~= KeyWords["none"] or Parms["YGrid"] ~= KeyWords["none"] then

table.insert(r, " <!-- == Axis Chart - Grids == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

if (HLabel == 'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"] == KeyWords["none"] then

-- no horizontal grid

else

table.insert(r, " <!-- " .. HLabel .. "-axis grid, vertical lines -->")

table.insert(r, "

.. " height=\"10\""

.. " width=\"" .. round(HGridInterval, 2) .. "\""

.. " patternUnits=\"userSpaceOnUse\">")

table.insert(r, "

.. " y1=\"0\""

.. " x2=\"0\""

.. " y2=\"10\""

.. " class=\"gridline\"/>")

table.insert(r, " ")

end

if (VLabel == 'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"] == KeyWords["none"] then

-- no vertical grid

else

table.insert(r, " <!-- " .. VLabel .. "-axis grid, horizontal lines -->")

table.insert(r, "

.. " height=\"" .. round(VGridInterval, 2) .. "\""

.. " width=\"10\""

.. " patternUnits=\"userSpaceOnUse\">")

table.insert(r, "

.. " y1=\"0\""

.. " x2=\"10\""

.. " y2=\"0\""

.. " class=\"gridline\"/>")

table.insert(r, " ")

end

table.insert(r, " ")

table.insert(r, " ")

local XStart = Parms[string.upper(HLabel) .. "Min"] * Mult[HLabel]

local YStart = -Parms[string.upper(VLabel) .. "Max"] * Mult[VLabel]

if (HLabel == 'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"] == KeyWords["none"] then

-- no horizontal grid

else

table.insert(r, "

.. " x=\"" .. round(XStart, 2) .. "\""

.. " y=\"" .. round(YStart, 2) .. "\""

.. " width=\"" .. Siz.ChartWidth + BaseLineWidth .. "\""

.. " height=\"" .. Siz.ChartHeight .. "\""

.. " fill=\"url(#" .. HLabel .. "-gridline)\"/>")

end

if (VLabel == 'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"] == KeyWords["none"] then

-- no vertical grid

else

table.insert(r, "

.. " x=\"" .. round(XStart, 2) .. "\""

.. " y=\"" .. round(YStart, 2) .. "\""

.. " width=\"" .. Siz.ChartWidth .. "\""

.. " height=\"" .. Siz.ChartHeight + BaseLineWidth .. "\""

.. " fill=\"url(#" .. VLabel .. "-gridline)\"/>")

end

table.insert(r, " ")

end

end

function stylesAreas()

table.insert(r, " <!-- == Graph - Area Styles == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

end

function codeStyleArea(SeriesNumber, Color, Pattern)

-- area style for a series

-- SeriesNumber, numeric

-- Color, text

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

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

table.insert(r, " .series" .. SeriesNumber .. " {")

-- 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, " <!-- == 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, " <!-- 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 = "

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

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

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType >= 21 and PatternType <= 24 then

-- cross hatches, close

l = "

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

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType >= 31 and PatternType <= 34 then

-- cross hatches, wide

l = "

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

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType >= 41 and PatternType <= 49 then

-- stipples

if PatternType == 41 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 42 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 43 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 44 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 45 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 46 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 47 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 48 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 49 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

end

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

elseif PatternType >= 51 and PatternType <= 56 then

-- checks

if PatternType == 51 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 52 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 53 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 54 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 55 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 56 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

end

table.insert(r, " ")

elseif PatternType >= 61 and PatternType <= 64 then

-- circles

if PatternType == 61 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 62 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 63 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

elseif PatternType == 64 then

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

end

table.insert(r, " ")

end

end

function stylesLines()

table.insert(r, " <!-- == Graph - Line Styles == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

end

function codeStyleLineMarker(SeriesNumber, LineRequired, LineColor, LineWidth, LineDash, MarkerRequired, MarkerFill)

-- line and marker styles for a series

-- SeriesNumber, numeric

-- LineRequired

-- LineColor, text

-- LineWidth, numeric

-- LineDash, text

-- MarkerRequired

-- MarkerFill, text

if LineRequired ~= nil or MarkerRequired ~= nil then

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

-- line defined if either line or marker required

table.insert(r, " .series" .. SeriesNumber .. " {")

-- 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, " <!-- == Graph - Markers == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

for i = 1, #SeriesData do

if Marker[i] ~= nil and Marker[i] ~= KeyWords["none"] then

-- define the shape for the marker

codeDefMarkerShape(i, Marker[i], BaseUnit * 2 * MarkerSize[i] / 100, MarkerFill[i])

-- and use the shape in the definition of the marker

codeDefMarkerCreate(i, BaseUnit * 2 * MarkerSize[i] / 100)

table.insert(r, " ")

end

end

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

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

if MarkerType ~= nil then

-- MarkerType is a number, use it for the type

else

-- MarkerType is not a number, use the series number

MarkerType = SeriesNumber

end

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

if MarkerType == 2 then

-- circle

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

l = "

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, " <!-- triangle, point up -->")

l = "

if MarkerFill ~= nil then

l = l .. " fill=\"" .. MarkerFill .. "\""

end

table.insert(r, l .. "/>")

elseif MarkerType == 4 then

-- tilted square (diamond)

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

l = "

.. " 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, " <!-- triangle, point down -->")

l = "

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, " <!-- cross -->")

table.insert(r, " ")

elseif MarkerType == 7 then

-- plus

t = round(MarkerSize / 2, 2)

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

table.insert(r, " ")

else

-- default and 1

-- square

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

l = "

.. " 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, " ")

end

function codeDefMarkerCreate(SeriesNumber, MarkerSize)

-- definition of a marker for a series

-- SeriesNumber, numeric

-- MarkerSize, numeric

local l = ""

l = "

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

table.insert(r, " ")

end

function elementsGraphs()

local BarNumber = BarSeriesCount + 1

local DoLabels = false

local LabelPos = {}

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

table.insert(r, " ")

for i = #SeriesData, 1, -1 do

if SeriesText[i] == nil then

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

else

table.insert(r, " <!-- " .. SeriesText[i] .. " -->")

end

if DoYAxis2 and YAxis2[i] ~= nil then

table.insert(r, " <!-- Y values are on second Y axis -->")

end

if Parms["IncludeOriginalData"] == KeyWords["no"]

or (Parms["IncludeOriginalData"] == KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then

table.insert(r, " <!-- original data: not included -->")

else

table.insert(r, " <!-- original data:")

for k, v in ipairs(OriginalData[i]) do

table.insert(r, " " .. v[1] .. " " .. v[2])

end

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

end

LabelPos[i] = {}

if SType[i] == KeyWords["bar"] then

BarNumber = BarNumber - 1

for k, v in ipairs(SeriesData[i]) do

if v[2] ~= nil then

local px = tonumber(v[1])

if #GroupText > 0 then

if DoStack or DoStack100 then

px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth

+ UnitWidth / 2

+ (BarSpace / 2)

+ iif(DoHorizontal, BarWidth, 0)

else

px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth

+ UnitWidth / 2

+ UnitWidth * (iif(DoGroupsTopDown, BarSeriesCount - BarNumber + 1, BarNumber) - iif(DoHorizontal, 0, 1))

+ (BarSpace / 2)

end

else

px = (px * Mult.x) + (iif(DoHorizontal, 1, -1) * (BarWidth / 2))

end

local Min, Multiply = Parms["YMin"], Mult.y

if DoYAxis2 and YAxis2[i] ~= nil then

Min = Parms["Y2Min"]

Multiply = Mult.y2

end

local val = tonumber(v[2])

local posn, size = 0, 0

if DoHorizontal then

if Min >= 0 then

posn = 0

size = val - Min

elseif val >= 0 then

posn = 0

size = val

else

posn = -val

size = -val

end

posn = Pos.XAxis.Line - (posn * Multiply)

size = size * Multiply

table.insert(r, "

.. " x=\"" .. round(posn, 2) .. "\" y=\"" .. round(-px, 2) .. "\""

.. " width=\"" .. round(size, 2) .. "\" height=\"" .. round(BarWidth, 2) .. "\" />")

else

if Min >= 0 then

posn = val - Min

size = val - Min

elseif val >= 0 then

posn = val

size = val

else

posn = 0

size = -val

end

posn = Pos.XAxis.Line - (posn * Multiply)

size = size * Multiply

table.insert(r, "

.. " x=\"" .. round(px, 2) .. "\" y=\"" .. round(posn, 2) .. "\""

.. " width=\"" .. round(BarWidth, 2) .. "\" height=\"" .. round(size, 2) .. "\" />")

end

if Labels[i] ~= nil then

LabelPos[i][k] = {}

if DoHorizontal then

LabelPos[i][k]["y"] = -(px - (BarWidth / 2))

if val <= 0 then

LabelPos[i][k]["x"] = posn - Siz.Text.Interline

else

LabelPos[i][k]["x"] = posn + size + Siz.Text.Interline

end

else

LabelPos[i][k]["x"] = px + (BarWidth / 2)

if val <= 0 then

LabelPos[i][k]["y"] = posn + size + Siz.Text.Interline + Siz.Text.Labels

else

LabelPos[i][k]["y"] = posn - Siz.Text.Interline

end

end

DoLabels = true

end

end

end

else

-- line

tr = "

if DoArea then

tr = tr .. "areas"

else

tr = tr .. "lines"

end

tr = tr .. "-general series" .. i .. "\""

table.insert(r, tr)

if not DoArea and 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 = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth

+ (GroupWidth / 2)

else

px = px * Mult.x

end

local py = tonumber(v[2])

if DoYAxis2 and YAxis2[i] ~= nil then

py = py * Mult.y2

else

py = py * Mult.y

end

if DoHorizontal then

if DoArea and k == 1 then

table.insert(r, " " .. round(iif(Parms["YMin"] > 0, Parms["YMin"] * Mult.y, 0), 2) .. ", " .. round(-px, 2) .. "")

end

table.insert(r, " " .. round(py, 2) .. ", " .. round(-px, 2) .. "")

lastx = px

if Labels[i] ~= nil then

LabelPos[i][k] = {}

LabelPos[i][k]["x"] = px + Siz.Text.Interline

LabelPos[i][k]["y"] = py + Siz.Text.Interline

DoLabels = true

end

else

if DoArea and k == 1 then

table.insert(r, " " .. round(px, 2) .. ", " .. round(iif(Parms["YMin"] > 0, -Parms["YMin"] * Mult.y, 0), 2) .. "")

end

table.insert(r, " " .. round(px, 2) .. ", " .. round(-py, 2) .. "")

lastx = px

if Labels[i] ~= nil then

LabelPos[i][k] = {}

LabelPos[i][k]["x"] = px + Siz.Text.Interline

LabelPos[i][k]["y"] = py + Siz.Text.Interline

DoLabels = true

end

end

end

if DoArea then

if DoHorizontal then

table.insert(r, " " .. round(iif(Parms["YMin"] > 0, Parms["YMin"] * Mult.y, 0), 2) .. ", " .. round(-lastx, 2) .. "")

else

table.insert(r, " " .. round(lastx, 2) .. ", " .. round(iif(Parms["YMin"] > 0, -Parms["YMin"] * Mult.y, 0), 2) .. "")

end

end

table.insert(r, " \"/>")

end

table.insert(r, " ")

end

if not DoStack100 and DoLabels then

table.insert(r, " <!-- == Data Labels == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

for i = #SeriesData, 1, -1 do

if Labels[i] ~= nil then

for k, v in ipairs(SeriesData[i]) do

if SType[i] == KeyWords["bar"] then

l = "

.. " x=\"" .. round(LabelPos[i][k]["x"], 2) .. "\""

.. " y=\"" .. round(LabelPos[i][k]["y"], 2) .. "\""

if DoHorizontal then

l = l .. " text-anchor=\"" .. iif(tonumber(v[2]) <= 0, "end", "start") .. "\""

.. " transform=\"translate(" .. 0 .. ", " .. round(Siz.Text.Labels / 3) .. ")\""

else

l = l .. " text-anchor=\"middle\""

end

l = l .. ">" .. v[2]

.. ""

table.insert(r, l)

else

if DoHorizontal then

table.insert(r, "

.. " x=\"" .. round(LabelPos[i][k]["y"], 2) .. "\""

.. " y=\"" .. round(-LabelPos[i][k]["x"], 2) .. "\""

.. " text-anchor=\"left\">"

.. v[2]

.. "")

else

table.insert(r, "

.. " x=\"" .. round(LabelPos[i][k]["x"], 2) .. "\""

.. " y=\"" .. round(-LabelPos[i][k]["y"], 2) .. "\""

.. " text-anchor=\"left\">"

.. v[2]

.. "")

end

end

end

table.insert(r, " ")

end

end

end

end

function elementsPie()

table.insert(r, " <!-- == Pie Segments == -->")

table.insert(r, " ")

table.insert(r, " ")

if Parms["IncludeOriginalData"] == KeyWords["no"]

or (Parms["IncludeOriginalData"] == KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then

table.insert(r, " <!-- original data: not included -->")

else

table.insert(r, " <!-- original data:")

for k, v in ipairs(OriginalData) do

table.insert(r, " " .. v[1] .. " " .. v[2])

end

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

end

local Total = 0

for k, v in ipairs(SeriesData) do

Total = Total + v[2]

end

local TwoPi = math.pi * 2

local Sweep = 0

if Parms["PieSweepDir"] ~= "AntiClockwise" then

-- change arcs to clockwise

Sweep = 1

end

local Radian = 0

if Parms["PieStartAngle"] ~= 0 then

Radian = math.rad(Parms["PieStartAngle"])

end

local InnerX, InnerY, InnerRadius = 0, 0, 0

if Parms["DoughnutHole"] == nil then

-- no doughnut, segments start at pie origin (0, 0)

else

-- doughnut, segments start at the hole radius

InnerRadius = Parms["DoughnutHole"] / 100 * PieRadius

InnerX = math.cos(Radian) * InnerRadius

InnerY = math.sin(Radian) * InnerRadius

end

-- outer arcs start at the pie radius

local OuterX = math.cos(Radian) * PieRadius

local OuterY = math.sin(Radian) * PieRadius

local Mid = {}

for k, v in ipairs(SeriesData) do

local Arc = 0 -- default is short arc (<= 180 degrees)

if (v[2] / Total * TwoPi) > math.pi then

Arc = 1 -- long arc

end

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

-- adjust X and Y for explode

local DX, DY = 0, 0

if Parms["Explode"] == nil or (type(Parms["Explode"]) == "number" and k > Parms["Explode"]) then

-- no explode for this segment

else

DX = math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)

DY = math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)

end

Radian = Radian + (v[2] / Total * TwoPi * iif(Sweep == 0, 1, -1))

local NextOuterX = math.cos(Radian) * PieRadius

local NextOuterY = math.sin(Radian) * PieRadius

t = "

.. " l " .. round(OuterX - InnerX, 2) .. ", " .. -round(OuterY - InnerY, 2)

.. " a " .. PieRadius .. ", " .. PieRadius .. " 0 " .. Arc .. " " .. Sweep .. " " .. round(NextOuterX - OuterX, 2) .. ", " .. -round(NextOuterY - OuterY, 2)

if Parms["DoughnutHole"] == nil then

t = t .. " z\" />"

else

local NextInnerX = math.cos(Radian) * InnerRadius

local NextInnerY = math.sin(Radian) * InnerRadius

t = t .. " l " .. round(NextInnerX - NextOuterX, 2).. ", " .. -round(NextInnerY - NextOuterY, 2)

.. " a " .. InnerRadius .. ", " .. InnerRadius .. " 0 " .. Arc .. " " .. iif(Sweep == 0, 1, 0) .. " " .. round(InnerX - NextInnerX, 2) .. ", " .. -round(InnerY - NextInnerY, 2) .. "\" />"

InnerX = NextInnerX

InnerY = NextInnerY

end

table.insert(r, t)

OuterX = NextOuterX

OuterY = NextOuterY

end

table.insert(r, " ")

table.insert(r, " ")

if Parms["SegmentText"] ~= nil then

-- pie segment texts

table.insert(r, " <!-- == Pie Segment Texts == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

for k, v in ipairs(SeriesData) do

-- adjust X and Y for explode

local DX, DY = 0, 0

if Parms["Explode"] == nil or (type(Parms["Explode"]) == "number" and k > Parms["Explode"]) then

-- no explode for this segment

else

DX = math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)

DY = math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)

end

local TextX = math.cos(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100)

local TextY = math.sin(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100)

if TextY < 0 then

TextY = TextY - FontSiz.LegendText

end

t = "

.. " x=\"" .. round(TextX + DX, 2) .. "\" y=\"" .. -round(TextY + DY, 2) .. "\""

t = t .. " text-anchor=\"" .. iif(TextX < 0, "end", "start") .. "\">"

local tt = ""

if string.find(Parms["SegmentText"], KeyWords["text"], 1, true) ~= nil then

tt = SeriesText[k]

end

if string.find(Parms["SegmentText"], KeyWords["value"], 1, true) ~= nil then

tt = tt .. iif(string.len(tt) > 0, " ", "") .. v[2]

end

if string.find(Parms["SegmentText"], KeyWords["percent"], 1, true) ~= nil then

tt = tt .. iif(string.len(tt) > 0, " ", "") .. round(v[2] / Total * 100, 0) .. "%"

end

t = t .. tt

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

end

table.insert(r, " ")

table.insert(r, " ")

end

end

function codeAxes()

if Parms["Debug"] ~= nil and string.find(Parms["Debug"], "pos") ~= nil then

-- show positions tables

table.insert(r, "<!--")

listTable(Mult, " ", "Mult")

listTable(FontSiz, " ", "FontSiz")

table.sort(Pos)

listTable(Pos, " ", "Pos")

table.sort(Siz)

listTable(Siz, " ", "Siz")

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

end

-- axis styles

table.insert(r, " <!-- == Axis Styles == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " <!-- == Axis Marks == -->")

table.insert(r, " ")

if DoHorizontal then

codeAxisMarks('x',

'y',

-1,

Pos.XAxis.Line,

-Parms["XMax"],

Siz.ChartHeight)

codeAxisMarks('y',

'x',

0,

Pos.YAxis.Line,

Parms["YMin"],

Siz.ChartWidth)

if DoYAxis2 then

codeAxisMarks('y2',

'x',

-1,

Pos.YAxis2.Line,

Parms["Y2Min"],

Siz.ChartWidth)

end

else

codeAxisMarks('x',

'x',

0,

Pos.XAxis.Line,

Parms["XMin"],

Siz.ChartWidth)

codeAxisMarks('y',

'y',

-1,

Pos.YAxis.Line,

-Parms["YMax"],

Siz.ChartHeight)

if DoYAxis2 then

codeAxisMarks('y2',

'y',

0,

Pos.YAxis2.Line,

-Parms["Y2Max"],

Siz.ChartHeight)

end

end

if Parms["XAxisArrows"] ~= nil or Parms["YAxisArrows"] ~= nil then

table.insert(r, " <!-- == Axis Arrows == -->")

table.insert(r, " ")

local ArrowSize = BaseLineWidth * 12

table.insert(r, " ")

-- start arrow, for axes

if Parms["XMin"] < 0 or Parms["YMin"] < 0 then

table.insert(r, " ")

t = round(ArrowSize / 2, 2)

table.insert(r, " ")

table.insert(r, " ")

end

table.insert(r, " ")

t = round(ArrowSize / 2, 2)

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

if Parms["XAxisArrows"] ~= nil then

if Parms["XMin"] < 0 then

l = "

l = l .. " class=\"axis-arrow axis-arrow-x\""

l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""

.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""

.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""

.. " overflow=\"visible\""

.. " orient=\"auto\""

.. " markerUnits=\"userSpaceOnUse\">"

table.insert(r, l)

table.insert(r, " ")

table.insert(r, " ")

end

l = "

l = l .. " class=\"axis-arrow axis-arrow-x\""

l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""

.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""

.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""

.. " overflow=\"visible\""

.. " orient=\"auto\""

.. " markerUnits=\"userSpaceOnUse\">"

table.insert(r, l)

table.insert(r, " ")

table.insert(r, " ")

end

if Parms["YAxisArrows"] ~= nil then

if Parms["YMin"] < 0 then

l = "

l = l .. " class=\"axis-arrow axis-arrow-y\""

l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""

.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""

.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""

.. " overflow=\"visible\""

.. " orient=\"auto\""

.. " markerUnits=\"userSpaceOnUse\">"

table.insert(r, l)

table.insert(r, " ")

table.insert(r, " ")

end

l = "

l = l .. " class=\"axis-arrow axis-arrow-y\""

l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""

.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""

.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""

.. " overflow=\"visible\""

.. " orient=\"auto\""

.. " markerUnits=\"userSpaceOnUse\">"

table.insert(r, l)

table.insert(r, " ")

table.insert(r, " ")

end

table.insert(r, " ")

table.insert(r, " ")

end

table.insert(r, " <!-- == Axis Lines == -->")

table.insert(r, " ")

if DoHorizontal then

codeAxisLine('x', 'y', Pos.XAxis.Line, -Parms["XMin"], -Siz.ChartHeight)

codeAxisLine('y', 'x', Pos.YAxis.Line, Parms["YMin"], Siz.ChartWidth)

if DoYAxis2 then

codeAxisLine('y2', 'x', Pos.YAxis2.Line, Parms["Y2Min"], Siz.ChartWidth)

end

else

codeAxisLine('x', 'x', Pos.XAxis.Line, Parms["XMin"], Siz.ChartWidth)

codeAxisLine('y', 'y', Pos.YAxis.Line, -Parms["YMin"], -Siz.ChartHeight)

if DoYAxis2 then

codeAxisLine('y2', 'y', Pos.YAxis2.Line, -Parms["Y2Min"], -Siz.ChartHeight)

end

end

table.insert(r, " ")

Format = {}

if #GroupText == 0 then

Format.x = (Parms["XMax"] >= 10000)

if Parms["XAxisValueFormat"] ~= nil then

Format.x = not (Parms["XAxisValueFormat"] == 'none')

end

end

Format.y = (Parms["YMax"] >= 10000)

if Parms["YAxisValueFormat"] ~= nil then

Format.y = not (Parms["YAxisValueFormat"] == 'none')

end

if DoYAxis2 then

Format.y2 = (Parms["Y2Max"] >= 10000)

if Parms["YAxis2ValueFormat"] ~= nil then

Format.y2 = not (Parms["YAxis2ValueFormat"] == 'none')

end

end

table.insert(r, " <!-- == Axis Values == -->")

table.insert(r, " ")

if DoHorizontal then

codeAxisValues('x', 'y',

Parms["XAxisValueStep"],

-1, -1,

Parms["XMin"], Parms["XMax"],

FontSiz["XAxisValues"] / 3, Pos.XAxis.Values,

Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"], Parms["XAxisValueAbsolute"],

Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"],

Format.x)

codeAxisValues('y', 'x',

Parms["YAxisValueStep"],

0, 1,

Parms["YMin"], Parms["YMax"],

0, Pos.YAxis.Values,

Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"], Parms["YAxisValueAbsolute"],

Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"],

Format.y)

if DoYAxis2 then

codeAxisValues('y2', 'x',

Parms["YAxis2ValueStep"],

-1, 1,

Parms["Y2Min"], Parms["Y2Max"],

0, Pos.YAxis2.Values,

Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"], Parms["YAxis2ValueAbsolute"],

Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"],

Format.y2)

end

else

codeAxisValues('x', 'x',

Parms["XAxisValueStep"],

0, 1,

Parms["XMin"], Parms["XMax"],

0, Pos.XAxis.Values,

Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"], Parms["XAxisValueAbsolute"],

Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"],

Format.x)

codeAxisValues('y', 'y',

Parms["YAxisValueStep"],

-1, -1,

Parms["YMin"], Parms["YMax"],

FontSiz["YAxisValues"] / 3, Pos.YAxis.Values,

Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"], Parms["YAxisValueAbsolute"],

Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"],

Format.y)

if DoYAxis2 then

codeAxisValues('y2', 'y',

Parms["YAxis2ValueStep"],

0, -1,

Parms["Y2Min"], Parms["Y2Max"],

FontSiz["YAxis2Values"] / 3, Pos.YAxis2.Values,

Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"], Parms["YAxis2ValueAbsolute"],

Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"],

Format.y2)

end

end

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

table.insert(r, " ")

table.insert(r, " ")

-- axis titles are always outside the chart, so are outside the chart translate

if Parms["XAxisTitle"] ~= nil

or Parms["YAxisTitle"] ~= nil

or (DoYAxis2 and Parms["YAxis2Title"] ~= nil) then

table.insert(r, " <!-- == Axis Titles == -->")

table.insert(r, " ")

if DoHorizontal then

if Parms["XAxisTitle"] ~= nil then

codeAxisTitle('x', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5))

end

if Parms["YAxisTitle"] ~= nil then

codeAxisTitle('y', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5))

end

if DoYAxis2 and Parms["YAxis2Title"] ~= nil then

codeAxisTitle('y2', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5))

end

else

if Parms["XAxisTitle"] ~= nil then

codeAxisTitle('x', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5))

end

if Parms["YAxisTitle"] ~= nil then

codeAxisTitle('y', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5))

end

if DoYAxis2 and Parms["YAxis2Title"] ~= nil then

codeAxisTitle('y2', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5))

end

end

table.insert(r, " ")

end

end

function codeAxisMarks(AxisName,

ChangeDim,

MarkOffset,

AxisLinePos,

AxisStart,

LineLength)

-- AxisName, text: the name of the axis being coded, "x", "y" or "y2"

-- ChangeDim, text: 'x' or 'y' - the dimension the axis actually changes in

-- MarkOffset, numeric: direction of mark start away from the line, -1 or 0

-- AxisLinePos, numeric: position of the axis, ie: distance of the line from the chart origin point

-- AxisStart, numeric: position of the start of the axis line

-- LineLength, numeric: the length of the axis line

local OtherDim = "y"

if ChangeDim == "y" then

OtherDim = "x"

end

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

if AxisName == "y2" then

AxisParmName = "YAxis2"

end

local MarkGapDim = "width"

local MarkSizDim = "height"

if ChangeDim == "y" then

MarkGapDim = "height"

MarkSizDim = "width"

end

if AxisName == "x" and #GroupText ~= 0 then

-- no marks for x axis with groups

else

table.insert(r, " ")

table.insert(r, "

.. " " .. MarkGapDim .. "=\"" .. round(Parms[AxisParmName .. "ValueStep"] * Mult[AxisName], 2) .. "\""

.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark .. "\" patternUnits=\"userSpaceOnUse\">")

table.insert(r, " ")

table.insert(r, " ")

if Parms[AxisParmName .. "Mark2Step"] ~= nil then

table.insert(r, "

.. " " .. MarkGapDim .. "=\"" .. round(Parms[AxisParmName .. "Mark2Step"] * Mult[AxisName], 2) .. "\""

.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark2 .. "\" patternUnits=\"userSpaceOnUse\">")

table.insert(r, " ")

table.insert(r, " ")

end

table.insert(r, " ")

table.insert(r, " ")

if Parms[AxisParmName .. "Mark2Step"] ~= nil then

table.insert(r, "

.. " " .. ChangeDim .. "=\"" .. round(AxisStart * Mult[AxisName], 2) .. "\""

.. " " .. OtherDim .. "=\"" .. round(AxisLinePos + (Siz.AxisMark2 * MarkOffset), 2) .. "\""

.. " " .. MarkGapDim .. "=\"" .. LineLength + BaseLineWidth .. "\""

.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark2 .. "\""

.. " fill=\"url(#" .. AxisName .. "-axismark-second)\"/>")

end

table.insert(r, "

.. " " .. ChangeDim .. "=\"" .. round(AxisStart * Mult[AxisName], 2) .. "\""

.. " " .. OtherDim .. "=\"" .. round(AxisLinePos + (Siz.AxisMark * MarkOffset), 2) .. "\""

.. " " .. MarkGapDim .. "=\"" .. LineLength + BaseLineWidth .. "\""

.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark .. "\""

.. " fill=\"url(#" .. AxisName .. "-axismark-main)\"/>")

table.insert(r, " ")

end

end

function codeAxisLine(AxisName,

ChangeDim,

AxisLinePos,

AxisStart, LineLength)

local OtherDim = "y"

if ChangeDim == "y" then

OtherDim = "x"

end

local l = "

if Parms[string.upper(AxisName) .. "AxisArrows"] ~= nil then

if Parms[string.upper(AxisName) .. "Min"] < 0 then

l = l .. " marker-start=\"url(#axis-arrow-" .. AxisName .. "-start)\""

end

l = l .. " marker-end=\"url(#axis-arrow-" .. AxisName .. "-end)\""

end

l = l .. " " .. ChangeDim .. "1=\"" .. round(AxisStart * Mult[AxisName], 2) .. "\""

.. " " .. OtherDim .. "1=\"" .. round(AxisLinePos, 2) .. "\""

.. " " .. ChangeDim .. "2=\"" .. round((AxisStart * Mult[AxisName]) + LineLength, 2) .. "\""

.. " " .. OtherDim .. "2=\"" .. round(AxisLinePos, 2) .. "\" class=\"axisline-" .. AxisName .. "\"/>"

table.insert(r, l)

end

function codeAxisValues(AxisName,

ChangeDim,

ValueStep,

MarkOffset,

PosChangeSign,

ValueMin, ValueMax,

TextShift, OtherValuesPos,

ValueMultiplier, ValueRound, ValueAbsolute,

Prefix, Suffix,

Format)

-- AxisName, text: the name of the axis being coded, "x", "y" or "y2"

-- Parms for location

-- ChangeDim, text: 'x' or 'y' - the dimension the axis actually changes in

-- ValueStep, numeric: the step between values (and major marks)

-- MarkOffset, numeric: direction of mark start away from the line, -1 or 0

-- PosChangeSign, numeric: the position direction for +ve changes in value, 1 or -1

-- ValueMin, numeric: the minimum value shown

-- ValueMax, numeric: the maximum value shown

-- TextShift, numeric: amount to shift text, to get it centered with its marks

-- OtherValuesPos, numeric: alignment position for all values

-- Parms for values format

-- ValueMultiplier, numeric

-- ValueRound, numeric

-- ValueAbsulute

-- Prefix, text

-- Suffix, text

-- Format, boolean

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

if AxisName == "y2" then

AxisParmName = "YAxis2"

end

local Anchor = "middle"

local RotateText = ""

if ChangeDim == "y" then

if MarkOffset == -1 then

-- text is left of line

Anchor = "end"

else

-- text is right of line

Anchor = "start"

end

end

if Parms[AxisParmName .. "ValueRotate"] ~= nil then

RotateText = " transform=\"rotate(" .. Parms[AxisParmName .. "ValueRotate"] .. ", "

if ChangeDim == "x" then

Anchor = "start"

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

Anchor = "end"

elseif Parms[AxisParmName .. "ValueRotate"] >= 0 and MarkOffset == -1 then

Anchor = "end"

end

end

end

local Format = (Parms[string.upper(AxisName) .. "Max"] >= 10000)

if Parms[AxisParmName .. "ValueFormat"] ~= nil then

Format = not (Parms[AxisParmName .. "ValueFormat"] == 'none')

end

local l = ""

local Position = 0

if AxisName ~= 'x' or #GroupText == 0 then

-- numeric values

table.insert(r, "

.. " class=\"axisnumber-" .. AxisName .. "\""

.. " text-anchor=\"" .. Anchor .. "\""

.. " transform=\"translate("

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

.. iif(ChangeDim == "x", round(OtherValuesPos, 2), TextShift) .. ")\""

.. ">")

local ValueStart = 0

if ValueMin ~= 0 then

ValueStart = math.ceil(ValueMin / ValueStep) * ValueStep

end

local Value = ValueStart

while Value <= ValueMax do

Position = Value * Mult[AxisName] * PosChangeSign

l = "

if ChangeDim == 'x' then

if string.len(RotateText) > 0 then

l = l .. RotateText .. round(Position, 2) .. ", 0)\""

end

else

if string.len(RotateText) > 0 then

l = l .. RotateText .. "0, " .. round(Position - TextShift, 2) .. ")\""

end

end

l = l .. ">"

l = l .. Prefix

local v = Value

if ValueAbsolute ~= nil then

v = math.abs(v)

end

if Format then

l = l .. mw.getContentLanguage():formatNum(round(v * ValueMultiplier, ValueRound))

else

l = l .. round(v * ValueMultiplier, ValueRound)

end

l = l .. Suffix .. ""

table.insert(r, l)

Value = Value + ValueStep

end

else

-- group name values

table.insert(r, "

.. " class=\"axisnumber-" .. AxisName .. "\""

.. " text-anchor=\"" .. Anchor .. "\""

.. " transform=\"translate("

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

.. iif(ChangeDim == "x", round(OtherValuesPos, 2), TextShift) .. ")\""

.. ">")

for k, v in ipairs(GroupText) do

Position = ((iif(DoGroupsTopDown, #GroupText - k, k - 1) * GroupWidth)

+ (GroupWidth / 2)) * PosChangeSign

l = "

if ChangeDim == 'x' then

if string.len(RotateText) > 0 then

l = l .. RotateText .. round(Position, 2) .. ", 0)\""

end

else

if string.len(RotateText) > 0 then

l = l .. RotateText .. "0, " .. round(Position - TextShift, 2) .. ")\""

end

end

l = l .. ">" .. v .. ""

table.insert(r, l)

end

end

table.insert(r, " ")

table.insert(r, " ")

end

function codeAxisTitle(AxisName,

ChangeDim,

TitleCentre)

local OtherDim = "y"

if ChangeDim == "y" then

OtherDim = "x"

end

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

if AxisName == "y2" then

AxisParmName = "YAxis2"

end

local l = "

.. " " .. ChangeDim .. "=\"" .. round(TitleCentre, 2) .. "\""

.. " " .. OtherDim .. "=\"" .. round(Pos[AxisParmName]["Title"], 2) .. "\""

if ChangeDim == 'y' then

l = l .. " transform = \"rotate(-90, "

.. round(Pos[AxisParmName]["Title"], 2)

.. ", "

.. round(TitleCentre, 2)

.. ")\""

end

l = l .. " text-anchor=\"middle\">"

.. Parms[AxisParmName .. "Title"]

.. ""

table.insert(r, l)

end

function commonBottom()

if Parms["LegendType"] == KeyWords["none"]

and Parms["Title"] == nil

and Parms["Footnote"] == nil

and #ChartText <= 0 then

-- no common-element styles

else

table.insert(r, " <!-- == Common-element Styles == -->")

table.insert(r, " ")

table.insert(r, " ")

table.insert(r, " ")

end

-- legend

if Parms["LegendType"] == KeyWords["none"] then

-- no legend

else

table.insert(r, " <!-- == Legend == -->")

table.insert(r, " ")

if Parms["LegendSVG"] ~= nil then

-- replace all of legend with user-supplied SVG code

table.insert(r, " " .. Parms["LegendSVG"])

table.insert(r, " ")

else

tr = "

if Parms["LegendType"] == KeyWords["horizontal"] then

-- horizontal legend

if Parms["LegendX"] ~= nil then

if DoHorizontal then

tr = tr .. round(Pos.YAxis.Zero + (Parms["LegendY"] * Mult.y), 2) .. ""

.. ", " .. round(Pos.XAxis.Zero - (Parms["LegendX"] * Mult.x), 2)

else

tr = tr .. round(Pos.XAxis.Zero + (Parms["LegendX"] * Mult.x), 2) .. ""

.. ", " .. round(Pos.YAxis.Zero - (Parms["LegendY"] * Mult.y), 2)

end

else

tr = tr .. round(Siz.Space.Left, 2) .. ", " .. round(Pos.Legend, 2)

end

table.insert(r, tr .. ")\">")

table.insert(r, "

.. " width=\"" .. Siz.Legend.Width .. "\""

.. " height=\"" .. Siz.Legend.Height .. "\"/>")

local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin

for k, v in ipairs(SeriesText) do

table.insert(r, " ")

codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Marker[k])

PosX = PosX + Siz.Legend.ElementWidth

if math.fmod(k, LegendElementsInWidth) == 0 then

-- new line of legend elements

PosX = Siz.ChartMargin

PosY = PosY + Siz.Legend.ElementHeight

end

end

else

-- vertical legend

if Parms["LegendX"] ~= nil then

if DoHorizontal then

tr = tr .. round(Pos.YAxis.Zero + (Parms["LegendY"] * Mult.y), 2)

.. ", " .. round(Pos.XAxis.Zero - (Parms["LegendX"] * Mult.x), 2)

else

tr = tr .. round(Pos.XAxis.Zero + (Parms["LegendX"] * Mult.x), 2)

.. " " .. round(Pos.YAxis.Zero - (Parms["LegendY"] * Mult.y), 2)

end

else

tr = tr .. round(Pos.Legend, 2) .. ", " .. round(Siz.Space.Top + (0.1 * Siz.ChartHeight), 2)

end

table.insert(r, tr .. ")\">")

table.insert(r, "

.. " width=\"" .. Siz.Legend.Width .. "\""

.. " height=\"" .. Siz.Legend.Height .. "\"/>")

local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin

for k, v in ipairs(SeriesText) do

table.insert(r, " ")

codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Marker[k])

PosY = PosY + Siz.Legend.ElementHeight

end

end

table.insert(r, " ")

table.insert(r, " ")

end

end

-- chart texts

if #ChartText > 0 then

table.insert(r, " <!-- == Chart Texts == -->")

table.insert(r, " ")

table.insert(r, " ")

for i = 1, #ChartText do

if ChartText[i] ~= nil then

if DoHorizontal then

table.insert(r, "

.. "x=\"" .. round(Pos.YAxis.Zero + (Parms["ChartText" .. i .. "Y"] * Mult.y), 2) .. "\""

.. " " .. "y=\"" .. round(Pos.XAxis.Zero - (Parms["ChartText" .. i .. "X"] * Mult.x), 2) .. "\">"

.. ChartText[i] .. "")

else

table.insert(r, "

.. "x=\"" .. round(Pos.XAxis.Zero + (Parms["ChartText" .. i .. "X"] * Mult.x), 2) .. "\""

.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["ChartText" .. i .. "Y"] * Mult.y), 2) .. "\">"

.. ChartText[i] .. "")

end

end

end

table.insert(r, " ")

table.insert(r, " ")

end

-- title and footnote

if Parms["Title"] ~= nil then

table.insert(r, " <!-- == Title Text == -->")

tr = "

if Parms["TitleX"] ~= nil then

if DoHorizontal then

tr = tr .. " " .. "x=\"" .. round(Pos.YAxis.Zero + (Parms["TitleY"] * Mult.y), 2) .. "\""

.. " " .. "y=\"" .. round(Pos.XAxis.Zero - (Parms["TitleX"] * Mult.x), 2) .. "\">"

else

tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms["TitleX"] * Mult.x), 2) .. "\""

.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["TitleY"] * Mult.y), 2) .. "\">"

end

else

tr = tr .. " x=\"" .. round(Siz.Space.Left + Siz.ChartWidth * 0.5, 2) .. "\""

.. " y=\"" .. round(Pos.Title, 2) .. "\">"

end

table.insert(r, tr .. Parms["Title"] .. "")

table.insert(r, " ")

end

if Parms["Footnote"] ~= nil then

table.insert(r, " <!-- == Footnote Text == -->")

tr = "

if Parms["FootnoteX"] ~= nil then

if DoHorizontal then

tr = tr .. " " .. "x=\"" .. round(Pos.YAxis.Zero + (Parms["FootnoteY"] * Mult.y), 2) .. "\""

.. " " .. "y=\"" .. round(Pos.XAxis.Zero - (Parms["FootnoteX"] * Mult.x), 2) .. "\">"

else

tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms["FootnoteX"] * Mult.x), 2) .. "\""

.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["FootnoteY"] * Mult.y), 2) .. "\">"

end

else

tr = tr .. " x=\"" .. round(ImageWidth - Siz.ImagePadding.Right, 2) .. "\""

.. " y=\"" .. round(Pos.Footnote, 2) .. "\">"

end

table.insert(r, tr .. Parms["Footnote"] .. "")

table.insert(r, " ")

end

if Parms["ImageForegroundSVG"] ~= nil then

table.insert(r, " <!-- Image Foreground SVG -->")

table.insert(r, " " .. Parms["ImageForegroundSVG"] .. "")

table.insert(r, " ")

end

table.insert(r, " ")

table.insert(r, " ")

end

function codeLegendElement(Lines, SeriesNumber, PosX, PosY, Padding, TextSize, Text, Marker)

-- one entry in the legend

-- Lines, boolean

-- SeriesNumber, numeric

-- PosX, numeric

-- PosY, numeric (= the top of the legend element)

-- Padding, numeric

-- TextSize, numeric

-- Text, text

-- Marker, text

local l = ""

if Lines then

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

l = "

.. " points=\""

.. round(PosX, 2) .. "," .. lineY .. " "

.. round(PosX + (TextSize * 1), 2) .. "," .. lineY .. " "

.. round(PosX + (TextSize * 2), 2) .. "," .. lineY .. "\""

if Marker ~= nil and Marker ~= 0 then

l = l .. " marker-mid=\"url(#series" .. SeriesNumber .. "marker)\""

end

table.insert(r, l .. "/>")

table.insert(r, "

.. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 2) .. "\""

.. " y=\"" .. round(PosY + TextSize, 2) .. "\">"

.. Text .. "")

else

table.insert(r, "

.. " x=\"" .. round(PosX, 2) .. "\""

.. " y=\"" .. round(PosY, 2) .. "\""

.. " width=\"" .. TextSize * 2 .. "\""

.. " height=\"" .. TextSize .. "\"/>")

table.insert(r, "

.. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 2) .. "\""

.. " y=\"" .. round(PosY + TextSize, 2) .. "\">"

.. Text .. "")

end

end

function outputDebugInfo()

if Parms["Debug"] ~= nil and string.find(Parms["Debug"], KeyWords["parms"]) ~= nil then

-- list all parameters

table.insert(r, " All Parameters :")

for k, v in pairs(Args) do

table.insert(r, " " .. k .. "=" .. v)

end

table.insert(r, " ")

-- list all recognised parameters sorted by key

table.insert(r, " Parameters :")

for k, v in spairs(Parms) do

table.insert(r, " " .. k .. "=" .. v .. " (" .. type(v) .. ")")

end

table.insert(r, " ")

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

return false

end

end

if Parms["Debug"] ~= nil and string.find(Parms["Debug"], "tables") ~= nil then

-- list contents of internal tables

if DoStack or DoStack100 then

listTable(OriginalData, " ", "OriginalData")

end

listTable(SeriesData, " ", "SeriesData")

listTable(SType, " ", "SType")

listTable(YAxis2, " ", "YAxis2")

listTable(Labels, " ", "Labels")

listTable(Color, " ", "Color")

listTable(LineShow, " ", "LineShow")

listTable(LineWidth, " ", "LineWidth")

listTable(LineDash, " ", "LineDash")

listTable(Marker, " ", "Marker")

listTable(MarkerFill, " ", "MarkerFill")

listTable(MarkerSize, " ", "MarkerSize")

listTable(FillPattern, " ", "FillPattern")

listTable(FillPatternColor, " ", "FillPatternColor")

listTable(SeriesText, " ", "SeriesText")

listTable(GroupText, " ", "GroupText")

listTable(ChartText, " ", "ChartText")

-- listTable(FontSiz, " ", "FontSiz")

-- listTable(Siz, " ", "Siz")

-- listTable(Pos, " ", "Pos")

-- listTable(Mult, " ", "Mult")

-- listTable(Adjusts, " ", "Adjusts")

table.insert(r, " ")

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

return false

end

end

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

table.insert(r, " ")

return 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

function unknownParameters(knowns, regexps)

-- outputs a table of parameters either not in knowns, or not matching a pattern in regexps

-- knowns - a table where the values are the known parameters

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

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

local knownparms = {}

local output = {}

-- create the lists of known parms and regular expressions

for k, v in spairs(knowns) do

v = trim(v)

knownparms[v] = 1

end

for k, v in pairs(regexps) do

regexps[k] = '^' .. v .. '$'

end

-- check each entry in Args

for k, v in pairs(Args) do

if type(k) == 'string' and knownparms[k] == nil then

local knownflag = false

for i, regexp in ipairs(regexps) do

if mw.ustring.match(k, regexp) then

knownflag = true

break

end

end

if not knownflag then

local vlen = mw.ustring.len(v)

v = mw.ustring.sub(v, 1, (vlen < 25) and vlen or 25)

table.insert(output, k .. "=" .. v .. ((vlen >= 25) and ' ...' or ''))

end

elseif type(k) == 'number' and knownparms[tostring(k)] == nil then

local vlen = mw.ustring.len(v)

v = mw.ustring.sub(v, 1, (vlen < 25) and vlen or 25)

table.insert(output, k .. "=" .. v .. ((vlen >= 25) and ' ...' or ''))

end

end

return output

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 trim(s)

-- the match this pattern returns is the string with any spaces (%s) at the start(^) and end ($) not included

return s:match('^%s*(.-)%s*$')

end

function round(x, p)

-- round number x to precision p

-- p > 0 rounds to p decimal places, eg: round(4.57, 1) = 4.6

-- p = 0 (or not given) rounds to nearest integer, eg: round(6.6) = 7, round(6.5) = 6

-- p < 0 rounds to p places above zero, eg: round(147, -1) = 150

local res

if type(x) ~= "number" then

return nil

end

if type(p) == "number" then

-- any decimal places in p are ignored

p = math.floor(p)

else

p = nil

end

if p == nil or p == 0 then

return math.floor(x + 0.5)

else

res = x * 10^p

res = math.floor(res + 0.5)

res = res * 10^-p

return res

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,

}