Module:Charts SVG
Documentation for this module may be created at Module:Charts SVG/doc
-- Module Charts SVG
local Args, Parms = {}, {}
local SeriesData, OriginalData = {}, {}
local SType, YAxis2, Labels, Color, LineShow, LineWidth, LineDash, Marker, MarkerFill, MarkerSize, FillPattern, FillPatternColor = {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
local SeriesText, GroupText, ChartText = {}, {}, {}
local SeriesCount, BarSeriesCount, SeriesMaxLen, GroupsCount, ChartTextCount = 0, 0, 0, 0, 0
local AutoDataPointsLimit, DataPointsCount = 100, 0
local DoPie, DoArea, DoStack, DoStack100, DoYAxis2 = false, false, false, false, false
local Msgs = {}
local FontSiz, Siz, Pos, Mult = {}, {}, {}, {}
local ChartWidth, ChartHeight, ImageWidth, ImageHeight
local GroupWidth, UnitWidth, BarWidth, BarSpace
local BaseUnit, BaseFontSize, BaseLineWidth = 3, 3, 1
local r, tr, t = {}
-- To translate parameters, translate the values in the KeyWords table. Do not translate the keys.
local KeyWords = {
barChart = 'barChart',
lineChart = 'lineChart',
scatterChart = 'scatterChart',
mixedChart = 'mixedChart',
pieChart = 'pieChart',
FileTitle = 'FileTitle',
FileDesc = 'FileDesc',
ImagePadding = 'ImagePadding',
ImagePaddingTop = 'ImagePaddingTop',
ImagePaddingBottom = 'ImagePaddingBottom',
ImagePaddingLeft = 'ImagePaddingLeft',
ImagePaddingRight = 'ImagePaddingRight',
ImageBackgroundColor = 'ImageBackgroundColor',
ImageBackgroundSVG = 'ImageBackgroundSVG',
ImageBorder = 'ImageBorder',
ImageForegroundSVG = 'ImageForegroundSVG',
Title = 'Title',
TitleX = 'TitleX',
TitleY = 'TitleY',
Footnote = 'Footnote',
FootnoteX = 'FootnoteX',
FootnoteY = 'FootnoteY',
Area = 'Area',
Stack = 'Stack',
Stack100 = 'Stack100',
ChartWidth = 'ChartWidth',
ChartHeight = 'ChartHeight',
GreyScale = 'GreyScale',
GrayScale = 'GrayScale',
LineWidth = 'LineWidth',
ChartBackgroundColor = 'ChartBackgroundColor',
XMin = 'XMin',
XMax = 'XMax',
XAxisTitle = 'XAxisTitle',
XAxisValueStep = 'XAxisValueStep',
XAxisValueMultiplier = 'XAxisValueMultiplier',
XAxisValueRound = 'XAxisValueRound',
XAxisValuePrefix = 'XAxisValuePrefix',
XAxisValueSuffix = 'XAxisValueSuffix',
XAxisValueFormat = 'XAxisValueFormat',
XAxisValueRotate = 'XAxisValueRotate',
XAxisMark2Step = 'XAxisMark2Step',
YMin = 'YMin',
YMax = 'YMax',
YAxisTitle = 'YAxisTitle',
YAxisValueStep = 'YAxisValueStep',
YAxisValueMultiplier = 'YAxisValueMultiplier',
YAxisValueRound = 'YAxisValueRound',
YAxisValuePrefix = 'YAxisValuePrefix',
YAxisValueSuffix = 'YAxisValueSuffix',
YAxisValueFormat = 'YAxisValueFormat',
YAxisMark2Step = 'YAxisMark2Step',
YAxisColor = 'YAxisColor',
Y2Min = 'Y2Min',
Y2Max = 'Y2Max',
YAxis2Title = 'YAxis2Title',
YAxis2ValueStep = 'YAxis2ValueStep',
XAxis2ValueMultiplier = 'XAxis2ValueMultiplier',
YAxis2ValueRound = 'YAxis2ValueRound',
YAxis2ValuePrefix = 'YAxis2ValuePrefix',
YAxis2ValueSuffix = 'YAxis2ValueSuffix',
YAxis2ValueFormat = 'YAxis2ValueFormat',
YAxis2Mark2Step = 'YAxis2Mark2Step',
YAxis2Color = 'YAxis2Color',
XGrid = 'XGrid',
YGrid = 'YGrid',
LegendType = 'LegendType',
LegendX = 'LegendX',
LegendY = 'LegendY',
LegendTextWidth = 'LegendTextWidth',
LegendBorder = 'LegendBorder',
LegendSVG = 'LegendSVG',
FontSize = 'FontSize',
LabelsFontSize = 'LabelsFontSize',
TitleFontSize = 'TitleFontSize',
FootnoteFontSize = 'FootnoteFontSize',
LegendFontSize = 'LegendFontSize',
XAxisTitleFontSize = 'XAxisTitleFontSize',
YAxisTitleFontSize = 'YAxisTitleFontSize',
YAxis2TitleFontSize = 'YAxis2TitleFontSize',
XAxisValuesFontSize = 'XAxisValuesFontSize',
YAxisValuesFontSize = 'YAxisValuesFontSize',
YAxis2ValuesFontSize = 'YAxis2ValuesFontSize',
ChartTextFontSize = 'ChartTextFontSize',
GraphLineWidth = 'GraphLineWidth',
BarWidth = 'BarWidth',
BarSpace = 'BarSpace',
PieRadius = 'PieRadius',
Explode = 'Explode',
ExplodeRadius = 'ExplodeRadius',
DoughnutHole = 'DoughnutHole',
PieStartAngle = 'PieStartAngle',
PieSweepDir = 'PieSweepDir',
SegmentText = 'SegmentText',
SegmentTextWidth = 'SegmentTextWidth',
SegmentTextRadius = 'SegmentTextRadius',
BorderColor = 'BorderColor',
BorderWidth = 'BorderWidth',
Series = 'Series',
Segment = 'Segment',
Type = 'Type',
Values = 'Values',
YAxis2 = 'YAxis2',
Labels = 'Labels',
Color = 'Color',
Line = 'Line',
Width = 'Width',
Dash = 'Dash',
Marker = 'Marker',
MarkerSize = 'MarkerSize',
MarkerFill = 'MarkerFill',
Pattern = 'Pattern',
PatternColor = 'PatternColor',
Text = 'Text',
Group = 'Group',
ChartText = 'ChartText',
IncludeOriginalData = 'IncludeOriginalData',
Debug = 'Debug',
-- these are values that some parameters recognise, and can be translated
none = 'none',
-- series types
bar = 'bar',
line = 'line',
-- legend types
vertical = 'vertical',
horizontal = 'horizontal',
-- pie segment texts
text = 'text',
value = 'value',
percent = 'percent',
-- original data
auto = 'auto',
-- debug
parms = 'parms',
--
yes = 'yes',
no = 'no',
-- these are special values that make the code simpler if they exist in the KeyWords table,
-- but they *must not* be translated
X = 'X',
Y = 'Y',
}
-- To translate messages, translate the values in the MessageTexts table. Do not translate the keys.
local MessageTexts = {
NotNumeric = "%s, value \"%s\" is not numeric.",
BelowChartMinSize = "%s, value \"%d\" is below the minimum chart size of %d.",
NotInGroup = "Series%dValues, pair %d: X value \"%s\" is not for a defined group.",
NotInMarkers = "Series%dMarker, value \"%s\" is not in the markers table.",
NotInDashPatterns = "Series%dDash, value \"%s\" is not in the dash patterns table.",
NotInFillPatterns = "Series%dPattern, value \"%s\" is not in the fill patterns table.",
NoYAxis2 = "Series%dYAxis2 is set, but no 2nd Y axis is shown - which may be because Stack or Stack100 are set.",
}
local DefColor = {
'rgb(0, 0, 255)', -- 1 = blue
'rgb(0, 192, 0)', -- 2 = mid green
'rgb(255, 255, 0)', -- 3 = yellow
'rgb(255, 0, 0)', -- 4 = red
'rgb(192, 192, 0)', -- 5 = light olive
'rgb(255, 0, 255)', -- 6 = magenta
'rgb(0, 255, 0)', -- 7 = lime
'rgb(128, 128, 128)', -- 8 = grey
'rgb(0, 255, 255)', -- 9 = cyan
'rgb(255, 165, 0)', -- 10 = orange
'rgb(0, 0, 192)', -- 11 = light navy
'rgb(128, 255, 128)', -- 12 = light green
'rgb(255, 255, 128)', -- 13 = sand yellow
'rgb(192, 0, 0)', -- 14 = red brown
'rgb(240, 240, 240)', -- 15 = pale grey
'rgb(255, 128, 255)', -- 16 = light magenta
'rgb(192, 255, 192)', -- 17 = pale green
'rgb(192, 0, 192)', -- 18 = dark mauve
'rgb(192, 192, 255)', -- 19 = blue-grey
'rgb(0, 192, 192)', -- 20 = dark cyan
'rgb(192, 192, 192)', -- 21 = light grey
'rgb(128, 128, 255)', -- 22 = dark blue-grey
'rgb(0, 128, 0)', -- 23 = green
'rgb(255, 255, 192)', -- 24 = light sand
'rgb(255, 192, 192)', -- 25 = pale red
'rgb(128, 128, 0)', -- 26 = olive
'rgb(255, 192, 255)', -- 27 = pale magenta
'rgb(255, 96, 0)', -- 28 = dark orange
'rgb(192, 255, 255)', -- 29 = light cyan
'rgb(255, 128, 128)', -- 30 = light red
}
-- grayscale equivalents of the above colours
local GrayColor = {
'rgb(18, 18, 18)', -- 1 = blue-g
'rgb(137, 137, 137)', -- 2 = midgreen-g
'rgb(237, 237, 237)', -- 3 = yellow-g
'rgb(54, 54, 54)', -- 4 = red-g
'rgb(178, 178, 178)', -- 5 = lightolive-g
'rgb(73, 73, 73)', -- 6 = magenta-g
'rgb(182, 182 ,182)', -- 7 = lime-g
'rgb(128, 128, 128)', -- 8 = grey-g
'rgb(201, 201 ,201)', -- 9 = cyan-g
'rgb(172, 172 172)', -- 10 = orange-g
'rgb(14, 14 ,14)', -- 11 = lightnavy-g
'rgb(219, 219, 219)', -- 12 = lightgreen-g
'rgb(246, 246, 246)', -- 13 = sandyellow-g
'rgb(41, 41, 41)', -- 14 = redbrown-g
'rgb(240, 240, 240)', -- 15 = palegrey-g
'rgb(164, 164, 164)', -- 16 = lightmagenta-g
'rgb(237, 237, 237)', -- 17 = palegreen-g
'rgb(55, 55, 55)', -- 18 = darkmauve-g
'rgb(197, 197, 197)', -- 19 = bluegrey-g
'rgb(151, 151, 151)', -- 20 = darkcyan-g
'rgb(192, 192, 192)', -- 21 = lightgrey-g
'rgb(137, 137, 137)', -- 22 = darkbluegrey-g
'rgb(92, 92, 92)', -- 23 = green-g
'rgb(250, 250, 250)', -- 24 = lightsand-g
'rgb(205, 205, 205)', -- 25 = palered-g
'rgb(119, 119, 119)', -- 26 = olive-g
'rgb(210, 210, 210)', -- 27 = palemagenta-g
'rgb(123, 123, 123)', -- 28 = darkorange-g
'rgb(242, 242, 242)', -- 29 = lightcyan-g
'rgb(155, 155, 155)' -- 30 = lightred-g
}
local DashPattern = {
"8,8", -- 1 = dashed, long
"4,4", -- 2 = dashed, short
"8,2", -- 3 = broken, long
"4,2", -- 4 = broken, short
"2,2", -- 5 = dots
"6,2,2,2", -- 6 = dash-dot
"6,2,2,2,2,2", -- 7 = dash-dot-dot
"6,2,2,2,2,2,2,2" -- 8 = dash-dot-dot-dot
}
--[[
The differences between the defaults for the 4 'graph' chart methods are:
barChart lineChart scatterChart mixedChart
series type bar* line* line* must be specified
line visibility -* yes none yes
marker -* nil series no. nil
group allowed allowed -* allowed
area -* nil -* -*
stack nil nil -* -*
stack100 nil nil -* -*
(* = cannot be changed by parameters)
]]
function barChart(frame)
Args = frame.args
getAllParms()
DoStack = (Parms["Stack"] ~= nil)
DoStack100 = (Parms["Stack100"] ~= nil)
DoYAxis2 = (Parms["Y2Max"] ~= nil)
-- fill internal tables
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
SType[i] = KeyWords["bar"]
end
copyTable(SeriesData, OriginalData) -- preserve original data
BarSeriesCount = SeriesCount
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)
build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil)
build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if DoStack or DoStack100 then
calcStack()
DoYAxis2 = false
end
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
stylesAreas()
defsAreas()
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function lineChart(frame)
Args = frame.args
getAllParms()
DoArea = (Parms["Area"] ~= nil)
DoStack = (Parms["Stack"] ~= nil)
DoStack100 = (Parms["Stack100"] ~= nil)
DoYAxis2 = (Parms["Y2Max"] ~= nil)
-- fill internal tables
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
SType[i] = KeyWords["line"]
end
copyTable(SeriesData, OriginalData) -- preserve original data
BarSeriesCount = 0
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)
build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes'
build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)
build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)
build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil
build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)
build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)
build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil)
build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if DoStack or DoStack100 then
calcStack()
DoArea = true
DoYAxis2 = false
end
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
if DoArea then
stylesAreas()
defsAreas()
else
stylesLines()
defsMarkers()
end
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function scatterChart(frame)
Args = frame.args
getAllParms()
DoYAxis2 = (Parms["Y2Max"] ~= nil)
-- fill internal tables
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
SType[i] = KeyWords["line"]
end
copyTable(SeriesData, OriginalData) -- preserve original data
BarSeriesCount = 0
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, DefColor, nil, nil)
build("Series", "Line", SeriesCount, LineShow, KeyWords["none"], nil, nil) -- the default line visibility is 'none'
build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)
build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)
build("Series", "Marker", SeriesCount, Marker, nil, true, nil) -- the default marker type is the series number
build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)
build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
stylesLines()
defsMarkers()
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function mixedChart(frame)
Args = frame.args
getAllParms()
DoYAxis2 = (Parms["Y2Max"] ~= nil)
-- fill internal tables
build("Series", "Type", SeriesCount, SType, "line", nil, nil)
for i = 1, SeriesCount do
if SType[i] == KeyWords["bar"] then
BarSeriesCount = BarSeriesCount + 1
end
end
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
end
copyTable(SeriesData, OriginalData) -- preserve original data
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)
build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes'
build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)
build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)
build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil
build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)
build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)
build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil)
build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
stylesAreas()
defsAreas()
stylesLines()
defsMarkers()
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function pieChart(frame)
Args = frame.args
getAllParms()
GroupsCount = 0 -- ensures any GroupNText parameters are ignored
-- fill internal tables
if SeriesCount > 0 then
transfer(Parms["Series" .. 1 .. "Values"], SeriesData, 2)
DataPointsCount = DataPointsCount + #SeriesData
end
copyTable(SeriesData, OriginalData) -- preserve original data
for i = 1, SeriesMaxLen do
Parms["Segment" .. i .. "Color"] = checkColor(getParm("Segment", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))
Parms["Segment" .. i .. "Pattern"] = getParm("Segment", i, "Pattern", "n")
if Parms["Segment" .. i .. "Pattern"] ~= nil then
Parms["Segment" .. i .. "PatternColor"] = checkColor(getParm("Segment", i, "PatternColor", "s", iif(Parms["GrayScale"] ~= nil, "white", "black")))
end
end
build("Segment", "Color", SeriesMaxLen, Color, nil, nil, iif(Parms["GrayScale"] ~= nil, GrayColor, DefColor))
build("Segment", "Pattern", SeriesMaxLen, FillPattern, KeyWords["none"], nil, nil)
build("Segment", "PatternColor", SeriesMaxLen, FillPatternColor, nil, nil, nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
-- transfer the first value in each SeriesData pair to table SeriesText, for the legend
for k, v in ipairs(SeriesData) do
SeriesText[k] = v[1]
end
SeriesCount = SeriesMaxLen
DoPie = true
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsPie()
commonTop()
stylesAreas()
defsAreas()
elementsPie()
commonBottom()
return table.concat(r, "\n")
end
function getAllParms()
-- get values for all parameters
-- Note that not all have a default value, ie: it is valid for some parameters to be nil. These are genarally switches for some behaviour.
-- SVG file metadata
Parms["FileTitle"] = getParm("FileTitle", nil, nil, "s", "SVG Chart")
Parms["FileDesc"] = getParm("FileDesc", nil, nil, "s", "SVG chart generated by Charts SVG")
-- general image
Parms["ImagePadding"] = getParm("ImagePadding", nil, nil, "n") -- switch for replacing default value with user setting for the size of the padding for all 4 spaces
Parms["ImagePaddingTop"] = getParm("ImagePaddingTop", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the top padding
Parms["ImagePaddingBottom"] = getParm("ImagePaddingBottom", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the bottom padding
Parms["ImagePaddingLeft"] = getParm("ImagePaddingLeft", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the left padding
Parms["ImagePaddingRight"] = getParm("ImagePaddingRight", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the right padding
Parms["ImageBorder"] = checkColor(getParm("ImageBorder", nil, nil, "s", KeyWords["none"]))
Parms["ImageBackgroundColor"] = checkColor(getParm("ImageBackgroundColor", nil, nil, "s", "white"))
Parms["ImageBackgroundSVG"] = getParm("ImageBackgroundSVG", nil, nil, "s") -- switch for background SVG for the image
Parms["ImageForegroundSVG"] = getParm("ImageForegroundSVG", nil, nil, "s") -- switch for foreground SVG for the image
-- display title
Parms["Title"] = getParm("Title", nil, nil, "s") -- switch to show title text
Parms["TitleX"] = getParm("TitleX", nil, nil, "n") -- switch to move title from original X position
Parms["TitleY"] = getParm("TitleY", nil, nil, "n") -- switch to move title from original Y position
-- footnote
Parms["Footnote"] = getParm("Footnote", nil, nil, "s") -- switch for footnote text
Parms["FootnoteX"] = getParm("FootnoteX", nil, nil, "n") -- switch to move footnote from original X position
Parms["FootnoteY"] = getParm("FootnoteY", nil, nil, "n") -- switch to move footnote from original Y position
-- general chart
Parms["Area"] = getParm("Area", nil, nil, "s") -- switch for area graphs
Parms["Stack"] = getParm("Stack", nil, nil, "s") -- switch for stacked graphs
Parms["Stack100"] = getParm("Stack100", nil, nil, "s") -- switch for stacked-to-100% graphs
Parms["ChartWidth"] = getParm("ChartWidth", nil, nil, "n", 500)
Parms["ChartHeight"] = getParm("ChartHeight", nil, nil, "n", 350)
if Parms["ChartWidth"] < 200 then
table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartWidth", Parms["ChartWidth"], 200))
end
if Parms["ChartHeight"] < 200 then
table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartHeight", Parms["ChartHeight"], 200))
end
Parms["GreyScale"] = getParm("GreyScale", nil, nil, "s")
Parms["GrayScale"] = getParm("GrayScale", nil, nil, "s", Parms["GreyScale"]) -- switch for grayscale instead of colours
Parms["LineWidth"] = getParm("LineWidth", nil, nil, "n", 100)
Parms["ChartBackgroundColor"] = checkColor(getParm("ChartBackgroundColor", nil, nil, "s")) -- switch for a background color for the chart
-- XAxis
Parms["XMin"] = getParm("XMin", nil, nil, "n", 0)
Parms["XMax"] = getParm("XMax", nil, nil, "n", 100)
Parms["XAxisTitle"] = getParm("XAxisTitle", nil, nil, "s") -- switch to show X axis title
Parms["XAxisValueStep"] = getParm("XAxisValueStep", nil, nil, "n", 10)
Parms["XAxisValueMultiplier"] = getParm("XAxisValueMultiplier", nil, nil, "n", 1)
Parms["XAxisValueRound"] = getParm("XAxisValueRound", nil, nil, "n", decPlaces(Parms["XAxisValueStep"]))
Parms["XAxisValuePrefix"] = getParm("XAxisValuePrefix", nil, nil, "s", "", "_", " ")
Parms["XAxisValueSuffix"] = getParm("XAxisValueSuffix", nil, nil, "s", "", "_", " ")
Parms["XAxisValueFormat"] = getParm("XAxisValueFormat", nil, nil, "s") -- switch to force values formatting on or off
Parms["XAxisValueRotate"] = getParm("XAxisValueRotate", nil, nil, "n") -- switch to rotate X-axis values
Parms["XAxisMark2Step"] = getParm("XAxisMark2Step", nil, nil, "n") -- switch for showing x-axis secondary marks
-- YAxis (Left)
if Parms["Stack100"] ~= nil then
Parms["YMin"] = 0
Parms["YMax"] = 100
else
Parms["YMin"] = getParm("YMin", nil, nil, "n", 0)
Parms["YMax"] = getParm("YMax", nil, nil, "n", 100)
end
Parms["YAxisTitle"] = getParm("YAxisTitle", nil, nil, "s") -- switch to show Y axis title
Parms["YAxisValueStep"] = getParm("YAxisValueStep", nil, nil, "n", 10)
Parms["YAxisValueMultiplier"] = getParm("YAxisValueMultiplier", nil, nil, "n", 1)
Parms["YAxisValueRound"] = getParm("YAxisValueRound", nil, nil, "n", decPlaces(Parms["YAxisValueStep"]))
Parms["YAxisValuePrefix"] = getParm("YAxisValuePrefix", nil, nil, "s", "", "_", " ")
if Parms["Stack100"] ~= nil then
Parms["YAxisValueSuffix"] = getParm("YAxisValueSuffix", nil, nil, "s", "%", "_", " ")
else
Parms["YAxisValueSuffix"] = getParm("YAxisValueSuffix", nil, nil, "s", "", "_", " ")
end
Parms["YAxisValueFormat"] = getParm("YAxisValueFormat", nil, nil, "s") -- switch to force values formatting on or off
Parms["YAxisMark2Step"] = getParm("YAxisMark2Step", nil, nil, "n") -- switch for showing y-axis secondary marks
Parms["YAxisColor"] = checkColor(getParm("YAxisColor", nil, nil, "s", "black"))
-- YAxis2 (Right)
Parms["YAxis2Title"] = getParm("YAxis2Title", nil, nil, "s") -- switch to show Y axis 2 title
Parms["Y2Min"] = getParm("Y2Min", nil, nil, "n", 0)
Parms["Y2Max"] = getParm("Y2Max", nil, nil, "n") -- switch to show the second Y axis
Parms["YAxis2ValueStep"] = getParm("YAxis2ValueStep", nil, nil, "n", 10)
Parms["YAxis2ValueMultiplier"] = getParm("YAxis2ValueMultiplier", nil, nil, "n", 1)
Parms["YAxis2ValueRound"] = getParm("YAxis2ValueRound", nil, nil, "n", decPlaces(Parms["YAxis2ValueStep"]))
Parms["YAxis2ValuePrefix"] = getParm("YAxis2ValuePrefix", nil, nil, "s", "", "_", " ")
Parms["YAxis2ValueSuffix"] = getParm("YAxis2ValueSuffix", nil, nil, "s", "", "_", " ")
Parms["YAxis2ValueFormat"] = getParm("YAxis2ValueFormat", nil, nil, "s") -- switch to force values formatting on or off
Parms["YAxis2Mark2Step"] = getParm("YAxis2Mark2Step", nil, nil, "n") -- switch for showing y-axis2 secondary marks
Parms["YAxis2Color"] = checkColor(getParm("YAxis2Color", nil, nil, "s", "black"))
-- grid lines
Parms["XGrid"] = getParm("XGrid", nil, nil, "s", Parms["XAxisValueStep"])
if Parms["XGrid"] ~= KeyWords["none"] then
Parms["XGrid"] = tonumber(Parms["XGrid"])
end
Parms["YGrid"] = getParm("YGrid", nil, nil, "s", Parms["YAxisValueStep"])
if Parms["YGrid"] ~= KeyWords["none"] then
Parms["YGrid"] = tonumber(Parms["YGrid"])
end
-- legend
Parms["LegendType"] = getParm("LegendType", nil, nil, "s", KeyWords["vertical"])
i = 1
Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N
while Parms["Series" .. i .. "Text"] ~= nil do
i = i + 1
Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N
end
Parms["LegendX"] = getParm("LegendX", nil, nil, "n") -- switch for moving the legend from the default position, and for not adding space for it to right space
Parms["LegendY"] = getParm("LegendY", nil, nil, "n") -- switch for moving the legend from the default position
Parms["LegendTextWidth"] = getParm("LegendTextWidth", nil, nil, "n", 100)
Parms["LegendBorder"] = checkColor(getParm("LegendBorder", nil, nil, "s", "black"))
Parms["LegendSVG"] = getParm("LegendSVG", nil, nil, "s") -- switch for replacing all legend code
-- font sizes
Parms["FontSize"] = getParm("FontSize", nil, nil, "n", 100)
Parms["TitleFontSize"] = getParm("TitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["FootnoteFontSize"] = getParm("FootnoteFontSize", nil, nil, "n", Parms["FontSize"])
Parms["LegendFontSize"] = getParm("LegendFontSize", nil, nil, "n", Parms["FontSize"])
Parms["XAxisTitleFontSize"] = getParm("XAxisTitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxisTitleFontSize"] = getParm("YAxisTitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxis2TitleFontSize"] = getParm("YAxis2TitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["XAxisValuesFontSize"] = getParm("XAxisValuesFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxisValuesFontSize"] = getParm("YAxisValuesFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxis2ValuesFontSize"] = getParm("YAxis2ValuesFontSize", nil, nil, "n", Parms["FontSize"])
Parms["ChartTextFontSize"] = getParm("ChartTextFontSize", nil, nil, "n", Parms["FontSize"])
Parms["LabelsFontSize"] = getParm("LabelsFontSize", nil, nil, "n", Parms["ChartTextFontSize"])
-- bar width & spacing
Parms["BarWidth"] = getParm("BarWidth", nil, nil, "n", 20)
Parms["BarSpace"] = getParm("BarSpace", nil, nil, "n", 0) -- % of bar width
-- general graph line width
Parms["GraphLineWidth"] = getParm("GraphLineWidth", nil, nil, "n", 100)
-- pie chart
Parms["PieRadius"] = getParm("PieRadius", nil, nil, "n", 200)
Parms["Explode"] = getParm("Explode", nil, nil, "s") -- switch for exploding some or all pie segments
if Parms["Explode"] ~= nil then
if tonumber(Parms["Explode"]) ~= nil then
-- if Explode is a number, ensure it is of numeric type
Parms["Explode"] = tonumber(Parms["Explode"])
-- default explode is 20%
Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 20)
else
-- any other value = all, default explode radius is 10%
Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 10)
end
end
Parms["SegmentText"] = getParm("SegmentText", nil, nil, "s") -- switch for showing series text and/or values on pie segments
Parms["SegmentTextWidth"] = getParm("SegmentTextWidth", nil, nil, "n", 100)
Parms["SegmentTextRadius"] = getParm("SegmentTextRadius", nil, nil, "n", 105)
Parms["DoughnutHole"] = getParm("DoughnutHole", nil, nil, "s") -- switch for a doughnut chart
if Parms["DoughnutHole"] ~= nil then
if tonumber(Parms["DoughnutHole"]) ~= nil then
-- if DoughnutHole is a number, ensure it is of numeric type
Parms["DoughnutHole"] = tonumber(Parms["DoughnutHole"])
else
-- any other value, hole size is 50%
Parms["DoughnutHole"] = 50
end
end
Parms["PieStartAngle"] = getParm("PieStartAngle", nil, nil, "n", 0) -- move the start angle
Parms["PieSweepDir"] = getParm("PieSweepDir", nil, nil, "s", "AntiClockwise") -- any value not "AntiClockwise" will switch the sweep direction
-- bar and pie-segment borders
Parms["BorderColor"] = checkColor(getParm("BorderColor", nil, nil, "s")) -- switch for showing borders on bars
Parms["BorderWidth"] = getParm("BorderWidth", nil, nil, "n", 100) -- % of standard line width
-- series
i = 1
Parms["Series" .. i .. "Values"] = getParm("Series", i, "Values", "s") -- switch for showing a series
while Parms["Series" .. i .. "Values"] ~= nil do
Parms["Series" .. i .. "Type"] = getParm("Series", i, "Type", "s")
Parms["Series" .. i .. "YAxis2"] = getParm("Series", i, "YAxis2") -- switch for series to use YAxis2 as scale for Y values
Parms["Series" .. i .. "Labels"] = getParm("Series", i, "Labels") -- switch for series to show data labels
Parms["Series" .. i .. "Color"] = checkColor(getParm("Series", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))
Parms["Series" .. i .. "Line"] = getParm("Series", i, "Line", "s")
Parms["Series" .. i .. "Width"] = getParm("Series", i, "Width", "n", Parms["GraphLineWidth"])
Parms["Series" .. i .. "Dash"] = getParm("Series", i, "Dash", "s", KeyWords["none"])
t = tonumber(Parms["Series" .. i .. "Dash"])
if t ~= nil and t <= #DashPattern then
-- if SeriesNDash is a number in the table of default dashes, use the dash pattern for that number
Parms["Series" .. i .. "Dash"] = DashPattern[t]
end
Parms["Series" .. i .. "Marker"] = getParm("Series", i, "Marker", "s") -- switch for markers on the graph
if tonumber(Parms["Series" .. i .. "Marker"]) ~= nil then
-- if SeriesNMarker is a number, ensure it is of numeric type
Parms["Series" .. i .. "Marker"] = tonumber(Parms["Series" .. i .. "Marker"])
end
Parms["Series" .. i .. "MarkerSize"] = getParm("Series", i, "MarkerSize", "n", 100)
Parms["Series" .. i .. "MarkerFill"] = checkColor(getParm("Series", i, "MarkerFill", "s"))
Parms["Series" .. i .. "Pattern"] = getParm("Series", i, "Pattern", "n")
if Parms["Series" .. i .. "Pattern"] ~= nil then
Parms["Series" .. i .. "PatternColor"] = checkColor(getParm("Series", i, "PatternColor", "s", iif(Parms["GrayScale"] ~= nil, "white", "black")))
end
SeriesCount = SeriesCount + 1
i = i + 1
Parms["Series" .. i .. "Values"] = getParm("Series", i, "Values", "s") -- switch for showing a series
end
-- groups
i = 1
Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s") -- switch for grouping values
while Parms["Group" .. i .. "Text"] ~= nil do
GroupsCount = GroupsCount + 1
i = i + 1
Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s")
end
-- chart texts
i = 1
Parms["ChartText" .. i] = getParm("ChartText", i, nil, "s") -- switch for showing chart text N
while Parms["ChartText" .. i] ~= nil do
Parms["ChartText" .. i .. "X"] = getParm("ChartText", i, "X", "n", 0)
Parms["ChartText" .. i .. "Y"] = getParm("ChartText", i, "Y", "n", 0)
ChartTextCount = ChartTextCount + 1
i = i + 1
Parms["ChartText" .. i] = getParm("ChartText", i, nil, "s") -- switch for showing chart text N
end
Parms["IncludeOriginalData"] = getParm("IncludeOriginalData", nil, nil, "s", KeyWords["auto"])
-- debug
Parms["Debug"] = getParm("Debug", nil, nil, "s") -- switch to run debug code
end
function getParm(s1, num, s2, typ, def, subst, with)
-- returns a named argument (of a specified type) or a default
-- the name may be built from multiple parts (s1, num, s2)
local s = KeyWords[s1]
if num ~= nil then
s = s .. num
end
if s2 ~= nil then
s = s .. KeyWords[s2]
end
local result
if Args[s] ~= nil then
result = Args[s]
if typ == "n" then
result = tonumber(result)
if result == nil then
table.insert(Msgs, string.format(MessageTexts.NotNumeric, s, Args[s]))
end
end
else
result = def
end
-- optional substitution of characters within the parameter value
if result ~= nil and subst and with then
result = mw.ustring.gsub(result, subst, with)
end
return result
end
function checkColor(p)
-- checks if p is numeric and in the colors table(s)
-- if so returns the color from the table
-- otherwise returns the original parameter value
if p == nil then
return nil
end
local t = tonumber(p)
if t ~= nil and t <= #DefColor then
return iif(Parms["GrayScale"] ~= nil, GrayColor[t], DefColor[t])
else
return p
end
end
function transfer(Parm, Tab, SetSize)
-- transfer values from a space-delimited parameter to a table
local i = 0
Parm = mw.text.trim(Parm)
if SetSize > 1 then
local x = SetSize + 1 -- to force new table for first time
for s in mw.text.gsplit(Parm, "%s+") do
if x > SetSize then
i = i + 1
Tab[i] = {}
x = 1
end
Tab[i][x] = s
x = x + 1
end
else
for s in mw.text.gsplit(Parm, "%s+") do
i = i + 1
Tab[i] = s
end
end
SeriesMaxLen = math.max(SeriesMaxLen, i)
end
function build(ParmStart, ParmEnd, Size, Tab, DefaultValue, UseI, DefaultTable)
-- builds table of specified size from a series of StartNEnd parameters
-- any nil parameters in the sequence may be filled with a default value, or i (with a prefix if set), or a value from a default table
for i = 1, Size do
if Parms[ParmStart .. i .. ParmEnd] ~= nil then
Tab[i] = Parms[ParmStart .. i .. ParmEnd]
elseif DefaultValue ~= nil then
Tab[i] = DefaultValue
elseif type(UseI) == "boolean" then
Tab[i] = i
elseif type(UseI) == "string" then
Tab[i] = UseI .. i
elseif DefaultTable ~= nil then
Tab[i] = DefaultTable[i]
else
-- nothing
end
end
end
function calcStack()
-- calculate stacked totals for series
-- the current accumlated total overwrites the SeriesData Y value
local Total = {}
for j = 1, #GroupText do
Total[j] = 0
local PosTotal, NegTotal = 0, 0
for i = 1, #SeriesData do
if SeriesData[i][j] ~= nil and tonumber(SeriesData[i][j][1]) == j then
Total[j] = Total[j] + SeriesData[i][j][2]
if tonumber(SeriesData[i][j][2]) < 0 then
NegTotal = NegTotal + SeriesData[i][j][2]
SeriesData[i][j][2] = NegTotal
else
PosTotal = PosTotal + SeriesData[i][j][2]
SeriesData[i][j][2] = PosTotal
end
end
end
end
if DoStack100 then
for j = 1, #GroupText do
for i = 1, #SeriesData do
if SeriesData[i][j] ~= nil then
SeriesData[i][j][2] = SeriesData[i][j][2] / Total[j] * 100
end
end
end
end
end
function checkParms()
-- check for parameter issues
if #GroupText > 0 then
for k1, v1 in ipairs(SeriesData) do
for k2, v2 in ipairs(v1) do
local t = tonumber(v2[1])
if t ~= math.floor(t)
or t < 1
or t > #GroupText then
table.insert(Msgs, string.format(MessageTexts.NotInGroup, k1, k2, v2[1]))
end
end
end
end
if not DoYAxis2 then
for k, v in pairs(YAxis2) do
table.insert(Msgs, string.format(MessageTexts.NoYAxis2, k))
end
end
for k, v in pairs(Marker) do
local t = tonumber(v)
if v == "none" then
-- OK
elseif t == nil then
table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v))
elseif (t >= 1 and t <= 7) then
-- OK
else
table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v))
end
end
for k, v in pairs(LineDash) do
local t = tonumber(v)
if v == "none" then
-- OK
elseif t == nil then
-- non-numerics are OK for LineDash
elseif (t >= 1 and t <= #DashPattern) then
-- OK
else
table.insert(Msgs, string.format(MessageTexts.NotInDashPatterns, k, v))
end
end
for k, v in pairs(FillPattern) do
local t = tonumber(v)
if v == "none" then
-- OK
elseif t == nil then
table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v))
elseif (t >= 1 and t <= 8)
or (t >= 11 and t <= 18)
or (t >= 21 and t <= 24)
or (t >= 31 and t <= 34)
or (t >= 41 and t <= 49)
or (t >= 51 and t <= 56)
or (t >= 61 and t <= 64) then
-- OK
else
table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v))
end
end
if #Msgs > 0 then
-- add messages to output
table.insert(r, " ===== Charts SVG messages =====")
for i = 1, math.min(#Msgs, 10) do
table.insert(r, " " .. Msgs[i])
end
return false
end
return true
end
function prelimsCommon()
Siz.ImagePadding = {}
Siz.Legend = {}
Siz.Text = {}
-- the base unit for font sizes can be changed by the user
BaseFontSize = BaseFontSize * (Parms["FontSize"] / 100)
FontSiz.Labels = 5 * BaseFontSize * Parms["LabelsFontSize"] / 100
FontSiz.Title = 7 * BaseFontSize * Parms["TitleFontSize"] / 100
FontSiz.Footnote = 3 * BaseFontSize * Parms["FootnoteFontSize"] / 100
FontSiz.LegendText = 5 * BaseFontSize * Parms["LegendFontSize"] / 100
FontSiz.ChartText = 5 * BaseFontSize * Parms["ChartTextFontSize"] / 100
-- the base line width can be changed by the user
BaseLineWidth = BaseLineWidth * Parms["LineWidth"] / 100
-- define sizes of other image components from base spacing units
Siz.ImagePadding.Top = 2 * BaseUnit
if Parms["ImagePaddingTop"] ~= nil then
Siz.ImagePadding.Top = Parms["ImagePaddingTop"]
end
Siz.ImagePadding.Bottom = 2 * BaseUnit
if Parms["ImagePaddingBottom"] ~= nil then
Siz.ImagePadding.Bottom = Parms["ImagePaddingBottom"]
end
Siz.ImagePadding.Left = 2 * BaseUnit
if Parms["ImagePaddingLeft"] ~= nil then
Siz.ImagePadding.Left = Parms["ImagePaddingLeft"]
end
Siz.ImagePadding.Right = 2 * BaseUnit
if Parms["ImagePaddingRight"] ~= nil then
Siz.ImagePadding.Right = Parms["ImagePaddingRight"]
end
Siz.ChartPadding = 2 * BaseUnit
Siz.Text.Interline = 2 * BaseFontSize
Siz.Text.Labels = FontSiz.Labels
Siz.Text.Title = iif(Parms["Title"] ~= nil and Parms["TitleY"] == nil, FontSiz.Title + Siz.Text.Interline, 0)
Siz.Footnote = iif(Parms["Footnote"] ~= nil and Parms["FootnoteY"] == nil, FontSiz.Footnote, 0)
Siz.Legend.Text = FontSiz.LegendText
Siz.Legend.TextWidth = 5 * FontSiz.LegendText * Parms["LegendTextWidth"] / 100
Siz.Legend.Offset = 3 * BaseUnit
Siz.Text.Chart = FontSiz.ChartText
-- chart size
ChartWidth = Parms["ChartWidth"]
ChartHeight = Parms["ChartHeight"]
-- legend height and width
Siz.Legend.ElementWidth = Siz.ChartPadding + (2 * Siz.Legend.Text) + Siz.ChartPadding + Siz.Legend.TextWidth
Siz.Legend.ElementHeight = Siz.Legend.Text + Siz.Text.Interline
local LegendElementsInWidth = 1
local LegendLines = #SeriesText
if Parms["LegendType"] == KeyWords["horizontal"] then
LegendElementsInWidth = math.floor(ChartWidth / Siz.Legend.ElementWidth)
LegendElementsInWidth = math.min(LegendElementsInWidth, #SeriesText)
LegendLines = math.ceil(#SeriesText / LegendElementsInWidth)
Siz.Legend.Width = (LegendElementsInWidth * Siz.Legend.ElementWidth) + Siz.ChartPadding
Siz.Legend.Height = Siz.ChartPadding + (LegendLines * Siz.Legend.ElementHeight) + Siz.ChartPadding
else
Siz.Legend.Width = Siz.Legend.ElementWidth + Siz.ChartPadding
Siz.Legend.Height = Siz.ChartPadding + (#SeriesText * Siz.Legend.ElementHeight) + Siz.ChartPadding
end
end
function prelimsAxes()
Pos.XAxis = {}
Pos.YAxis = {}
Pos.YAxis2 = {}
Siz.Space = {}
Siz.XAxis = {}
Siz.YAxis = {}
Siz.YAxis2 = {}
BarWidth = Parms["BarWidth"]
BarSpace = Parms["BarSpace"] / 100
-- The Mult values are the number of pixels per 1 unit on each axis
if #GroupText > 0 then
if BarSeriesCount > 0 then
GroupWidth = ChartWidth / #GroupText
if DoStack or DoStack100 then
UnitWidth = round(GroupWidth / 2, 0)
else
UnitWidth = round(GroupWidth / (BarSeriesCount + 1), 0)
end
BarWidth = round(UnitWidth / (1 + BarSpace), 0)
BarSpace = round(UnitWidth - BarWidth, 0)
Mult.X = ChartWidth / #GroupText
else
GroupWidth = ChartWidth / #GroupText
Mult.X = GroupWidth
end
else
Mult.X = ChartWidth / (math.abs(Parms["XMax"] - Parms["XMin"]))
end
Mult.Y = ChartHeight / math.abs(Parms["YMax"] - Parms["YMin"])
if DoYAxis2 then
Mult.Y2 = ChartHeight / math.abs(Parms["Y2Max"] - Parms["Y2Min"])
else
Mult.Y2 = 1
end
--
FontSiz.XAxisTitle = 6 * BaseFontSize * Parms["XAxisTitleFontSize"] / 100
FontSiz.XAxisValues = 5 * BaseFontSize * Parms["XAxisValuesFontSize"] / 100
FontSiz.YAxisTitle = 6 * BaseFontSize * Parms["YAxisTitleFontSize"] / 100
FontSiz.YAxisValues = 5 * BaseFontSize * Parms["YAxisValuesFontSize"] / 100
FontSiz.YAxis2Title = 6 * BaseFontSize * Parms["YAxis2TitleFontSize"] / 100
FontSiz.YAxis2Values = 5 * BaseFontSize * Parms["YAxis2ValuesFontSize"] / 100
Siz.AxisMark = 2 * BaseUnit
Siz.AxisMark2 = 1 * BaseUnit
Siz.XAxis.Values = FontSiz.XAxisValues + Siz.Text.Interline
Siz.XAxis.Title = iif(Parms["XAxisTitle"] ~= nil, FontSiz.XAxisTitle + Siz.Text.Interline, 0)
Siz.YAxis.Values = (2 * FontSiz.YAxisValues) + Siz.Text.Interline
Siz.YAxis.Title = iif(Parms["YAxisTitle"] ~= nil, FontSiz.YAxisTitle + Siz.Text.Interline, 0)
Siz.YAxis2.Values = 0
Siz.YAxis2.Title = 0
if DoYAxis2 then
Siz.YAxis2.Values = (2 * FontSiz.YAxis2Values) + Siz.Text.Interline
if Parms["YAxis2Title"] ~= nil then
Siz.YAxis2.Title = FontSiz.YAxis2Title + Siz.Text.Interline
end
end
-- spaces around the chart (working out from the chart):
-- AxisMarks
-- ChartPadding
-- AxisValues
-- AxisTitle
-- Title
-- Legend
-- Footnote
-- ImagePadding
-- Top Space
Pos.Title = Siz.ImagePadding.Top + FontSiz.Title
Siz.Space.Top = Siz.ImagePadding.Top + Siz.Text.Title + Siz.ChartPadding
-- Bottom Space
if Parms["YMin"] < 0 then
-- X axis line is within chart
Pos.XAxis.Line = Siz.Space.Top + ChartHeight
- round(((0 - Parms["YMin"]) * Mult.Y), 1)
Pos.XAxis.Marks = Pos.XAxis.Line
Pos.XAxis.Values = Pos.XAxis.Line + Siz.AxisMark + FontSiz.XAxisValues
Siz.Space.Bottom = Siz.ChartPadding
else
-- X axis line is at bottom of chart
Pos.XAxis.Line = Siz.Space.Top + ChartHeight
Pos.XAxis.Marks = Pos.XAxis.Line
Siz.Space.Bottom = iif(#GroupText > 0, 0, Siz.AxisMark)
Pos.XAxis.Values = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + Siz.ChartPadding + FontSiz.XAxisValues
Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartPadding + Siz.XAxis.Values
end
if Parms["XAxisTitle"] ~= nil then
Pos.XAxis.Title = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + FontSiz.XAxisTitle
Siz.Space.Bottom = Siz.Space.Bottom + Siz.XAxis.Title
end
if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline
end
if Parms["Footnote"] ~= nil then
Pos.Footnote = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + Siz.Footnote
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote
end
Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom
-- Left Space
Siz.Space.Left = Siz.ImagePadding.Left
if Parms["YAxisTitle"] ~= nil then
Pos.YAxis.Title = Siz.Space.Left + FontSiz.YAxisTitle -- pos is right edge of title
Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Title
end
if Parms["XMin"] < 0 and #GroupText == 0 then
-- Y axis line is within chart
Siz.Space.Left = Siz.Space.Left + Siz.ChartPadding
Pos.YAxis.Line = Siz.Space.Left
+ round(((0 - Parms["XMin"]) * Mult.X), 1)
Pos.YAxis.Marks = Pos.YAxis.Line - Siz.AxisMark -- pos is left edge of marks box
Pos.YAxis.Values = Pos.YAxis.Marks - Siz.ChartPadding -- pos is right edge of values box
else
-- Y axis line is at left of chart
Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Values + Siz.ChartPadding + Siz.AxisMark
Pos.YAxis.Line = Siz.Space.Left
Pos.YAxis.Marks = Pos.YAxis.Line - Siz.AxisMark -- pos is left edge of marks box
Pos.YAxis.Values = Pos.YAxis.Marks - Siz.ChartPadding -- pos is right edge of values box
end
-- Right Space
Pos.YAxis2.Line = Siz.Space.Left + ChartWidth
Pos.YAxis2.Marks = Pos.YAxis2.Line
Siz.Space.Right = Siz.AxisMark
Pos.YAxis2.Values = Pos.YAxis2.Line + Siz.Space.Right + Siz.ChartPadding -- pos is left edge of values box
Siz.Space.Right = Siz.Space.Right + Siz.ChartPadding + Siz.YAxis2.Values
Pos.YAxis2.Title = Siz.Space.Left + ChartWidth + Siz.Space.Right + FontSiz.YAxis2Title
Siz.Space.Right = Siz.Space.Right + Siz.YAxis2.Title
if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Left + ChartWidth + Siz.Space.Right + Siz.Legend.Offset
Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width
end
Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right
Pos.XAxis.Zero = Siz.Space.Left - (iif(#GroupText == 0, Parms["XMin"], 0) * Mult.X)
Pos.YAxis.Zero = Siz.Space.Top + ChartHeight + (Parms["YMin"] * Mult.Y)
Pos.YAxis2.Zero = Siz.Space.Top + ChartHeight + (Parms["Y2Min"] * Mult.Y2)
-- Image size
ImageWidth = round(Siz.Space.Left + ChartWidth + Siz.Space.Right, 0)
ImageHeight = round(Siz.Space.Top + ChartHeight + Siz.Space.Bottom, 0)
end
function prelimsPie()
Siz.Space = {}
-- pie chart radius & origin
PieRadius = Parms["PieRadius"] -- not local
PieOriginX, PieOriginY = PieRadius, PieRadius -- not local
if Parms["Explode"] ~= nil then
PieOriginX = PieOriginX + (PieRadius * Parms["ExplodeRadius"] / 100)
PieOriginY = PieOriginY + (PieRadius * Parms["ExplodeRadius"] / 100)
end
if Parms["SegmentText"] ~= nil then
-- possibly add for segment texts outside the pie
local TextSpaceX = iif(Parms["Explode"] ~= nil, (PieRadius * Parms["ExplodeRadius"] / 100), 0)
+ (PieRadius * Parms["SegmentTextRadius"] / 100)
+ (5 * Siz.Text.Chart * Parms["SegmentTextWidth"] / 100)
PieOriginX = math.max(PieOriginX, TextSpaceX)
local TextSpaceY = iif(Parms["Explode"] ~= nil, (PieRadius * Parms["ExplodeRadius"] / 100), 0)
+ (PieRadius * Parms["SegmentTextRadius"] / 100)
+ (Siz.Text.Chart)
PieOriginY = math.max(PieOriginY, TextSpaceY)
end
-- for pie charts, chart size is calculated, not user-defined
ChartWidth = PieOriginX * 2
ChartHeight = PieOriginY * 2
-- Top Space
Siz.Space.Top = Siz.ImagePadding.Top + Siz.Text.Title + Siz.ChartPadding
Pos.Title = Siz.ImagePadding.Top + FontSiz.Title
-- Bottom Space
Siz.Space.Bottom = Siz.ChartPadding
if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline
end
if Parms["Footnote"] ~= nil then
Pos.Footnote = Siz.Space.Top + ChartHeight + Siz.Space.Bottom + Siz.Footnote
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote
end
Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom
-- Left Space
Siz.Space.Left = Siz.ImagePadding.Left + Siz.ChartPadding
-- Right Space
Siz.Space.Right = Siz.ChartPadding
if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Left + ChartWidth + Siz.Space.Right + Siz.Legend.Offset
Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width
end
Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right
-- Image size
ImageWidth = round(Siz.Space.Left + ChartWidth + Siz.Space.Right, 0)
ImageHeight = round(Siz.Space.Top + ChartHeight + Siz.Space.Bottom, 0)
-- Mult.X and Mult.Y are needed for positioning of Title, Footnote and Chart texts
Mult.X = round(ChartWidth / 100, 2)
Mult.Y = round(ChartHeight / 100, 2)
end
function commonTop()
-- output header
table.insert(r, "Select and copy the following text. Paste it into a plain text file. The text file should have an svg extension, for example ''mychart.svg''.")
table.insert(r, " ")
-- SVG header stuff
table.insert(r, " <?xml version=\"1.0\" encoding=\"UTF-8\" ?>")
table.insert(r, " <!-- Generator: en.wikipedia.org/wiki/Module:Charts SVG -->")
table.insert(r, " <svg id=\"head\"")
table.insert(r, " xmlns=\"http://www.w3.org/2000/svg\"")
table.insert(r, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"")
table.insert(r, " version=\"1.1\"")
table.insert(r, " font-family=\"Liberation Sans, Arial, sans-serif\"")
table.insert(r, " width=\"" .. ImageWidth .. "\"")
table.insert(r, " height=\"" .. ImageHeight .. "\"")
table.insert(r, " >")
table.insert(r, " ")
table.insert(r, " <title>" .. Parms["FileTitle"] .. "</title>")
table.insert(r, " <desc>")
table.insert(r, " " .. Parms["FileDesc"] .. "")
table.insert(r, " </desc>")
table.insert(r, " ")
local DT = os.date("*t") -- returns a table with the current date & time
table.insert(r, " <metadata>")
table.insert(r, " <rdf:RDF")
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, " <rdf:Description")
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, " </rdf:Description>")
table.insert(r, " </rdf:RDF>")
table.insert(r, " </metadata>")
table.insert(r, " ")
table.insert(r, " <!-- == Backgrounds == -->")
table.insert(r, " ")
-- image background rectangle
table.insert(r, " <!-- image background -->")
table.insert(r, " <rect id=\"imagebackground\" x=\"0\" y=\"0\" width=\"" .. ImageWidth .. "\" height=\"" .. ImageHeight .. "\""
.. " 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, " <rect id=\"ChartBackground\" x=\"" .. Siz.Space.Left .. "\" y=\"" .. Siz.Space.Top .. "\""
.. " width=\"" .. ChartWidth .. "\""
.. " height=\"" .. ChartHeight .. "\""
.. " fill=\"" .. Parms["ChartBackgroundColor"] .. "\"/>")
table.insert(r, " ")
end
end
function codeAxisGrids()
local XAxisGridStepStretch
if #GroupText == 0 and tonumber(Parms["XGrid"]) ~= nil then
XAxisGridStepStretch = round(Parms["XGrid"] * Mult.X, 2)
end
local YAxisGridStepStretch
if tonumber(Parms["YGrid"]) ~= nil then
YAxisGridStepStretch = round(Parms["YGrid"] * Mult.Y, 2)
end
if Parms["XGrid"] ~= KeyWords["none"] or Parms["YGrid"] ~= KeyWords["none"] then
table.insert(r, " <!-- == Axis Grids == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
table.insert(r, " .gridline {")
table.insert(r, " stroke: lightgrey;")
table.insert(r, " stroke-width: " .. BaseLineWidth .. ";")
table.insert(r, " }")
table.insert(r, " ]]>")
table.insert(r, " </style>")
table.insert(r, " ")
table.insert(r, " <defs>")
if #GroupText == 0 and Parms["XGrid"] ~= KeyWords["none"] then
table.insert(r, " <!-- x-axis grid lines, vertical -->")
table.insert(r, " <pattern id=\"x-gridline\""
.. " x=\"" .. Pos.XAxis.Zero .. "\""
.. " y=\"" .. Pos.YAxis.Zero .. "\""
.. " height=\"" .. ChartHeight .. "\""
.. " width=\"" .. XAxisGridStepStretch .. "\""
.. " patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\""
.. " y1=\"0\""
.. " x2=\"0\""
.. " y2=\"" .. ChartHeight .. "\""
.. " class=\"gridline\"/>")
table.insert(r, " </pattern>")
end
if Parms["YGrid"] ~= KeyWords["none"] then
table.insert(r, " <!-- y-axis grid lines, horizontal -->")
table.insert(r, " <pattern id=\"y-gridline\""
.. " x=\"" .. Pos.XAxis.Zero .. "\""
.. " y=\"" .. Pos.YAxis.Zero .. "\""
.. " height=\"" .. YAxisGridStepStretch .. "\""
.. " width=\"" .. ChartWidth .. "\""
.. " patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\""
.. " y1=\"0\""
.. " x2=\"" .. ChartWidth .. "\""
.. " y2=\"0\""
.. " class=\"gridline\"/>")
table.insert(r, " </pattern>")
end
table.insert(r, " </defs>")
table.insert(r, " ")
if #GroupText == 0 and Parms["XGrid"] ~= KeyWords["none"] then
table.insert(r, " <rect id=\"x-gridline-area\" x=\"" .. Siz.Space.Left .. "\" y=\"" .. Siz.Space.Top .. "\""
.. " width=\"" .. round(ChartWidth * 1.01, 1) .. "\""
.. " height=\"" .. ChartHeight .. "\""
.. " fill=\"url(#x-gridline)\"/>")
end
if Parms["YGrid"] ~= KeyWords["none"] then
table.insert(r, " <rect id=\"y-gridline-area\" x=\"" .. Siz.Space.Left .. "\" y=\"" .. Siz.Space.Top .. "\""
.. " width=\"" .. ChartWidth .. "\""
.. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""
.. " fill=\"url(#y-gridline)\"/>")
end
table.insert(r, " ")
end
end
function stylesAreas()
table.insert(r, " <!-- == Graph Area Styles == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
table.insert(r, " /*-- general style of areas --*/")
table.insert(r, " .series-areas-general {")
if Parms["BorderColor"] ~= nil then
table.insert(r, " stroke: " .. Parms["BorderColor"] .. ";")
table.insert(r, " stroke-width: " .. BaseLineWidth * Parms["BorderWidth"] / 100 .. ";")
else
table.insert(r, " stroke-width: " .. 0 .. ";")
end
table.insert(r, " }")
if DoPie then
for k, v in ipairs(SeriesText) do
codeStyleArea(k, Color[k], FillPattern[k])
end
else
for i = 1, #SeriesData do
if SeriesData[i] ~= nil and (SType[i] == KeyWords["bar"] or DoArea) then
codeStyleArea(i, Color[i], FillPattern[i])
end
end
end
table.insert(r, " ]]>")
table.insert(r, " </style>")
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, " <defs>")
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, " </defs>")
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 = " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("
if PatternType == 1 then
l = l .. "0"
elseif PatternType == 2 then
l = l .. "90"
elseif PatternType == 3 then
l = l .. "45"
elseif PatternType == 4 then
l = l .. "-45"
elseif PatternType == 5 then
l = l .. "22.5"
elseif PatternType == 6 then
l = l .. "-22.5"
elseif PatternType == 7 then
l = l .. "67.5"
elseif PatternType == 8 then
l = l .. "-67.5"
end
table.insert(r, l .. ")\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <line x1=\"0\" y1=\"2\" x2=\"6\" y2=\"2\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>")
table.insert(r, " </pattern>")
elseif PatternType >= 11 and PatternType <= 18 then
-- line hatches, wide
l = " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("
if PatternType == 11 then
l = l .. "0"
elseif PatternType == 12 then
l = l .. "90"
elseif PatternType == 13 then
l = l .. "45"
elseif PatternType == 14 then
l = l .. "-45"
elseif PatternType == 15 then
l = l .. "22.5"
elseif PatternType == 16 then
l = l .. "-22.5"
elseif PatternType == 17 then
l = l .. "67.5"
elseif PatternType == 18 then
l = l .. "-67.5"
end
table.insert(r, l .. ")\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <line x1=\"0\" y1=\"8\" x2=\"12\" y2=\"8\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>")
table.insert(r, " </pattern>")
elseif PatternType >= 21 and PatternType <= 24 then
-- cross hatches, close
l = " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("
if PatternType == 21 then
l = l .. "0"
elseif PatternType == 22 then
l = l .. "45"
elseif PatternType == 23 then
l = l .. "22.5"
elseif PatternType == 24 then
l = l .. "67.5"
end
table.insert(r, l .. ")\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <line x1=\"0\" y1=\"4\" x2=\"6\" y2=\"4\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>")
table.insert(r, " <line x1=\"4\" y1=\"0\" x2=\"4\" y2=\"6\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>")
table.insert(r, " </pattern>")
elseif PatternType >= 31 and PatternType <= 34 then
-- cross hatches, wide
l = " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate("
if PatternType == 31 then
l = l .. "0"
elseif PatternType == 32 then
l = l .. "45"
elseif PatternType == 33 then
l = l .. "22.5"
elseif PatternType == 34 then
l = l .. "67.5"
end
table.insert(r, l .. ")\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <line x1=\"0\" y1=\"2\" x2=\"12\" y2=\"2\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>")
table.insert(r, " <line x1=\"4\" y1=\"0\" x2=\"4\" y2=\"12\" stroke=\"" .. PatternColor .. "\" stroke-width=\"2\"/>")
table.insert(r, " </pattern>")
elseif PatternType >= 41 and PatternType <= 49 then
-- stipples
if PatternType == 41 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"6\" y=\"6\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 42 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"4\" y=\"4\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 43 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"3\" y=\"3\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 44 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"4\" height=\"4\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"2\" y=\"2\" width=\"1\" height=\"1\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 45 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"6\" y=\"6\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 46 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"4\" y=\"4\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 47 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"3\" y=\"3\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 48 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"4\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"8\" y=\"12\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 49 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"6\" y=\"6\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
end
table.insert(r, l .. " </pattern>")
elseif PatternType >= 51 and PatternType <= 56 then
-- checks
if PatternType == 51 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"4\" height=\"4\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"2\" y=\"2\" width=\"2\" height=\"2\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 52 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"3\" height=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"3\" y=\"3\" width=\"3\" height=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 53 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"4\" y=\"4\" width=\"4\" height=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 54 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"10\" height=\"10\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"5\" height=\"5\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"5\" y=\"5\" width=\"5\" height=\"5\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 55 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"12\" height=\"12\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"12\" height=\"12\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"6\" y=\"6\" width=\"6\" height=\"6\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 56 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"8\" height=\"8\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <rect x=\"8\" y=\"8\" width=\"8\" height=\"8\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
end
table.insert(r, " </pattern>")
elseif PatternType >= 61 and PatternType <= 64 then
-- circles
if PatternType == 61 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <circle cx=\"4\" cy=\"4\" r=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <circle cx=\"12\" cy=\"12\" r=\"3\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 62 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"20\" height=\"20\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
table.insert(r, " <circle cx=\"15\" cy=\"15\" r=\"4\" fill=\"" .. PatternColor .. "\" stroke=\"none\"/>")
elseif PatternType == 63 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"16\" height=\"16\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <circle cx=\"4\" cy=\"4\" r=\"3\" fill=\"none\" stroke=\"" .. PatternColor .. "\"/>")
table.insert(r, " <circle cx=\"12\" cy=\"12\" r=\"3\" fill=\"none\" stroke=\"" .. PatternColor .. "\"/>")
elseif PatternType == 64 then
table.insert(r, " <pattern id=\"series" .. SeriesNumber .. "pattern\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <rect x=\"0\" y=\"0\" width=\"20\" height=\"20\"" .. " fill=\"" .. FillColor .. "\" stroke=\"none\"/>")
table.insert(r, " <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"none\" stroke=\"" .. PatternColor .. "\" stroke-width=\"1\"/>")
table.insert(r, " <circle cx=\"15\" cy=\"15\" r=\"4\" fill=\"none\" stroke=\"" .. PatternColor .. "\" stroke-width=\"1\"/>")
end
table.insert(r, " </pattern>")
end
end
function stylesLines()
table.insert(r, " <!-- == Graph Line Styles == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
table.insert(r, " /*-- general style of graph lines --*/")
table.insert(r, " .series-lines-general {")
table.insert(r, " stroke-width: " .. BaseLineWidth * Parms["GraphLineWidth"] / 100 .. ";")
table.insert(r, " stroke-linejoin: round;")
table.insert(r, " stroke-linecap: round;")
table.insert(r, " fill: none;")
table.insert(r, " }")
if #Marker > 0 then
table.insert(r, " /*-- general style of markers --*/")
table.insert(r, " .graph-marker {")
table.insert(r, " stroke-width: " .. BaseLineWidth .. ";")
table.insert(r, " fill: white;")
table.insert(r, " stroke-linejoin: round;")
table.insert(r, " }")
end
for i = 1, #SeriesData do
if SeriesData[i] ~= nil and SType[i] == KeyWords["line"] then
local lw = 0
if LineWidth[i] ~= nil then
lw = BaseLineWidth * LineWidth[i] / 100
end
codeStyleLineMarker(i, LineShow[i], Color[i], lw, LineDash[i], Marker[i], MarkerFill[i])
end
end
table.insert(r, " ]]>")
table.insert(r, " </style>")
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, " <defs>")
table.insert(r, " <g class=\"graph-marker\">")
for i = 1, #SeriesData do
if Marker[i] ~= nil and Marker[i] ~= KeyWords["none"] then
-- define the shape for the marker
codeDefMarkerShape(i, Marker[i], BaseUnit * 2 * MarkerSize[i] / 100, MarkerFill[i])
-- and use the shape in the definition of the marker
codeDefMarkerCreate(i, BaseUnit * 2 * MarkerSize[i] / 100)
table.insert(r, " ")
end
end
table.insert(r, " </g>")
table.insert(r, " </defs>")
table.insert(r, " ")
end
end
function codeDefMarkerShape(SeriesNumber, MarkerType, MarkerSize, MarkerFill)
-- definition of a shape for a series
-- SeriesNumber, numeric
-- MarkerType, numeric or text, if text (eg: 'yes') the default is SeriesNumber
-- MarkerSize, numeric
-- MarkerFill, text
-- addition of further markers will require changes in checkParms() to allow them
local l, t = "", ""
table.insert(r, " <g id=\"series" .. SeriesNumber .. "markershape\">")
if MarkerType ~= nil then
-- MarkerType is a number, use it for the type
else
-- MarkerType is not a number, use the series number
MarkerType = SeriesNumber
end
-- all shapes are defined around a centre point of 0,0
if MarkerType == 2 then
-- circle
table.insert(r, " <!-- circle -->")
l = " <circle cx=\"0\" dx=\"0\" r=\"" .. round(MarkerSize / 2, 2) .. "\""
if MarkerFill ~= nil then
l = l .. " fill=\"" .. MarkerFill .. "\""
end
table.insert(r, l .. "/>")
elseif MarkerType == 3 then
-- triangle (point up)
t = round(MarkerSize / 2, 2)
table.insert(r, " <!-- triangle, point up -->")
l = " <polygon points=\"" .. -t .. "," .. t .. " " .. t .. "," .. t .. ", " .. 0 .. "," .. -t .."\""
if MarkerFill ~= nil then
l = l .. " fill=\"" .. MarkerFill .. "\""
end
table.insert(r, l .. "/>")
elseif MarkerType == 4 then
-- tilted square (diamond)
table.insert(r, " <!-- diamond -->")
l = " <rect transform=\"rotate(45)\""
.. " x=\"" .. round(-MarkerSize / 2, 2) .. "\""
.. " y=\"" .. round(-MarkerSize / 2, 2) .. "\""
.. " width=\"" .. round(MarkerSize, 2) .. "\""
.. " height=\"" .. round(MarkerSize, 2) .. "\""
if MarkerFill ~= nil then
l = l .. " fill=\"" .. MarkerFill .. "\""
end
table.insert(r, l .. "/>")
elseif MarkerType == 5 then
-- tilted triangle (point down)
t = round(MarkerSize / 2, 2)
table.insert(r, " <!-- triangle, point down -->")
l = " <polygon points=\"" .. 0 .. "," .. t .. " " .. -t .. "," .. -t .. ", " .. t .. "," .. -t .. "\""
if MarkerFill ~= nil then
l = l .. " fill=\"" .. MarkerFill .. "\""
end
table.insert(r, l .. "/>")
elseif MarkerType == 6 then
-- cross
t = round(MarkerSize / 2, 2)
table.insert(r, " <!-- cross -->")
table.insert(r, " <path d=\"M " .. -t .. "," .. t .. " L " .. t .. "," .. -t .. " z M " .. t .. "," .. t .. " L " .. -t .. "," .. -t .. " z\"/>")
elseif MarkerType == 7 then
-- plus
t = round(MarkerSize / 2, 2)
table.insert(r, " <!-- plus -->")
table.insert(r, " <path d=\"M " .. 0 .. "," .. t .. " L " .. 0 .. "," .. -t .. " z M " .. -t .. "," .. 0 .. " L " .. t .. "," .. 0 .. " z\"/>")
else
-- default and 1
-- square
table.insert(r, " <!-- square -->")
l = " <rect x=\"" .. round(-MarkerSize / 2, 2) .. "\""
.. " y=\"" .. round(-MarkerSize / 2, 2) .. "\""
.. " width=\"" .. round(MarkerSize, 2) .. "\""
.. " height=\"" .. round(MarkerSize, 2) .. "\""
if MarkerFill ~= nil then
l = l .. " fill=\"" .. MarkerFill .. "\""
end
table.insert(r, l .. "/>")
end
table.insert(r, " </g>")
end
function codeDefMarkerCreate(SeriesNumber, MarkerSize)
-- definition of a marker for a series
-- SeriesNumber, numeric
-- MarkerSize, numeric
local l = ""
l = " <marker id=\"series" .. SeriesNumber .. "marker\""
l = l .. " class=\"series" .. SeriesNumber .. "-marker\""
l = l .. " viewBox=\"0 0 " .. round(MarkerSize, 2) .. " " .. round(MarkerSize, 2) .. "\""
.. " markerWidth=\"" .. round(MarkerSize, 2) .. "\""
.. " markerHeight=\"" .. round(MarkerSize, 2) .. "\""
.. " overflow=\"visible\""
.. " markerUnits=\"userSpaceOnUse\">"
table.insert(r, l)
table.insert(r, " <use xlink:href=\"#series" .. SeriesNumber .. "markershape\"/>")
table.insert(r, " </marker>")
end
function elementsGraphs()
local BarNumber = BarSeriesCount + 1
local LabelPos = {}
table.insert(r, " <!-- == Graph Bars and Lines == -->")
table.insert(r, " ")
table.insert(r, " <g id=\"graphs\" transform=\"translate(" .. Siz.Space.Left .. ", " .. Siz.Space.Top + ChartHeight .. ")\">")
for i = #SeriesData, 1, -1 do
if SeriesText[i] == nil then
table.insert(r, " <!-- " .. "Series" .. i .. " -->")
else
table.insert(r, " <!-- " .. SeriesText[i] .. " -->")
end
if DoYAxis2 and YAxis2[i] ~= nil then
table.insert(r, " <!-- Y values are on right Y axis -->")
end
if Parms["IncludeOriginalData"] == KeyWords["no"]
or (Parms["IncludeOriginalData"] == KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then
table.insert(r, " <!-- 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 = round((px - 1) * GroupWidth
+ UnitWidth / 2
+ (BarSpace / 2), 1)
else
px = round((px - 1) * GroupWidth
+ UnitWidth / 2
+ UnitWidth * (BarNumber - 1)
+ (BarSpace / 2), 1)
end
else
px = round(((px - Parms["XMin"]) * Mult.X) - (BarWidth / 2), 1)
end
local val = tonumber(v[2])
local top, ht = 0, 0
local Min, Multiply = Parms["YMin"], Mult.Y
if DoYAxis2 and YAxis2[i] ~= nil then
Min = Parms["Y2Min"]
Multiply = Mult.Y2
end
if Min >= 0 then
top = Min - val
ht = val - Min
elseif val >= 0 then
top = Min - val
ht = val
else
top = Min
ht = -val
end
top = round(top * Multiply, 1)
ht = round(ht * Multiply, 1)
table.insert(r, " <rect id=\"series" .. i .. "-" .. k .. "\" class=\"series-areas-general series" .. i .. "\""
.. " x=\"" .. px .. "\" y=\"" .. top .. "\""
.. " width=\"" .. BarWidth .. "\" height=\"" .. ht .. "\" />")
if Labels[i] ~= nil then
LabelPos[i][k] = {}
LabelPos[i][k]["x"] = px + (BarWidth / 2)
if val <= 0 then
LabelPos[i][k]["y"] = top + ht + Siz.Text.Interline + Siz.Text.Labels
else
LabelPos[i][k]["y"] = top - Siz.Text.Interline
end
end
end
end
else
-- line
tr = " <polyline id=\"graph" .. i .. "\" class=\"series-"
if DoArea then
tr = tr .. "areas"
else
tr = tr .. "lines"
end
tr = tr .. "-general series" .. i .. "\""
table.insert(r, tr)
if Marker[i] ~= nil and Marker[i] ~= KeyWords["none"] then
-- set the line to have markers
table.insert(r, " marker-start=\"url(#series" .. i .. "marker)\" marker-mid=\"url(#series" .. i .. "marker)\" marker-end=\"url(#series" .. i .. "marker)\"")
end
table.insert(r, " points=\"")
local lastx = 0
-- multiply numeric values as necessary
for k, v in ipairs(SeriesData[i]) do
local px = tonumber(v[1])
if #GroupText > 0 then
px = round((px - 1) * GroupWidth
+ (GroupWidth / 2), 1)
else
px = round((px - Parms["XMin"]) * Mult.X, 1)
end
local py = tonumber(v[2])
if DoYAxis2 and YAxis2[i] ~= nil then
py = round((Parms["Y2Min"] - py) * Mult.Y2, 1)
else
py = round((Parms["YMin"] - py) * Mult.Y, 1)
end
if DoArea and k == 1 then
table.insert(r, " " .. px .. ", " .. iif(Parms["YMin"] < 0, Parms["YMin"] * Mult.Y, 0) .. "")
end
table.insert(r, " " .. px .. ", " .. py .. "")
lastx = px
if Labels[i] ~= nil then
LabelPos[i][k] = {}
LabelPos[i][k]["x"] = px + Siz.Text.Interline
LabelPos[i][k]["y"] = py - Siz.Text.Interline
end
end
if DoArea then
table.insert(r, " " .. lastx .. ", " .. iif(Parms["YMin"] < 0, Parms["YMin"] * Mult.Y, 0) .. "")
end
table.insert(r, " \"/>")
end
table.insert(r, " ")
end
if not DoStack100 and #Labels > 0 then
table.insert(r, " <!-- == Data Labels == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
table.insert(r, " .labeltext {")
table.insert(r, " font-size: " .. Siz.Text.Labels .. "px;")
table.insert(r, " }")
table.insert(r, " ]]>")
table.insert(r, " </style>")
table.insert(r, " ")
for i = #SeriesData, 1, -1 do
if Labels[i] ~= nil then
for k, v in ipairs(SeriesData[i]) do
if SType[i] == KeyWords["bar"] then
table.insert(r, " <text id=\"series" .. i .. "-" .. k .. "-label\" class=\"labelstext\""
.. " x=\"" .. LabelPos[i][k]["x"] .. "\""
.. " y=\"" .. LabelPos[i][k]["y"] .. "\""
.. " text-anchor=\"middle\">"
.. v[2]
.. "</text>")
else
table.insert(r, " <text id=\"series" .. i .. "-" .. k .. "-label\" class=\"labelstext\""
.. " x=\"" .. LabelPos[i][k]["x"] .. "\""
.. " y=\"" .. LabelPos[i][k]["y"] .. "\""
.. " text-anchor=\"left\">"
.. v[2]
.. "</text>")
end
end
end
end
end
table.insert(r, " </g>")
table.insert(r, " ")
end
function elementsPie()
-- pie segments
table.insert(r, " <!-- == Pie Segments == -->")
table.insert(r, " ")
table.insert(r, " <g id=\"segments\" class=\"series-areas-general\" transform=\"translate(" .. Siz.Space.Left + PieOriginX .. ", " .. Siz.Space.Top + PieOriginY .. ")\">")
if Parms["IncludeOriginalData"] == KeyWords["no"]
or (Parms["IncludeOriginalData"] == KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then
table.insert(r, " <!-- 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 = round(math.cos(Radian) * InnerRadius, 1)
InnerY = round(math.sin(Radian) * InnerRadius, 1)
end
-- outer arcs start at the pie radius
local OuterX = round(math.cos(Radian) * PieRadius, 1)
local OuterY = round(math.sin(Radian) * PieRadius, 1)
local Mid = {}
for k, v in ipairs(SeriesData) do
local Arc = 0 -- default is short arc (<= 180 degrees)
if (v[2] / Total * TwoPi) > math.pi then
Arc = 1 -- long arc
end
Mid[k] = Radian + ((v[2] / Total * TwoPi) / 2 * iif(Sweep == 0, 1, -1))
-- adjust X and Y for explode
local DX, DY = 0, 0
if Parms["Explode"] == nil or (type(Parms["Explode"]) == "number" and k > Parms["Explode"]) then
-- no explode for this segment
else
DX = round(math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1)
DY = round(math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1)
end
Radian = Radian + (v[2] / Total * TwoPi * iif(Sweep == 0, 1, -1))
local NextOuterX = round(math.cos(Radian) * PieRadius, 1)
local NextOuterY = round(math.sin(Radian) * PieRadius, 1)
t = " <path id=\"segment" .. k .. "\" class=\"series" .. k .. "\" d=\"M " .. (0 + InnerX + DX) .. ", " .. -(0 + InnerY + DY)
.. " l " .. (OuterX - InnerX) .. ", " .. -(OuterY - InnerY)
.. " a " .. PieRadius .. ", " .. PieRadius .. " 0 " .. Arc .. " " .. Sweep .. " " .. (NextOuterX - OuterX) .. ", " .. -(NextOuterY - OuterY)
if Parms["DoughnutHole"] == nil then
t = t .. " z\" />"
else
local NextInnerX = round(math.cos(Radian) * InnerRadius, 1)
local NextInnerY = round(math.sin(Radian) * InnerRadius, 1)
t = t .. " l " .. (NextInnerX - NextOuterX).. ", " .. -(NextInnerY - NextOuterY)
.. " a " .. InnerRadius .. ", " .. InnerRadius .. " 0 " .. Arc .. " " .. iif(Sweep == 0, 1, 0) .. " " .. (InnerX - NextInnerX) .. ", " .. -(InnerY - NextInnerY) .. "\" />"
InnerX = NextInnerX
InnerY = NextInnerY
end
table.insert(r, t)
OuterX = NextOuterX
OuterY = NextOuterY
end
table.insert(r, " </g>")
table.insert(r, " ")
if Parms["SegmentText"] ~= nil then
-- pie segment texts
table.insert(r, " <!-- == Pie Segment Texts == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
table.insert(r, " .segmenttext {")
table.insert(r, " font-size: " .. FontSiz.LegendText .. "px;")
table.insert(r, " }")
table.insert(r, " ]]>")
table.insert(r, " </style>")
table.insert(r, " ")
table.insert(r, " <g id=\"segmenttexts\" class=\"segmenttext\" transform=\"translate(" .. Siz.Space.Left + PieOriginX .. ", " .. Siz.Space.Top + PieOriginY .. ")\">")
for k, v in ipairs(SeriesData) do
-- adjust X and Y for explode
local DX, DY = 0, 0
if Parms["Explode"] == nil or (type(Parms["Explode"]) == "number" and k > Parms["Explode"]) then
-- no explode for this segment
else
DX = round(math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1)
DY = round(math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100), 1)
end
local TextX = round(math.cos(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100), 1)
local TextY = round(math.sin(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100), 1)
if TextY < 0 then
TextY = TextY - FontSiz.LegendText
end
t = " <text id=\"segment" .. k .. "text\""
.. " x=\"" .. (TextX + DX) .. "\" y=\"" .. -(TextY + DY) .. "\""
t = t .. " text-anchor=\"" .. iif(TextX < 0, "end", "start") .. "\">"
local tt = ""
if string.find(Parms["SegmentText"], KeyWords["text"], 1, true) > 0 then
tt = SeriesText[k]
end
if string.find(Parms["SegmentText"], KeyWords["value"], 1, true) > 0 then
tt = tt .. iif(string.len(tt) > 0, " ", "") .. v[2]
end
if string.find(Parms["SegmentText"], KeyWords["percent"], 1, true) > 0 then
tt = tt .. iif(string.len(tt) > 0, " ", "") .. round(v[2] / Total * 100, 0) .. "%"
end
t = t .. tt
table.insert(r, t .. "</text>")
end
table.insert(r, " </g>")
table.insert(r, " ")
end
end
function codeAxes()
--[=[ to display positioning tables
table.insert(r, "<!--")
listTable(Mult, " ", "Mult")
listTable(FontSiz, " ", "FontSiz")
listTable(Pos, " ", "Pos")
listTable(Siz, " ", "Siz")
table.insert(r, "-->")
]=]
-- axis styles
table.insert(r, " <!-- == Axis Styles == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
table.insert(r, " .axisline-x {")
table.insert(r, " stroke: black;")
table.insert(r, " stroke-width: " .. BaseLineWidth * 2 .. ";")
table.insert(r, " stroke-linecap: butt;")
table.insert(r, " }")
table.insert(r, " .axisline-y {")
table.insert(r, " stroke: " .. Parms["YAxisColor"] .. ";")
table.insert(r, " stroke-width: " .. BaseLineWidth * 2 .. ";")
table.insert(r, " stroke-linecap: butt;")
table.insert(r, " }")
table.insert(r, " .axisline-y2 {")
table.insert(r, " stroke: " .. Parms["YAxis2Color"] .. ";")
table.insert(r, " stroke-width: " .. BaseLineWidth * 2 .. ";")
table.insert(r, " stroke-linecap: butt;")
table.insert(r, " }")
table.insert(r, " .axismark-main {")
table.insert(r, " stroke: black;")
table.insert(r, " stroke-width: " .. BaseLineWidth .. ";")
table.insert(r, " }")
table.insert(r, " .axismark-second {")
table.insert(r, " stroke: black;")
table.insert(r, " stroke-width: " .. BaseLineWidth .. ";")
table.insert(r, " }")
table.insert(r, " .axistitle-x {")
table.insert(r, " font-size: " .. FontSiz.XAxisTitle .. "px;")
table.insert(r, " }")
table.insert(r, " .axisnumber-x {")
table.insert(r, " font-size: " .. FontSiz.XAxisValues .. "px;")
table.insert(r, " }")
table.insert(r, " .axistitle-y {")
table.insert(r, " font-size: " .. FontSiz.YAxisTitle .. "px;")
table.insert(r, " }")
table.insert(r, " .axisnumber-y {")
table.insert(r, " font-size: " .. FontSiz.YAxisValues .. "px;")
table.insert(r, " }")
if DoYAxis2 then
table.insert(r, " .axistitle-y2 {")
table.insert(r, " font-size: " .. FontSiz.YAxis2Title .. "px;")
table.insert(r, " }")
table.insert(r, " .axisnumber-y2 {")
table.insert(r, " font-size: " .. FontSiz.YAxis2Values .. "px;")
table.insert(r, " }")
end
table.insert(r, " ]]>")
table.insert(r, " </style>")
table.insert(r, " ")
-- calculate stretched values for grid & mark spacings
local XAxisMarkStepStretch = round(Parms["XAxisValueStep"] * Mult.X, 3)
local XAxisMark2StepStretch = 0
if Parms["XAxisMark2Step"] ~= nil then
XAxisMark2StepStretch = round(Parms["XAxisMark2Step"] * Mult.X, 3)
end
local YAxisMarkStepStretch = round(Parms["YAxisValueStep"] * Mult.Y, 3)
local YAxisMark2StepStretch = 0
if Parms["YAxisMark2Step"] ~= nil then
YAxisMark2StepStretch = round(Parms["YAxisMark2Step"] * Mult.Y, 3)
end
local YAxis2MarkStepStretch = round(Parms["YAxis2ValueStep"] * Mult.Y2, 3)
local YAxis2Mark2StepStretch = 0
if Parms["YAxis2Mark2Step"] ~= nil then
YAxis2Mark2StepStretch = round(Parms["YAxis2Mark2Step"] * Mult.Y2, 3)
end
table.insert(r, " <!-- == Axis Marks == -->")
table.insert(r, " ")
-- X Axis Marks
if #GroupText == 0 then
table.insert(r, " <defs>")
table.insert(r, " <pattern id=\"x-axismark-main\""
.. " x=\"" .. Pos.XAxis.Zero .. "\""
.. " width=\"" .. XAxisMarkStepStretch .. "\""
.. " height=\"" .. Siz.AxisMark .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"" .. Siz.AxisMark .. "\" class=\"axismark-main\"/>")
table.insert(r, " </pattern>")
if Parms["XAxisMark2Step"] ~= nil then
table.insert(r, " <pattern id=\"x-axismark-second\""
.. " x=\"" .. Pos.XAxis.Zero .. "\""
.. " width=\"" .. XAxisMark2StepStretch .. "\""
.. " height=\"" .. Siz.AxisMark2 .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"" .. Siz.AxisMark2 .. "\" class=\"axismark-second\"/>")
table.insert(r, " </pattern>")
end
table.insert(r, " </defs>")
table.insert(r, " ")
if Parms["XAxisMark2Step"] ~= nil then
table.insert(r, " <rect id=\"x-axismark2\""
.. " x=\"" .. Siz.Space.Left .. "\""
.. " y=\"" .. Pos.XAxis.Marks .. "\""
.. " width=\"" .. round(ChartWidth * 1.01, 1) .. "\""
.. " height=\"" .. Siz.AxisMark2 .. "\""
.. " fill=\"url(#x-axismark-second)\"/>")
end
table.insert(r, " <rect id=\"x-axismark\""
.. " x=\"" .. Siz.Space.Left .. "\""
.. " y=\"" .. Pos.XAxis.Marks .. "\""
.. " width=\"" .. round(ChartWidth * 1.01, 1) .. "\""
.. " height=\"" .. Siz.AxisMark .. "\""
.. " fill=\"url(#x-axismark-main)\"/>")
table.insert(r, " ")
end
-- Y Axis Marks
table.insert(r, " <defs>")
table.insert(r, " <pattern id=\"y-axismark-main\""
.. " y=\"" .. Pos.YAxis.Zero .. "\""
.. " width=\"" .. Siz.AxisMark .. "\""
.. " height=\"" .. YAxisMarkStepStretch .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark .. "\" y2=\"0\" class=\"axismark-main\"/>")
table.insert(r, " </pattern>")
if Parms["YAxisMark2Step"] ~= nil then
table.insert(r, " <pattern id=\"y-axismark-second\""
.. " y=\"" .. Pos.YAxis.Zero .. "\""
.. " width=\"" .. Siz.AxisMark2 .. "\""
.. " height=\"" .. YAxisMark2StepStretch .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark2 .. "\" y2=\"0\" class=\"axismark-second\"/>")
table.insert(r, " </pattern>")
end
table.insert(r, " </defs>")
table.insert(r, " ")
if Parms["YAxisMark2Step"] ~= nil then
table.insert(r, " <rect id=\"y-axismark2\""
.. " x=\"" .. Pos.YAxis.Marks + Siz.AxisMark - Siz.AxisMark2 .. "\""
.. " y=\"" .. Siz.Space.Top .. "\""
.. " width=\"" .. Siz.AxisMark2 .. "\""
.. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""
.. " fill=\"url(#y-axismark-second)\"/>")
end
table.insert(r, " <rect id=\"y-axismark\""
.. " x=\"" .. Pos.YAxis.Marks .. "\""
.. " y=\"" .. Siz.Space.Top .. "\""
.. " width=\"" .. Siz.AxisMark .. "\""
.. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""
.. " fill=\"url(#y-axismark-main)\"/>")
table.insert(r, " ")
-- Y Axis 2 Marks
if DoYAxis2 then
table.insert(r, " <defs>")
table.insert(r, " <pattern id=\"y-axis2mark-main\""
.. " y=\"" .. Pos.YAxis2.Zero .. "\""
.. " width=\"" .. Siz.AxisMark .. "\""
.. " height=\"" .. YAxis2MarkStepStretch .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark .. "\" y2=\"0\" class=\"axismark-main\"/>")
table.insert(r, " </pattern>")
if Parms["YAxis2Mark2Step"] ~= nil then
table.insert(r, " <pattern id=\"y-axis2mark-second\""
.. " y=\"" .. Pos.YAxis2.Zero.. "\""
.. " width=\"" .. Siz.AxisMark2 .. "\""
.. " height=\"" .. YAxis2Mark2StepStretch .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" x2=\"" .. Siz.AxisMark2 .. "\" y2=\"0\" class=\"axismark-second\"/>")
table.insert(r, " </pattern>")
end
table.insert(r, " </defs>")
table.insert(r, " ")
if Parms["YAxis2Mark2Step"] ~= nil then
table.insert(r, " <rect id=\"y-axis2mark2\""
.. " x=\"" .. Pos.YAxis2.Marks .. "\""
.. " y=\"" .. Siz.Space.Top .. "\""
.. " width=\"" .. Siz.AxisMark2 .. "\""
.. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""
.. " fill=\"url(#y-axis2mark-second)\"/>")
end
table.insert(r, " <rect id=\"y-axis2mark\""
.. " x=\"" .. Pos.YAxis2.Marks .. "\""
.. " y=\"" .. Siz.Space.Top .. "\""
.. " width=\"" .. Siz.AxisMark .. "\""
.. " height=\"" .. round(ChartHeight * 1.01, 1) .. "\""
.. " fill=\"url(#y-axis2mark-main)\"/>")
table.insert(r, " ")
end
table.insert(r, " <!-- == Axis Lines == -->")
table.insert(r, " ")
table.insert(r, " <line id=\"x-axis\""
.. " x1=\"" .. Siz.Space.Left .. "\""
.. " y1=\"" .. Pos.XAxis.Line .. "\""
.. " x2=\"" .. Siz.Space.Left + ChartWidth .. "\""
.. " y2=\"" .. Pos.XAxis.Line .. "\" class=\"axisline-x\"/>")
table.insert(r, " <line id=\"y-axis\""
.. " x1=\"" .. Pos.YAxis.Line .. "\""
.. " y1=\"" .. Siz.Space.Top .. "\""
.. " x2=\"" .. Pos.YAxis.Line .. "\""
.. " y2=\"" .. Siz.Space.Top + ChartHeight .. "\" class=\"axisline-y\"/>")
if DoYAxis2 then
table.insert(r, " <line id=\"y-axis2\""
.. " x1=\"" .. Pos.YAxis2.Line .. "\""
.. " y1=\"" .. Siz.Space.Top .. "\""
.. " x2=\"" .. Pos.YAxis2.Line .. "\""
.. " y2=\"" .. Siz.Space.Top + ChartHeight .. "\" class=\"axisline-y2\" />")
end
table.insert(r, " ")
table.insert(r, " <!-- == Axis Values == -->")
table.insert(r, " ")
local Anchor = "middle"
local RotateText = ""
if Parms["XAxisValueRotate"] ~= nil then
if Parms["XAxisValueRotate"] < 0 then
Anchor = "end"
else
Anchor = "start"
end
RotateText = " transform=\"rotate(" .. Parms["XAxisValueRotate"] .. ", "
end
if #GroupText == 0 then
t = (Parms["XMax"] >= 10000)
if Parms["XAxisValueFormat"] ~= nil then
t = not (Parms["XAxisValueFormat"] == 'none')
end
table.insert(r, " <g id=\"x-axis-values\" class=\"axisnumber-x\" text-anchor=\"" .. Anchor .. "\">")
codeAxisValues("x",
Pos.XAxis.Zero,
Parms["XAxisValueStep"],
Parms["XMin"], Parms["XMax"],
Mult.X, 0, round(Pos.XAxis.Values, 1),
Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"],
Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"],
t, RotateText)
table.insert(r, " </g>")
table.insert(r, " ")
else
table.insert(r, " <g id=\"x-axis-values\" class=\"axisnumber-x\" transform=\"translate("
.. Siz.Space.Left .. ", "
.. Pos.XAxis.Values
.. ")\" text-anchor=\"" .. Anchor .. "\">")
for k, v in ipairs(GroupText) do
local bx = ((k - 1) * GroupWidth)
+ (GroupWidth / 2)
table.insert(r, " <text x=\"" .. round(bx, 1) .. "\""
.. iif(string.len(RotateText) > 0, RotateText .. round(bx, 1) .. ", " .. -round(FontSiz.XAxisValues / 3, 1) .. ")\"", "")
.. ">" .. v .. "</text>")
end
table.insert(r, " </g>")
table.insert(r, " ")
end
t = (Parms["YMax"] >= 10000)
if Parms["YAxisValueFormat"] ~= nil then
t = not (Parms["YAxisValueFormat"] == 'none')
end
table.insert(r, " <g id=\"y-axis-values\" class=\"axisnumber-y\" text-anchor=\"end\">")
codeAxisValues("y",
Pos.YAxis.Zero,
Parms["YAxisValueStep"],
Parms["YMin"], Parms["YMax"],
Mult.Y, FontSiz["YAxisValues"] / 3, round(Pos.YAxis.Values, 1),
Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"],
Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"],
t, "")
table.insert(r, " </g>")
table.insert(r, " ")
if DoYAxis2 then
t = (Parms["Y2Max"] >= 10000)
if Parms["YAxis2ValueFormat"] ~= nil then
t = not (Parms["YAxis2ValueFormat"] == 'none')
end
table.insert(r, " <g id=\"y-axis2-values\" class=\"axisnumber-y2\" text-anchor=\"start\">")
codeAxisValues("y",
Pos.YAxis2.Zero,
Parms["YAxis2ValueStep"],
Parms["Y2Min"], Parms["Y2Max"],
Mult.Y2, FontSiz["YAxis2Values"] / 3, round(Pos.YAxis2.Values, 1),
Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"],
Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"],
t, "")
table.insert(r, " </g>")
table.insert(r, " ")
end
if Parms["XAxisTitle"] ~= nil
or Parms["YAxisTitle"] ~= nil
or (DoYAxis2 and Parms["YAxis2Title"] ~= nil) then
table.insert(r, " <!-- == Axis Titles == -->")
table.insert(r, " ")
end
if Parms["XAxisTitle"] ~= nil then
table.insert(r, " <text id=\"title-x\" class=\"axistitle-x\""
.. " x=\"" .. Siz.Space.Left + (ChartWidth * 0.5) .. "\""
.. " y=\"" .. round(Pos.XAxis.Title, 1) .. "\" text-anchor=\"middle\">"
.. Parms["XAxisTitle"]
.. "</text>")
table.insert(r, " ")
end
if Parms["YAxisTitle"] ~= nil then
table.insert(r, " <text id=\"title-y\" class=\"axistitle-y\""
.. " x=\"-" .. Siz.Space.Top + (ChartHeight * 0.5) .. "\""
.. " y=\"" .. round(Pos.YAxis.Title, 1) .. "\""
.. " transform=\"rotate(-90)\" text-anchor=\"middle\">"
.. Parms["YAxisTitle"]
.. "</text>")
table.insert(r, " ")
end
if DoYAxis2 and Parms["YAxis2Title"] ~= nil then
table.insert(r, " <text id=\"title-y2\" class=\"axistitle-y2\""
.. " x=\"-" .. Siz.Space.Top + (ChartHeight * 0.5) .. "\""
.. " y=\"" .. round(Pos.YAxis2.Title, 1) .. "\""
.. " transform=\"rotate(-90)\" text-anchor=\"middle\">"
.. Parms["YAxis2Title"]
.. "</text>")
table.insert(r, " ")
end
end
function codeAxisValues(Axis, PosAxisZero, ValueStep, AxisMin, AxisMax, Stretch, YShift, OtherPos, ValueMultiplier, ValueRound, Prefix, Suffix, Format, XRotateText)
-- Parms for location
-- Axis, text: 'x' or 'y'
-- PosAxisZero, numeric
-- ValueStep, numeric
-- AxisMin, numeric
-- AxisMax, numeric
-- Stretch, numeric
-- YShift, numeric
-- OtherPos, numeric
-- Parms for format
-- ValueMultiplier, numeric
-- ValueRound, numeric
-- Prefix, text
-- Suffix, text
-- Format, boolean
-- XRotateText, text
local l = ""
local ValueStart = 0
if AxisMin ~= 0 then
ValueStart = math.ceil(AxisMin / ValueStep) * ValueStep
end
local Value = ValueStart
while Value <= AxisMax do
if Axis == 'x' then
Position = PosAxisZero + (Value * Stretch)
l = " <text x=\"" .. round(Position, 1) .. "\" y=\"" .. OtherPos .. "\""
.. iif(string.len(XRotateText) > 0, XRotateText .. round(Position, 1) .. ", " .. OtherPos-round(FontSiz.XAxisValues / 3, 1) .. ")\"", "")
.. ">"
else
Position = PosAxisZero - (Value * Stretch) + YShift
l = " <text x=\"" .. OtherPos .. "\" y=\"" .. round(Position, 1) .. "\">"
end
if Format then
l = l .. Prefix .. mw.getContentLanguage():formatNum(round(Value * ValueMultiplier, ValueRound)) .. Suffix .. "</text>"
else
l = l .. Prefix .. round(Value * ValueMultiplier, ValueRound) .. Suffix .. "</text>"
end
table.insert(r, l)
Value = Value + ValueStep
end
end
function commonBottom()
if (#SeriesText < 1 or Parms["LegendType"] == KeyWords["none"])
and Parms["Title"] == nil
and Parms["Footnote"] == nil
and #ChartText <= 0 then
-- no common-element styles
else
table.insert(r, " <!-- == Common-element Styles == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
if #SeriesText < 1 or Parms["LegendType"] == KeyWords["none"] then
-- no legend
else
table.insert(r, " .legendbox {")
table.insert(r, " stroke: " .. Parms["LegendBorder"] .. ";")
table.insert(r, " stroke-width: " .. BaseLineWidth .. ";")
table.insert(r, " fill: white;")
table.insert(r, " }")
table.insert(r, " .legendtext {")
table.insert(r, " font-size: " .. FontSiz.LegendText .. "px;")
table.insert(r, " text-anchor: start;")
table.insert(r, " }")
end
if Parms["Title"] ~= nil then
table.insert(r, " .titletext {")
table.insert(r, " font-size: " .. FontSiz.Title .. "px;")
table.insert(r, " }")
end
if Parms["Footnote"] ~= nil then
table.insert(r, " .footnotetext {")
table.insert(r, " font-size: " .. FontSiz.Footnote .. "px;")
table.insert(r, " }")
end
if #ChartText > 0 then
table.insert(r, " .charttext {")
table.insert(r, " font-size: " .. FontSiz.ChartText .. "px;")
table.insert(r, " }")
end
table.insert(r, " ]]>")
table.insert(r, " </style>")
table.insert(r, " ")
end
-- legend
if #SeriesText < 1 or 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 = " <g id=\"legend\" transform=\"translate("
if Parms["LegendType"] == KeyWords["horizontal"] then
-- horizontal legend
if Parms["LegendX"] ~= nil then
tr = tr .. round(Siz.Space.Left + (Parms["LegendX"] * Mult.X), 1)
else
tr = tr .. round(Siz.Space.Left, 1)
end
tr = tr .. ", "
if Parms["LegendY"] ~= nil then
tr = tr .. round(Siz.Space.Top + ChartHeight - (Parms["LegendY"] * Mult.Y), 1)
else
tr = tr .. round(Pos.Legend, 1)
end
table.insert(r, tr .. ")\">")
table.insert(r, " <rect id=\"legend-background\" class=\"legendbox\" x=\"0\" y=\"0\""
.. " width=\"" .. Siz.Legend.Width .. "\""
.. " height=\"" .. Siz.Legend.Height .. "\"/>")
local PosX, PosY = Siz.ChartPadding, Siz.ChartPadding
for k, v in ipairs(SeriesText) do
table.insert(r, " ")
codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartPadding, Siz.Legend.Text, v, Marker[k])
PosX = PosX + Siz.Legend.ElementWidth
if k == LegendElementsInWidth then
-- new line of legend elements
PosX = Siz.ChartPadding
PosY = PosY + Siz.Legend.ElementHeight
end
end
else
-- vertical legend
if Parms["LegendX"] ~= nil then
tr = tr .. round(Siz.Space.Left + (Parms["LegendX"] * Mult.X), 1)
else
tr = tr .. round(Pos.Legend, 1)
end
tr = tr .. ", "
if Parms["LegendY"] ~= nil then
t = Parms["LegendY"]
else
t = 0.9 * math.abs(Parms["YMax"] - Parms["YMin"])
end
table.insert(r, tr .. round(Siz.Space.Top + ChartHeight - (t * Mult.Y), 1) .. ")\">")
table.insert(r, " <rect id=\"legend-background\" class=\"legendbox\" x=\"0\" y=\"0\""
.. " width=\"" .. Siz.Legend.Width .. "\""
.. " height=\"" .. Siz.Legend.Height .. "\"/>")
local PosX, PosY = Siz.ChartPadding, Siz.ChartPadding
for k, v in ipairs(SeriesText) do
table.insert(r, " ")
codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartPadding, Siz.Legend.Text, v, Marker[k])
PosY = PosY + Siz.Legend.ElementHeight
end
end
table.insert(r, " </g>")
table.insert(r, " ")
end
end
-- title and footnote
if Parms["Title"] ~= nil then
table.insert(r, " <!-- == Title Text == -->")
tr = " <text id=\"title\" class=\"titletext\" text-anchor=\"" .. iif(Parms["TitleX"] ~= nil or Parms["TitleY"] ~= nil, "left", "middle") .. "\""
.. " x=\""
if Parms["TitleX"] ~= nil then
tr = tr .. round(Siz.Space.Left + (Parms["TitleX"] * Mult.X), 1) .. "\""
else
tr = tr .. round(Siz.Space.Left + ChartWidth * 0.5, 1) .. "\""
end
tr = tr .. " y=\""
if Parms["TitleY"] ~= nil then
tr = tr .. round(Siz.Space.Top + ChartHeight - (Parms["TitleY"] * Mult.Y), 1) .. "\">"
else
tr = tr .. round(Pos.Title, 1) .. "\">"
end
table.insert(r, tr .. Parms["Title"] .. "</text>")
table.insert(r, " ")
end
if Parms["Footnote"] ~= nil then
table.insert(r, " <!-- == Footnote Text == -->")
tr = " <text id=\"footnote\" class=\"footnotetext\" text-anchor=\"" .. iif(Parms["FootnoteX"] ~= nil or Parms["FootnoteY"] ~= nil, "left", "end") .. "\""
.. " x=\""
if Parms["FootnoteX"] ~= nil then
tr = tr .. round(Siz.Space.Left + (Parms["FootnoteX"] * Mult.X), 1) .. "\""
else
tr = tr .. round(ImageWidth - Siz.ImagePadding.Right, 1) .. "\""
end
tr = tr .. " y=\""
if Parms["FootnoteY"] ~= nil then
tr = tr .. round(Siz.Space.Top + ChartHeight - (Parms["FootnoteY"] * Mult.Y), 1) .. "\">"
else
tr = tr .. round(Pos.Footnote, 1) .. "\">"
end
table.insert(r, tr .. Parms["Footnote"] .. "</text>")
table.insert(r, " ")
end
-- chart texts
if #ChartText > 0 then
table.insert(r, " <!-- == Chart Texts == -->")
table.insert(r, " ")
table.insert(r, " <g id=\"charttexts\" class=\"charttext\" transform=\"translate(" .. Siz.Space.Left .. ", " .. Siz.Space.Top + ChartHeight .. ")\">")
for i = 1, #ChartText do
if ChartText[i] ~= nil then
table.insert(r, " <text x=\"" .. round((Parms["ChartText" .. i .. "X"] - iif(#GroupText == 0, Parms["XMin"], 0)) * Mult.X, 1) .. "\""
.. " y=\"" .. -round((Parms["ChartText" .. i .. "Y"] - Parms["YMin"]) * Mult.Y, 1) .. "\">"
.. ChartText[i] .. "</text>")
end
end
table.insert(r, " </g>")
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, " </svg>")
table.insert(r, " ")
end
function codeLegendElement(Lines, SeriesNumber, PosX, PosY, Padding, TextSize, Text, Marker)
-- one entry in the legend
-- Lines, boolean
-- SeriesNumber, numeric
-- PosX, numeric
-- PosY, numeric (= the top of the legend element)
-- Padding, numeric
-- TextSize, numeric
-- Text, text
-- Marker, text
local l = ""
if Lines then
local lineY = round(PosY + TextSize / 2, 1)
l = " <polyline id=\"legend-line" .. SeriesNumber .. "\" class=\"series-lines-general series" .. SeriesNumber .. "\""
.. " points=\""
.. round(PosX, 1) .. "," .. lineY .. " "
.. round(PosX + (TextSize * 1), 1) .. "," .. lineY .. " "
.. round(PosX + (TextSize * 2), 1) .. "," .. lineY .. "\""
if Marker ~= nil and Marker ~= 0 then
l = l .. " marker-mid=\"url(#series" .. SeriesNumber .. "marker)\""
end
table.insert(r, l .. "/>")
table.insert(r, " <text id=\"legend-text" .. SeriesNumber .. "\" class=\"legendtext\""
.. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 1) .. "\""
.. " y=\"" .. round(PosY + TextSize, 1) .. "\">"
.. Text .. "</text>")
else
table.insert(r, " <rect id=\"legend-area" .. SeriesNumber .. "\" class=\"series-areas-general series" .. SeriesNumber .. "\""
.. " x=\"" .. round(PosX, 1) .. "\""
.. " y=\"" .. round(PosY, 1) .. "\""
.. " width=\"" .. (TextSize * 2) .. "\""
.. " height=\"" .. TextSize .. "\"/>")
table.insert(r, " <text id=\"legend-text" .. SeriesNumber .. "\" class=\"legendtext\""
.. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 1) .. "\""
.. " y=\"" .. round(PosY + TextSize, 1) .. "\">"
.. Text .. "</text>")
end
end
function outputDebugInfo()
if Parms["Debug"] ~= nil and string.find(Parms["Debug"], KeyWords["parms"]) ~= nil then
-- list all arguments
table.insert(r, " All Arguments :")
for k, v in pairs(Args) do
table.insert(r, " " .. k .. "=" .. v)
end
table.insert(r, " ")
-- list all recognised parameters sorted by key
table.insert(r, " Parameters :")
for k, v in spairs(Parms) do
table.insert(r, " " .. k .. "=" .. v .. " (" .. type(v) .. ")")
end
table.insert(r, " ")
if string.find(Parms["Debug"], "parmsstop") ~= nil then
return false
end
end
if Parms["Debug"] ~= nil and string.find(Parms["Debug"], "tables") ~= nil then
-- list contents of internal tables
if DoStack or DoStack100 then
listTable(OriginalData, " ", "OriginalData")
end
listTable(SeriesData, " ", "SeriesData")
listTable(SType, " ", "SType")
listTable(YAxis2, " ", "YAxis2")
listTable(Labels, " ", "Labels")
listTable(Color, " ", "Color")
listTable(LineShow, " ", "LineShow")
listTable(LineWidth, " ", "LineWidth")
listTable(LineDash, " ", "LineDash")
listTable(Marker, " ", "Marker")
listTable(MarkerFill, " ", "MarkerFill")
listTable(MarkerSize, " ", "MarkerSize")
listTable(FillPattern, " ", "FillPattern")
listTable(FillPatternColor, " ", "FillPatternColor")
listTable(SeriesText, " ", "SeriesText")
listTable(GroupText, " ", "GroupText")
listTable(ChartText, " ", "ChartText")
table.insert(r, " ")
if string.find(Parms["Debug"], "tablesstop") ~= nil then
return false
end
end
table.insert(r, "----------")
table.insert(r, " ")
return true
end
function listTable(Tab, Indent, Title)
-- lists all contents of a table, iterating over all sub-tables
if Title ~= nil then
table.insert(r, " " .. Title .. ":")
end
for k, v in pairs(Tab) do
if type(v) == "table" then
table.insert(r, Indent .. k .. ":")
listTable(v, Indent .. " ", nil)
else
table.insert(r, Indent .. k .. ": " .. v .. " (" .. type(v) .. ")")
end
end
end
-- utility functions
function iif(test, tret, fret)
-- if test is true, returns tret if defined, otherwise returns true
-- if test is false, returns fret if defined, otherwise returns false
-- note that this function cannot be used to avoid evaluating either tret or fret, as they are both evaluated in the function call
if test then
if tret == nil then
return true
else
return tret
end
end
if fret == nil then
return false
end
return fret
end
function round(x, p)
-- round number x to precision p
-- p > 0 rounds to p decimal places, eg: round(4.57, 1) = 4.5
-- p = 0 (or not given) rounds to nearest integer
-- p < 0 rounds to p places above zero, eg: round(147, -1) = 150
local res
if type(x) ~= "number" then
return nil
end
if type(p) == "number" then
-- any decimal places in p are ignored
p = math.floor(p)
else
p = nil
end
if p == nil or p == 0 then
return math.floor(x + 0.5)
else
res = x * 10^p
res = math.floor(res + 0.5)
res = res * 10^-p
return res
end
end
function decPlaces(val)
-- returns the number of decimal places in a numeric value, or a string convertible to a number
val = tonumber(val)
if val == nil then
return 0
end
val = tostring(val)
local point = string.find(val, ".")
if point == 0 then
return 0
end
return string.len(val) - point
end
function copyTable(from, to)
-- copies a table to another table
local k, v
for k, v in ipairs(from) do
if type(v) == "table" then
to[k] = {}
copyTable(v, to[k])
else
to[k] = v
end
end
end
function spairs(t, order)
-- returns an iterator function that in turn returns table t in the order of the keys
-- sort is done using an optional order function
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
return {
[KeyWords.barChart] = barChart,
[KeyWords.lineChart] = lineChart,
[KeyWords.scatterChart] = scatterChart,
[KeyWords.mixedChart] = mixedChart,
[KeyWords.pieChart] = pieChart,
}