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