モジュール: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
SeriesDatai = {}
transfer(Parms"Series" .. i .. "Values", SeriesDatai, 2)
DataPointsCount = DataPointsCount + #SeriesDatai
STypei = 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
SeriesDatai = {}
transfer(Parms"Series" .. i .. "Values", SeriesDatai, 2)
DataPointsCount = DataPointsCount + #SeriesDatai
STypei = 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
SeriesDatai = {}
transfer(Parms"Series" .. i .. "Values", SeriesDatai, 2)
DataPointsCount = DataPointsCount + #SeriesDatai
STypei = 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 STypei == KeyWords"bar" then
BarSeriesCount = BarSeriesCount + 1
end
end
for i = 1, SeriesCount do
SeriesDatai = {}
transfer(Parms"Series" .. i .. "Values", SeriesDatai, 2)
DataPointsCount = DataPointsCount + #SeriesDatai
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, GrayColori, DefColori)))
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
SeriesTextk = v1
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"  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 = KeyWordss1
if num ~= nil then
s = s .. num
end
if s2 ~= nil then
s = s .. KeyWordss2
end
local result
if Argss ~= nil then
result = Argss
if typ == "n" then
result = tonumber(result)
if result == nil then
table.insert(Msgs, string.format(MessageTexts.NotNumeric, s, Argss))
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  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
Tabi = {}
x = 1
end
Tabix = s
x = x + 1
end
else
for s in mw.text.gsplit(Parm, "%s+") do
i = i + 1
Tabi = 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 ParmsParmStart .. i .. ParmEnd ~= nil then
Tabi = ParmsParmStart .. i .. ParmEnd
elseif DefaultValue ~= nil then
Tabi = DefaultValue
elseif type(UseI) == "boolean" then
Tabi = i
elseif type(UseI) == "string" then
Tabi = UseI .. i
elseif DefaultTable ~= nil then
Tabi = DefaultTablei
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
PosTotalj = 0
NegTotalj = 0
for i = 1, #SeriesData do
if SeriesDataij ~= nil and tonumber(SeriesDataij1) == j then
if tonumber(SeriesDataij2)  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, "  " .. unknownsk)
end
for i = 1, #tmp do
table.insert(Msgs, tmpi)
end
end
if #GroupText > 0 then
for k1, v1 in ipairs(SeriesData) do
for k2, v2 in ipairs(v1) do
local t = tonumber(v21)
if t ~= math.floor(t)
or t  #GroupText then
table.insert(Msgs, string.format(MessageTexts.NotInGroup, k1, k2, v21))
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 = 1 and t = 1 and t = 11 and t = 21 and t = 31 and t = 41 and t = 51 and t = 61 and t  0 then
-- add messages to output
table.insert(r, " " .. MessageTexts.MsgHeading)
for i = 1, math.min(#Msgs, 10) do
table.insert(r, "   " .. Msgsi)
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, 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" ")
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " " .. Parms"FileTitle" .. "")
table.insert(r, " ")
table.insert(r, "   " .. Parms"FileDesc" .. "")
table.insert(r, " ")
table.insert(r, " ")
local DT = os.date("*t") -- returns a table with the current date & time
table.insert(r, " ")
table.insert(r, "   ")
table.insert(r, "     ")
table.insert(r, "     ")
table.insert(r, "   ")
table.insert(r, " ")
table.insert(r, " ")
if not DoPie and DoChartAdjust then
outputAdjusts()
end
table.insert(r, " ")
table.insert(r, " ")
-- image background rectangle
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
if Parms"ImageBackgroundSVG" ~= nil then
table.insert(r, " ")
table.insert(r, " " .. Parms"ImageBackgroundSVG" .. "")
table.insert(r, " ")
end
-- chart background
if Parms"ChartBackgroundColor" ~= nil then
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
end
end
function codeAxisGrids()
table.insert(r, " ")
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 Parmsstring.upper(HLabel) .. "Grid" == KeyWords"none" then
-- no horizontal grid
else
HGridInterval = Parmsstring.upper(HLabel) .. "Grid" * MultHLabel
end
if (VLabel == 'x' and #GroupText > 0) or Parmsstring.upper(VLabel) .. "Grid" == KeyWords"none" then
-- no vertical grid
else
VGridInterval = Parmsstring.upper(VLabel) .. "Grid" * MultVLabel
end
if Parms"XGrid" ~= KeyWords"none" or Parms"YGrid" ~= KeyWords"none" then
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, "  ")
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
if (HLabel == 'x' and #GroupText > 0) or Parmsstring.upper(HLabel) .. "Grid" == KeyWords"none" then
-- no horizontal grid
else
table.insert(r, "   ")
table.insert(r, "   ")
table.insert(r, "     ")
table.insert(r, "   ")
end
if (VLabel == 'x' and #GroupText > 0) or Parmsstring.upper(VLabel) .. "Grid" == KeyWords"none" then
-- no vertical grid
else
table.insert(r, "   ")
table.insert(r, "   ")
table.insert(r, "     ")
table.insert(r, "   ")
end
table.insert(r, " ")
table.insert(r, " ")

local XStart = Parmsstring.upper(HLabel) .. "Min" * MultHLabel
local YStart = -Parmsstring.upper(VLabel) .. "Max" * MultVLabel
if (HLabel == 'x' and #GroupText > 0) or Parmsstring.upper(HLabel) .. "Grid" == KeyWords"none" then
-- no horizontal grid
else
table.insert(r, " ")
end
if (VLabel == 'x' and #GroupText > 0) or Parmsstring.upper(VLabel) .. "Grid" == KeyWords"none" then
-- no vertical grid
else
table.insert(r, " ")
end
table.insert(r, " ")
end
end
function stylesAreas()
table.insert(r, " ")
table.insert(r, " ")
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 FillPatterni ~= nil and FillPatterni ~= KeyWords"none" then
exists = true
end
end
if exists then
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
for i = 1, SeriesCount do
if FillPatterni ~= nil and FillPatterni ~= KeyWords"none" then
-- define the pattern
codeDefFillPattern(i, FillPatterni, Colori, FillPatternColori)
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, "   ")
-- 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 ")
table.insert(r, "     ")
table.insert(r, "     ")
table.insert(r, "   ")
elseif PatternType >= 11 and PatternType ")
table.insert(r, "     ")
table.insert(r, "     ")
table.insert(r, "   ")
elseif PatternType >= 21 and PatternType ")
table.insert(r, "     ")
table.insert(r, "     ")
table.insert(r, "     ")
table.insert(r, "   ")
elseif PatternType >= 31 and PatternType ")
table.insert(r, "     ")
table.insert(r, "     ")
table.insert(r, "     ")
table.insert(r, "   ")
elseif PatternType >= 41 and PatternType ")
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 ")
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 ")
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, " ")
table.insert(r, " ")
table.insert(r, "   0 then
table.insert(r, "   /*-- general style of markers --*/")
table.insert(r, "   .graph-marker {")
table.insert(r, "     stroke-width:    " .. BaseLineWidth .. ";")
table.insert(r, "     fill:            white;")
table.insert(r, "     stroke-linejoin: round;")
table.insert(r, "   }")
end
for i = 1, #SeriesData do
if SeriesDatai ~= nil and STypei == KeyWords"line" then
local lw = 0
if LineWidthi ~= nil then
lw = BaseLineWidth * LineWidthi / 100
end
codeStyleLineMarker(i, LineShowi, Colori, lw, LineDashi, Markeri, MarkerFilli)
end
end
table.insert(r, " >")
table.insert(r, " ")
table.insert(r, " ")
end
function codeStyleLineMarker(SeriesNumber, LineRequired, LineColor, LineWidth, LineDash, MarkerRequired, MarkerFill)
-- line and marker styles for a series
-- SeriesNumber, numeric
-- LineRequired
-- LineColor, text
-- LineWidth, numeric
-- LineDash, text
-- MarkerRequired
-- MarkerFill, text
if LineRequired ~= nil or MarkerRequired ~= nil then
table.insert(r, "   /*-- series " .. SeriesNumber .. " --*/")
-- line defined if either line or marker required
table.insert(r, "   .series" .. SeriesNumber .. " {")
-- stroke for the line
if LineRequired == KeyWords"none" then
table.insert(r, "     stroke:           none;")
else
table.insert(r, "     stroke:           " .. LineColor .. ";")
table.insert(r, "     stroke-width:     " .. LineWidth .. ";")
-- dash array for the line
if LineDash ~= nil and LineDash ~= KeyWords"none" then
table.insert(r, "     stroke-dasharray: " .. LineDash .. ";")
table.insert(r, "     stroke-linecap:   butt;")
end
end
if MarkerRequired == nil or MarkerRequired == KeyWords"none" then
----close the line style
table.insert(r, "   }")
else
-- note: markers are set on the lines when they are created, not here in the line style
--   this enables the line in the legend to have only a mid-marker
table.insert(r, "   }")
-- define the marker stroke and fill
table.insert(r, "   .series" .. SeriesNumber .. "-marker {")
-- marker stroke color is always the same as the line
table.insert(r, "     stroke: " .. LineColor .. ";")
table.insert(r, "     fill:   " .. MarkerFill .. ";")
table.insert(r, "   }")
end
end
end
function defsMarkers()
local exists = false
for i = 1, #SeriesData do
if Markeri ~= nil and Markeri ~= KeyWords"none" then
exists = true
end
end
if exists then
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, "   ")
for i = 1, #SeriesData do
if Markeri ~= nil and Markeri ~= KeyWords"none" then
-- define the shape for the marker
codeDefMarkerShape(i, Markeri, BaseUnit * 2 * MarkerSizei / 100, MarkerFilli)
-- and use the shape in the definition of the marker
codeDefMarkerCreate(i, BaseUnit * 2 * MarkerSizei / 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, "       ")
l = "       ")
elseif MarkerType == 3 then
-- triangle (point up)
t = round(MarkerSize / 2, 2)
table.insert(r, "       ")
l = "       ")
elseif MarkerType == 4 then
-- tilted square (diamond)
table.insert(r, "       ")
l = "       ")
elseif MarkerType == 5 then
-- tilted triangle (point down)
t = round(MarkerSize / 2, 2)
table.insert(r, "       ")
l = "       ")
elseif MarkerType == 6 then
-- cross
t = round(MarkerSize / 2, 2)
table.insert(r, "       ")
table.insert(r, "       ")
elseif MarkerType == 7 then
-- plus
t = round(MarkerSize / 2, 2)
table.insert(r, "       ")
table.insert(r, "       ")
else
-- default and 1
-- square
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 = "     "
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, " ")
table.insert(r, " ")
for i = #SeriesData, 1, -1 do
if SeriesTexti  == nil then
table.insert(r, "   ")
else
table.insert(r, "   ")
end
if DoYAxis2 and YAxis2i ~= nil then
table.insert(r, "   ")
end
if Parms"IncludeOriginalData" == KeyWords"no"
or (Parms"IncludeOriginalData" == KeyWords"auto" and DataPointsCount > AutoDataPointsLimit) then
table.insert(r, "   ")
else
table.insert(r, "   ")
end
LabelPosi = {}
if STypei == KeyWords"bar" then
BarNumber = BarNumber - 1
for k, v in ipairs(SeriesDatai) do
if v2 ~= nil then
local px = tonumber(v1)
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 YAxis2i ~= nil then
Min = Parms"Y2Min"
Multiply = Mult.y2
end

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

if Labelsi ~= nil then
LabelPosik = {}
if DoHorizontal then
LabelPosik"y" = -(px - (BarWidth / 2))
if val  0 then
px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth
+ (GroupWidth / 2)
else
px = px * Mult.x
end
local py = tonumber(v2)
if DoYAxis2 and YAxis2i ~= 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 Labelsi ~= nil then
LabelPosik = {}
LabelPosik"x" = px + Siz.Text.Interline
LabelPosik"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 Labelsi ~= nil then
LabelPosik = {}
LabelPosik"x" = px + Siz.Text.Interline
LabelPosik"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, "   ")
table.insert(r, " ")
table.insert(r, "    ")
table.insert(r, "   ")
table.insert(r, " ")
for i = #SeriesData, 1, -1 do
if Labelsi ~= nil then
for k, v in ipairs(SeriesDatai) do
if STypei == KeyWords"bar" then
l = "   " .. v2
.. ""
table.insert(r, l)
else
if DoHorizontal then
table.insert(r, "   "
.. v2
.. "")
else
table.insert(r, "   "
.. v2
.. "")
end       
end
end
table.insert(r, " ")
end
end
end
end
function elementsPie()
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
if Parms"IncludeOriginalData" == KeyWords"no"
or (Parms"IncludeOriginalData" == KeyWords"auto" and DataPointsCount > AutoDataPointsLimit) then
table.insert(r, "   ")
else
table.insert(r, "   ")
end
local Total = 0
for k, v in ipairs(SeriesData) do
Total = Total + v2
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 ( math.pi then
Arc = 1 -- long arc
end
Midk = Radian + ((v2 / 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(Midk) * (PieRadius * Parms"ExplodeRadius" / 100)
DY = math.sin(Midk) * (PieRadius * Parms"ExplodeRadius" / 100)
end
Radian = Radian + (v2 / Total * TwoPi * iif(Sweep == 0, 1, -1))
local NextOuterX = math.cos(Radian) * PieRadius
local NextOuterY = math.sin(Radian) * PieRadius
t = "   "
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, " ")
table.insert(r, " ")
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(Midk) * (PieRadius * Parms"ExplodeRadius" / 100)
DY = math.sin(Midk) * (PieRadius * Parms"ExplodeRadius" / 100)
end
local TextX = math.cos(Midk) * (PieRadius * Parms"SegmentTextRadius" / 100)
local TextY = math.sin(Midk) * (PieRadius * Parms"SegmentTextRadius" / 100)
if TextY "
local tt = ""            
if string.find(Parms"SegmentText", KeyWords"text", 1, true) ~= nil then
tt = SeriesTextk
end
if string.find(Parms"SegmentText", KeyWords"value", 1, true) ~= nil then
tt = tt .. iif(string.len(tt) > 0, " ", "") .. v2
end
if string.find(Parms"SegmentText", KeyWords"percent", 1, true) ~= nil then
tt = tt .. iif(string.len(tt) > 0, " ", "") .. round(v2 / 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, "")
end

-- axis styles
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, "  ")
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
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, " ")
table.insert(r, " ")

local ArrowSize = BaseLineWidth * 12

table.insert(r, " ")
-- start arrow, for axes
if Parms"XMin" ")
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" "
table.insert(r, l)
table.insert(r, "     ")
table.insert(r, "   ")
end
l = "   "
table.insert(r, l)
table.insert(r, "     ")
table.insert(r, "   ")
end

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

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

table.insert(r, " ")
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, " ")
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, " ")
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, " ")
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, "   ")
table.insert(r, "     ")
table.insert(r, "   ")
if ParmsAxisParmName .. "Mark2Step" ~= nil then
table.insert(r, "   ")
table.insert(r, "     ")
table.insert(r, "   ")
end
table.insert(r, " ")
table.insert(r, " ")
if ParmsAxisParmName .. "Mark2Step" ~= nil then
table.insert(r, " ")
end
table.insert(r, " ")
table.insert(r, " ")
end
end
function codeAxisLine(AxisName,
ChangeDim,
AxisLinePos,
AxisStart, LineLength)
local OtherDim = "y"
if ChangeDim == "y" then
OtherDim = "x"
end

local l = " "
table.insert(r, l)
end
function codeAxisValues(AxisName,
ChangeDim,
ValueStep,
MarkOffset,
PosChangeSign, 
ValueMin, ValueMax, 
TextShift, OtherValuesPos,
ValueMultiplier, ValueRound, ValueAbsolute,
Prefix, Suffix, 
Format)

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

local AxisParmName = string.upper(AxisName) .. "Axis"
if AxisName == "y2" then
AxisParmName = "YAxis2"
end

local Anchor = "middle"
local RotateText = ""
if ChangeDim == "y" then
if MarkOffset == -1 then
-- text is left of line
Anchor = "end"
else
-- text is right of line
Anchor = "start"
end
end
if ParmsAxisParmName .. "ValueRotate" ~= nil then
RotateText = " transform=\"rotate(" .. ParmsAxisParmName .. "ValueRotate" .. ", "
if ChangeDim == "x" then
Anchor = "start" 
if ParmsAxisParmName .. "ValueRotate" = 0 and MarkOffset == -1 then
Anchor = "end"
end
end
end

local Format = (Parmsstring.upper(AxisName) .. "Max" >= 10000)
if ParmsAxisParmName .. "ValueFormat" ~= nil then
Format = not (ParmsAxisParmName .. "ValueFormat" == 'none')
end
local l = ""
local Position = 0
if AxisName ~= 'x' or #GroupText == 0 then
-- numeric values
table.insert(r, " ")
local ValueStart = 0
if ValueMin ~= 0 then
ValueStart = math.ceil(ValueMin / ValueStep) * ValueStep
end
local Value = ValueStart
while Value  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, " ")
for k, v in ipairs(GroupText) do
Position = ((iif(DoGroupsTopDown, #GroupText - k, k - 1) * GroupWidth)
+ (GroupWidth / 2)) * PosChangeSign
l = "    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 = " "
.. ParmsAxisParmName .. "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
table.insert(r, "   .charttext {")
table.insert(r, "     font-size: " .. FontSiz.ChartText .. "px;")
table.insert(r, "   }")
end

if Parms"Title" ~= nil then
table.insert(r, "   .titletext {")
table.insert(r, "     font-size: " .. FontSiz.Title .. "px;")
table.insert(r, "   }")
end

if Parms"Footnote" ~= nil then
table.insert(r, "   .footnotetext {")
table.insert(r, "     font-size: " .. FontSiz.Footnote .. "px;")
table.insert(r, "   }")
end

table.insert(r, " >")
table.insert(r, " ")
table.insert(r, " ")
end
-- legend
if Parms"LegendType" == KeyWords"none" then
-- no legend
else
table.insert(r, " ")
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 = " ")
table.insert(r, "   ")
local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin
for k, v in ipairs(SeriesText) do
table.insert(r, " ")
codeLegendElement((STypek == KeyWords"line" and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Markerk)
PosX = PosX + Siz.Legend.ElementWidth
if math.fmod(k, LegendElementsInWidth) == 0 then
-- new line of legend elements
PosX = Siz.ChartMargin
PosY = PosY + Siz.Legend.ElementHeight
end
end
else
-- vertical legend
if Parms"LegendX" ~= nil then
if DoHorizontal then
tr = tr .. round(Pos.YAxis.Zero + (Parms"LegendY" * Mult.y), 2)
.. ", " .. round(Pos.XAxis.Zero - (Parms"LegendX" * Mult.x), 2)
else
tr = tr .. round(Pos.XAxis.Zero + (Parms"LegendX" * Mult.x), 2)
.. " " .. round(Pos.YAxis.Zero - (Parms"LegendY" * Mult.y), 2)
end
else
tr = tr .. round(Pos.Legend, 2) .. ", " .. round(Siz.Space.Top + (0.1 * Siz.ChartHeight), 2)
end
table.insert(r, tr .. ")\">")
table.insert(r, "   ")
local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin
for k, v in ipairs(SeriesText) do
table.insert(r, " ")
codeLegendElement((STypek == KeyWords"line" and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Markerk)
PosY = PosY + Siz.Legend.ElementHeight
end
end
table.insert(r, " ")
table.insert(r, " ")
end
end
-- chart texts
if #ChartText > 0 then
table.insert(r, " ")
table.insert(r, " ")
table.insert(r, " ")
for i = 1, #ChartText do
if ChartTexti ~= nil then
if DoHorizontal then
table.insert(r, "   "
.. ChartTexti .. "")
else
table.insert(r, "   "
.. ChartTexti .. "")
end
end
end
table.insert(r, " ")
table.insert(r, " ")
end

-- title and footnote
if Parms"Title" ~= nil then
table.insert(r, " ")
tr = " "
else
tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms"TitleX" * Mult.x), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms"TitleY" * Mult.y), 2) .. "\">"
end
else
tr = tr .. " x=\"" .. round(Siz.Space.Left + Siz.ChartWidth * 0.5, 2) .. "\""
.. " y=\"" .. round(Pos.Title, 2) .. "\">"
end
table.insert(r, tr .. Parms"Title" .. "")
table.insert(r, " ")
end
if Parms"Footnote" ~= nil then
table.insert(r, " ")
tr = " "
else
tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms"FootnoteX" * Mult.x), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms"FootnoteY" * Mult.y), 2) .. "\">"
end
else
tr = tr .. " x=\"" .. round(ImageWidth - Siz.ImagePadding.Right, 2) .. "\""
.. " y=\"" .. round(Pos.Footnote, 2) .. "\">"
end
table.insert(r, tr .. Parms"Footnote" .. "")
table.insert(r, " ")
end
if Parms"ImageForegroundSVG" ~= nil then
table.insert(r, " ")
table.insert(r, " " .. Parms"ImageForegroundSVG" .. "")
table.insert(r, " ")
end
table.insert(r, " ")
table.insert(r, " ")
end
function codeLegendElement(Lines, SeriesNumber, PosX, PosY, Padding, TextSize, Text, Marker)
-- one entry in the legend
-- Lines, boolean
-- SeriesNumber, numeric
-- PosX, numeric
-- PosY, numeric (= the top of the legend element)
-- Padding, numeric
-- TextSize, numeric
-- Text, text
-- Marker, text
local l = ""
if Lines then
local lineY = round(PosY + TextSize / 2, 2)
l = "   ")
table.insert(r, "   "
.. Text .. "")
else
table.insert(r, "   ")
table.insert(r, "   "
.. Text .. "")
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)
knownparmsv = 1
end

for k, v in pairs(regexps) do
regexpsk = '^' .. v .. '$'
end

-- check each entry in Args
for k, v in pairs(Args) do
if type(k) == 'string' and knownparmsk == 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 ' ...' or ))
end
elseif type(k) == 'number' and knownparmstostring(k) == nil then
local vlen = mw.ustring.len(v)
v = mw.ustring.sub(v, 1, (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
tok = {}
copyTable(v, tok)
else
tok = 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 keysi then
return keysi, tkeysi
end
end
end
return {
KeyWords.barChart = barChart,
KeyWords.lineChart = lineChart,
KeyWords.scatterChart = scatterChart,
KeyWords.mixedChart = mixedChart,
KeyWords.pieChart = pieChart,
}