Module:Charts SVG
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
This module creates charts by the generation of code for an SVG file.
(For background to the project, and current issues with the module, see Module talk:Charts SVG.)
The module exposes five functions: barChart, lineChart, mixedChart, scatterChart and pieChart.
Setup & Usage
[edit]This module is not intended to be used in Wikipedia articles. It is best used in a page in your own userspace, or in a sandbox. On an existing page, add {{#invoke:Charts SVG}} (with further parameters) to the page, and click Preview. When you do, the module will show a box that includes a lot of text, starting with <?xml version="1.0" encoding="UTF-8" ?>.
The process of using the module from there involves a number of steps:
- on your computer, create a file with the filename extension '.svg' (eg: mychart.svg)
- open this file with a text editor (eg: Notepad)
- back in your browser, use your mouse to select the text within the box. It will start with <?xml version="1.0" encoding="UTF-8" ?> and end with </svg>.
- copy this text, paste it into the file on your computer, and save the file.
- To test the file is producing the image you want, you can view the file directly in some web browsers, or go to the Labs SVG Checker.
- Note that results in web browsers, and in the Wikimedia Labs checker, may be different from the final Wikimedia rendering. Currently the only way to be absolutely sure of the final rendering is to upload the file and view it on a Wikipedia page. See #Known Rendering Issues.
- Once you are happy with the image, upload the .svg file, then you can use it in a Wikipedia article using a [[File:]] link.
- If you upload the image to commons, which is preferred to uploading to individual wikis, please add [[Category:Charts created with Charts SVG]] to the bottom of the file description page for the image.
- It is also good practice to include the module-call that produced the image on the file description page.
Invoking the Module
[edit]The first parameter in the module call must be one of the following function names:
barChart | Creates a bar chart with X and Y axes, showing bars of height (or length) Y at position X. |
lineChart | Creates a line chart with X and Y axes, showing lines between specified X/Y points. |
mixedChart | Creates a chart with X and Y axes, with both bars and lines. |
scatterChart | Creates a scatter chart with X and Y axes, showing markers at specified X/Y points. |
pieChart | Creates a pie chart with segments proportional to each of a series of values. |
Without further parameters the module will produce (for barChart, lineChart, mixedChart and scatterChart) a chart with X and Y axes and gridlines, but no other content. For a pieChart, it will produce a completely blank image.
A note on terminology: throughout this documentation 'image' means the whole area produced by the SVG code produced by this module, while 'chart' refers to the area bounded by the X and Y axes (or, for pie charts, the rectangle that includes the pie). Most texts appear (by default) outside the area of the 'chart'.
- for some parameters named using the style eg: Series<N>Values, parameters are ignored beyond the first value of <N> that does not have a parameter defined: eg: if only Series1Values, Series2Values and Series4Values are defined, only series 1 and 2 will be used. This is noted in the parameter descriptions as "Must be in sequence."
Script Errors and Messages
[edit]Instead of the SVG code, you may get some red text saying Script error. You can click on that text to get the details of the error. If the details simply say 'Script error: No such module.', the module name is in error. WP is case-sensitive about module names, so make sure the name is 'Charts SVG'. If the details say 'Script error: The function you specified did not exist.', the first parameter is not one of the exposed function names.
Any other Script errors are errors in the module script itself. Hopefully these do not occur, but if they do, please report them at Module talk:Charts SVG, including the backtrace information and details of the module call.
Output may also, instead of SVG code, be messages headed "Charts SVG - Messages", containing messages from the module about issues with the supplied parameters. This can include a list of parameters whose names are not recognised. A maximum of 10 issues are reported at a time.
Parameters - X/Y Graphs
[edit]Chart Size
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
ChartWidth | bar line mixed scatter |
Sets the width of the chart, in pixels. Default: 500 | Numeric must be >= 200 |
ChartHeight | bar line mixed scatter |
Sets the width of the chart, in pixels. Default: 350 | Numeric must be >= 200 |
ChartAdjust | bar line mixed scatter |
Any value sets axis XMax, XMin, YMax and YMin values to be adjusted by the module to whole multiples of AxisValueStep (or if specified, AxisMark2) for the appropriate direction, and then the related chart dimension is adjusted similarly. This ensures the mark spacings are in exact whole numbers of pixels. The before and after adjustment values are written in a comment in the output SVG - using the adjusted values means further adjustment will not be required. Note: Y2Max and Y2Min are not adjusted. | string |
Axes
[edit]X and Y Axes
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
XMin YMin |
bar line mixed scatter |
The minimum value for the range of numbers shown on the axis. Default: 0 | Numeric |
XMax YMax |
bar line mixed scatter |
The maximum value for the range of numbers shown on the axis. Default: 100 | Numeric |
XAxisValueStep YAxisValueStep |
bar line mixed scatter |
The interval between intermediate values shown on the axis, and therefore the major tick-marks. Default: 10 | Numeric |
XAxisMark2Step YAxisMark2Step |
bar line mixed scatter |
The interval between minor tick-marks shown on the axis. If not set, minor tick-marks are not shown. | Numeric |
Grid Lines
[edit]Grid lines are shown on charts with axes by default, though defining any data groups will hide the XAxis grid lines. The default grid interval is that of the major tick-marks, which is equal to the ValueStep parameter for the axis.
Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
XGrid YGrid |
bar line mixed scatter |
A value of none will hide the grid lines for the axis. Any numeric value sets the grid line interval. Default: the ValueStep value for the axis. | Numeric or none |
Second Y Axis
[edit]A second Y axis can be shown on the right hand side of the chart. The switch to show this axis is setting Y2Max to any value. However if Stack or Stack100 are set, a second Y Axis will not be shown.
Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Y2Min | bar line mixed scatter |
The minimum value for the range of numbers shown on the axis. Default: 0 | Numeric |
Y2Max | bar line mixed scatter |
The maximum value for the range of numbers shown on the axis. | Numeric |
YAxis2ValueStep | bar line mixed scatter |
The interval between intermediate values shown on the axis, and therefore the major tick-marks. Default: 10 | Numeric |
YAxis2Mark2Step | bar line mixed scatter |
The interval between minor tick-marks shown on the axis. If not set, minor tick-marks are not shown. | Numeric |
Note that the zero points of the left and right Y axes will only automatically align if both YMin and Y2Min are zero.
Axis Titles and Values
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
XAxisTitle YAxisTitle YAxis2Title |
bar line mixed scatter |
Adds a title for the axis, which appears outside the axis values. | String |
XAxisValueMultiplier YAxisValueMultiplier YAxis2ValueMultiplier |
bar line mixed scatter |
Multiplies the displayed values by the specified value. A common pattern would be to set YAxisValueMultiplier to 0.001, which would show '1', '2', ... on the Y axis instead of '1,000', '2,000'. Note that the Y data values in this example must still be in the range YMin to YMax. Default: 1 | Numeric |
XAxisValueRound YAxisValueRound YAxis2ValueRound |
bar line mixed scatter |
Rounds the displayed values to the specified precision. Positive values round to that number of decimal places (but note that it will not add zeros to that number of places). Negative values round to that number of places above zero (eg: -3 will round to the nearest 1000). A value of zero will round to the nearest integer. The default is the number of decimal places in the axis ValueStep (eg: XAxisValueStep). | Numeric |
XAxisValueAbsolute YAxisValueAbsolute YAxis2ValueAbsolute |
bar line mixed scatter |
Any value sets the axis values to display as absolute values (ie: without -ve signs). This allows graphs centered around an axis (eg: population pyramids) to show +ve values on both sides of the axis. | Numeric |
XAxisValuePrefix YAxisValuePrefix YAxis2ValuePrefix |
bar line mixed scatter |
Add text to the start of the axis values. An underline in this text will be converted to a space when displayed. Commonly this would be $. | String |
XAxisValueSuffix YAxisValueSuffix YAxis2ValueSuffix |
bar line mixed scatter |
Add text to the end of the axis values. An underline in this text will be converted to a space when displayed. Commonly this would be %. | String |
XAxisValueFormat YAxisValueFormat YAxis2ValueFormat |
bar line mixed scatter |
By default the module will add numeric (eg: thousands) separators to numeric axis values if the maximum value for the axis (eg: XMax) is 10,000 or greater. Setting the ValueFormat parameter to none for an axis will suppress this formatting, any other value will force it to occur (regardless of the axis max value). Note that group names are text, and never re-formatted by the module. |
String |
XAxisValueRotate YAxisValueRotate YAxis2ValueRotate |
bar line mixed scatter |
Sets the angle by which the axis values are rotated. By default the module shows values in horizontal text, and centered on the mark. | Numeric (-90 to 90) |
XAxisValueSpace YAxisValueSpace YAxis2ValueSpace |
bar line mixed scatter |
Sets the space available for the axis values outside the chart. These only need to be set if the calculated spaces are too small. | Numeric +ve only |
Axis Lines
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
YAxisColor YAxis2Color |
bar line mixed scatter |
The color for the axis line. Must be a number in the colors table or an SVG color term. Default: black | String |
XAxisArrows YAxisArrows |
bar line mixed scatter |
Any value sets the axis line to show arrow heads at the ends of the line, though an arrow pointing in the negative direction is not shown unless the axis Min value is less than zero. | Any |
Data Series
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Series1Values (Series2Values, ...) Must be in sequence. |
bar line mixed scatter |
A string containing a series of X and Y numeric-value pairs, all separated by spaces. Each pair specifies a point (in scatter and line charts), or an X-position and bar-height (in bar charts). All points, lines and bars in a series are shown in the same style. | String |
Series1Type (Series2Type, ...) |
mixed |
Default: line |
String |
Series1YAxis2 (Series2YAxis2, ...) |
bar line mixed scatter |
Any value sets the series to use the 2nd Y axis (if it is displayed) as the scale for the Y values. | Any |
Series1Labels (Series2Labels, ...) |
bar line mixed scatter |
Any value sets the series to show the Y-value of each data point next to that point (or above the bar). The default size for the labels text is the same as that for chart texts. Labels are not shown if Stack100 is set. | String |
Series1Color (Series2Color, ...) |
bar line mixed scatter |
The color of the markers, lines or bars. Must be a number in the colors table or an SVG color term. The default color for series N is color N from the colors table. | String |
Series1Line (Series2Line, ...) |
line mixed scatter |
A value of none will hide the line between the points of the series, any other value will show a line. The default for scatter charts is none, for line charts yes. | String |
Series1Width (Series2Width, ...) |
line mixed scatter |
The width of the line for the series, as a percentage of the standard line width. Default: 100 | Numeric +ve only |
Series1Dash (Series2Dash, ...) |
line mixed scatter |
The dash pattern for the line for the series. Either a number (1 to 8), which will select a pattern from the dash patterns table, or an SVG dash pattern term. All lines are solid by default. | Numeric (1-8) or string |
Series1Marker (Series2Marker, ...) |
line mixed scatter |
The marker shown at the points for the series. A number (1 to 7) will select a marker from the markers table. none will hide the markers for the series. Markers and lines for a series are always the same color. | Numeric (0-7) or none |
Series1MarkerSize (Series2MarkerSize, ...) |
line mixed scatter |
The size of the series markers, as a percentage of the standard size. Default: 100 | Numeric +ve only |
Series1MarkerFill (Series2MarkerFill, ...) |
line mixed scatter |
The fill color for the series markers. Must be a number in the colors table or an SVG color term. The default color for series N is color N from the colors table. | String |
Series1Pattern (Series2Pattern, ...) |
bar line mixed |
The fill pattern for the area fill or bar. Must be a number in the fill-patterns table. | Numeric |
Series1PatternColor (Series2PatternColor, ...) |
bar line mixed |
The color used for the fill pattern. Must be a number in the colors table or an SVG color term. Default: black | String |
Be aware that data series are displayed in reverse order, so series 1 is shown in front of series 2.
Data Groups
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Group1Text (Group2Text, ...) Must be in sequence. |
bar line mixed scatter |
The name for the data group. If Group1Text is defined, the X axis will be divided into groups as required, and the X values in Series1Values (etc.) must be integers corresponding to the defined groups. The XMin and XMax parameters will be ignored. | String |
Bar Width & Spacing
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
BarWidth | bar mixed |
The width of bars, in pixels. Only used if groups are not defined. Default: 20 | Numeric +ve only |
BarSpace | bar mixed |
The width of the gap between bars in a group, as a percentage of the bar width. Only used if groups are defined. Default: 0 | Numeric +ve only |
Chart Variations
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Area | line | Any value sets all series lines to be filled with color (or a fill pattern) from the X axis to the line. It also turns off any markers or dash patterns on the lines. | Any |
Stack | bar line |
Only valid if data groups are defined. Any value sets series Y values to be stacked. Be aware that this may require adjustment of the YMax and YMin values for proper display. Negative values are stacked separately from positive values. | Any |
Stack100 | bar line |
Only valid if data groups are defined. Any value sets series Y values to be stacked, then re-scaled to the range 1 to 100. The Y axis will be forced to the range 0 (or -100 if there are any negative values) to 100, the YMin and YMax parameters will be ignored, and YAxisValueSuffix will be set to %. | Any |
HorizontalBarGraph | bar | Any value sets a bar graph to display horizontal bars, with X values on the vertical axis, and Y values on the horizontal axis. Be aware that, if changing a vertical bar graph to a horizontal one, parameters such as ChartHeight, ChartWidth and axis-value rotates may also need to be changed. | Any |
GroupsTopDown | bar | Any value sets a horizontal bar graph with groups on the X axis to show the groups in sequence from the top down instead of the default bottom up. It also changes the series order in each group to top-down. | Any |
In a lineChart, setting Stack or Stack100 will set Area to 'on'.
Parameters - Pie Charts
[edit]For pie charts, the chart size is calculated from the PieRadius.
Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
PieRadius | pie | The radius of the pie chart, in pixels. Default: 200 | Numeric +ve only |
Explode | pie | A numeric value will explode the specified number of pie segments (starting with the first) away from the centre. Any non-numeric value will explode all segments. | Any |
ExplodeRadius | pie | The proportion of the radius that exploded segments are shifted. A percentage of the total pie radius. Default: if Explode is non-numeric, 10, otherwise 20. | Numeric +ve only |
DoughnutHole | pie | A numeric value specifies the size of the hole in the centre of the pie (so producing a doughnut chart), as a percentage of the pie radius. Any non-numeric value will produce the default hole size of 50%. | Any |
SegmentText | pie |
Any combination of the above three values can be in the parameter, eg: SegmentText=text percent will show the segment text followed by the percentage. The font size for segment texts is the same as that for legend texts. |
String |
SegmentTextWidth | pie | Sets the horizontal space allowed for all segment texts, as a percentage of the standard width. The standard width allows for about 10 characters. Set this parameter above 100 if any of the segment texts fail to show completely. Default: 100 | Numeric +ve only |
SegmentTextRadius | pie | The proportion of the radius at which segment texts are placed. A percentage of the pie radius. Default: 105 | Numeric +ve only |
PieStartAngle | pie | Sets the angle at which the first segment starts. Default: 0 The default means the first segment will start at the normal start angle for trigonometric circles, the X-axis (ie: 3 o'clock). | Numeric 0 to 360 only |
PieSweepDir | pie | Sets the direction segments sweep in. Any value other than the default will cause the segments to sweep in a clockwise direction. Default: AntiClockwise | String |
Pie Chart Data
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Series1Values | pie | A string containing text and value pairs, separated by spaces. The texts will be used for legend or segment texts, the values used to calculate the segments of the pie. In pie charts, parameters Series2Values etc. will be ignored. | String |
Pie Segment Appearance
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Segment1Color (Segment2Color, ...) |
pie | The color of the pie segment. Must be a number in the colors table or an SVG color term. The default color for segment N is color N from the colors table. | String |
Segment1Pattern (Segment2Pattern, ...) |
pie | The fill pattern for the pie segment. Must be a number in the fill-patterns table. | Numeric |
Segment1PatternColor (Segment2PatternColor, ...) |
pie | The color used for the fill pattern. Must be a number in the colors table or an SVG color term. Default: black | String |
Parameters - General
[edit]SVG File Descriptive Elements
[edit]These parameters set values that are written to the SVG file, but are not displayed in the image.
Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
FileTitle | All | Adds a <title> element to the SVG file. Default: 'SVG chart' | String |
FileDesc | All | Adds a <description> element to the SVG file. Default: 'SVG chart generated by Charts SVG' | String |
Title and Footnote
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Title | All | Sets the text to show as a title. By default, the title is centered above the chart. | String |
TitleX TitleY |
bar line mixed scatter |
Moves the title from its default position to a specified location within the chart. The X and Y values are as for graph points.
The position specifies the left end and the baseline of the text. If TitleX is not specified, the title will appear in its default position. If TitleX is specified but TitleY is not, TitleY will default to zero. |
Numeric |
TitleX TitleY |
pie | Moves the title from its default position (above the chart) to a specified location within the chart. The X and Y values are percentages of the chart size. (0, 0) = bottom left, (50, 50) = the pie origin.
The position specifies the left end and the baseline of the text. If TitleX is not specified, the title will appear in its default position. If TitleX is specified but TitleY is not, TitleY will default to zero. |
Numeric |
Footnote | All | Sets the text to show as a footnote. By default, the footnote is shown at the bottom right of the image. | String |
FootnoteX FootnoteY |
bar line mixed scatter |
Moves the footnote from its default position to a specified location within the chart. The X and Y values are as for graph points.
The position specifies the left end and the baseline of the text. If FootnoteX is not specified, the title will appear in its default position. If FootnoteX is specified but FootnoteY is not, FootnoteY will default to zero. |
Numeric |
FootnoteX FootnoteY |
pie | Moves the footnote from its default position to a specified location within the chart. The X and Y values are percentages of the chart size. (0, 0) = bottom left, (50, 50) = the pie origin.
The position specifies the left end and the baseline of the text. If FootnoteX is not specified, the title will appear in its default position. If FootnoteX is specified but FootnoteY is not, FootnoteY will default to zero. |
Numeric |
Legend
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Series1Text (Series2Text, ...) Must be in sequence. |
All | Sets the text that appears in the legend as the name for the series. If no Series<N>Text parameters are set, the legend will not be shown. | String |
LegendType | All |
Default: vertical |
String |
LegendTextWidth | All | Sets the horizontal space allowed for all legend texts, as a percentage of the standard width. The standard width allows for about 10 characters. Set this parameter above 100 if any of the legend texts fail to show fully, or if they extend outside the legend box. Default: 100 | Numeric +ve only |
LegendBorder | All | The color for the border surrounding the legend. Must be a number in the colors table or an SVG color term. A value of none will hide the border. Default: black | String |
LegendX LegendY |
All | Moves the legend from its default position (which depends on the LegendType setting) to a specified location within the chart. The X and Y values are as for graph points (or, for a pie chart, as a % of the chart width/height).
The position specifies the top left corner of the legend box. If LegendX is not specified, the legend will appear in its default position. If LegendX is specified but LegendY is not, LegendY will default to zero. |
Numeric |
LegendSVG | All | Completely replaces the legend with the supplied SVG code. | String |
Chart Texts
[edit]Additional texts can be shown on the chart. Appropriately placed, they can be a substitute for a legend.
Parameter | Chart Types | Description | Type & Limits | |
---|---|---|---|---|
ChartText1 (ChartText2, ...) Must be in sequence. |
All | The text to show. | String | |
ChartText1X (ChartText2X, ...) |
bar line mixed scatter |
Sets the X-Axis location for the text. (If groups are defined, each group is 1 unit.) Default: 0 | Numeric +ve only |
Numeric |
ChartText1Y (ChartText2Y, ...) |
bar line mixed scatter |
Sets the Y-Axis location for the text. Default: 0 | Numeric +ve only | |
ChartText1X (ChartText2X, ...) |
pie | Sets the horizontal location for the text. The value is a percentage of the chart size. Default: 0 | Numeric +ve only | |
ChartText1X (ChartText2X, ...) |
pie | Sets the vertical location for the text. The value is a percentage of the chart size. Default: 0 | Numeric +ve only |
Numeric |
In all cases the position specifies the baseline and left end of the text.
General Image
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
ImagePadding | All | Sets the number of pixels in the padding. Replaces the default padding within the image border, on all 4 sides. | Numeric |
ImagePaddingTop ImagePaddingBottom ImagePaddingLeft ImagePaddingRight |
All | Sets the number of pixels in the padding. Sets the padding amount on one side of the image only. Default: ImagePadding (if set), otherwise 6 pixels. | Numeric |
ImageBorder | All | The color of a border at the outside edges of the image. Must be a number in the colors table or an SVG color term. The border (if shown) is always 1px in width. | String |
ImageBackgroundColor | All | The color for the background of the whole image. Must be a number in the colors table or an SVG color term. Default: white | String |
ImageBackgroundSVG | All | Allows insertion of user-defined SVG code into the file. The specified elements are shown in front of the image background color, but behind all other elements in the image. | String |
ImageForegroundSVG | All | Allows insertion of user-defined SVG code into the file. The specified elements are shown in front of all other elements in the image. | String |
General Chart
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
GrayScale GreyScale |
All | Any value changes the color table to one of grey-scale equivalents of the colors (see colors table). It also changes the default pattern color to white.
Note that any colors defined with a number from the colors table will be shown as the grey-scale equivalent, but colors defined with an SVG color term will be shown with the color as defined. |
Any |
LineWidth | All | Changes the default width for all line components of the chart. A percentage of the default line width. Default: 100 | Numeric +ve only |
GraphLineWidth | line mixed scatter |
Changes the default width for all series lines on the chart. A percentage of the default line width. Default: 100 | Numeric +ve only |
ChartBackgroundColor | All | The background color for the chart area. Must be a number in the colors table or an SVG color term. | String |
Font Sizes
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
FontSize | All | Changes the font size for all text elements in the image. A percentage of the default size for each element. Default: 100 | Numeric +ve only |
LabelsFontSize | All | Changes the font size for data labels in the image. A percentage of the default size. Default: 100 | Numeric +ve only |
TitleFontSize FootnoteFontSize LegendFontSize ChartTextFontSize |
All | Changes the font size for the particular text element. A percentage of the default size for each element. Default: 100 | Numeric +ve only |
XAxisTitleFontSize XAxisValuesFontSize YAxisTitleFontSize YAxisValuesFontSize YAxis2TitleFontSize YAxis2ValuesFontSize |
bar line mixed scatter |
Changes the font size for the particular text element(s). A percentage of the default size for each element. Default: 100 | Numeric +ve only |
Bar, Pie-segment and Area Borders
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
BorderColor | bar line mixed pie |
The color of the border around each bar, pie segment or line area. Must be a number in the colors table or an SVG color term. No border is shown unless BorderColor is specified. | String |
BorderWidth | bar line mixed pie |
The width of the border line (if shown) around bars, pie segments and line areas. A percentage of the standard line width. Default: 100 | Numeric +ve only |
Original Data
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
IncludeOriginalData | All |
Default: auto |
String |
Debug
[edit]Parameter | Chart Types | Description | Type & Limits |
---|---|---|---|
Debug | All | A value of parms will show the values of all parameters used in the module. This includes the user-supplied parameters that are recognised, and the default values for all other parameters that have them. It does not include parameters that are not recognised, including because they are mis-spelled or mis-capitalised. | String |
rem | All | An existing parameter in a call to this module can be left in the call text but ignored by the module by making it a 'rem' parameter. Eg: a call with HorizontalBarGraph=yes can have the parameter ignored by changing it to rem=HorizontalBarGraph=yes. Switching between using and ignoring the parameter is then just a matter of value of removing or adding 'rem=' before the rest of the parameter text. | String |
Values Tables
[edit]Colors
[edit]These are the defult colors for data series and pie segments. To set any color, use either a number from this table or an SVG color term.
Number | Color | GreyScale equivalent | Number | Color | GreyScale equivalent | Number | Color | GreyScale equivalent | ||
---|---|---|---|---|---|---|---|---|---|---|
1 | 11 | 21 | ||||||||
2 | 12 | 22 | ||||||||
3 | 13 | 23 | ||||||||
4 | 14 | 24 | ||||||||
5 | 15 | 25 | ||||||||
6 | 16 | 26 | ||||||||
7 | 17 | 27 | ||||||||
8 | 18 | 28 | ||||||||
9 | 19 | 29 | ||||||||
10 | 20 | 30 |
Markers
[edit]To set a marker for series <N>, set the Series<N>Marker parameter to one of these numbers.
Dash Patterns
[edit]To set a dash pattern for series <N>, set the Series<N>Dash parameter to one of these numbers, or to an SVG dash pattern term.
Fill Patterns
[edit]To set a fill pattern for series <N>, set the Series<N>Pattern parameter to one of these numbers.
Notes
[edit]SVG Color Terms
[edit]Colors in SVG files can be specified in a number of ways:
- as a 3- or 6-digit hexadecimal number, prefixed with'#', eg:
#f08
, or#ff0088
(which are equivalent). - in rgb functional notation, eg: rgb(255, 0, 128) or rgb(100%, 0, 50%) (which are equivalent).
- as keywords, eg: blue, red. SVG recognises 147 color keywords, which are listed in the SVG specification.
In all cases an SVG color term should be entered in a parameter without surrounding quotes, eg: Series1Color=rgb(100%, 0%, 50%)
or Series1Color=blue
.
SVG Dash Pattern Terms
[edit]Dash patterns in SVG are specified by a series of space- or comma-separated numbers, which specify the lengths of the drawn or empty sections of the pattern. Eg: '8,2' specifies a dash of length 8 units, followed by a space of 2 units.
Dash pattern terms should be entered in a parameter without surrounding quotes, eg: Series1Dash=8,2
.
Spaces in Parameters
[edit]Some parameters (especially Series1Values, ...) contain multiple values, separated by spaces. The Series<N>Values parameters are written into the SVG code as comments (subject to the IncludeOriginalData parameter). Eg:
{{#invoke:Charts SVG|lineChart|Series1Values= 10 90 20 75 35 24 70 62 }}
Groups of spaces and line-breaks are regarded as a single space when separating values.
User-supplied SVG code
[edit]There a number of parameters where the text of the parameter is written directly to the SVG output, with the expectation it is valid SVG code. If it is not, this will only be known when the SVG file is viewed as an SVG image. This may produce an error, or all or part of the SVG code may be ignored.
The parameters handled this way are:
- ImageBackgroundSVG
- ImageForegroundSVG
- LegendSVG
- all Color and Dash parameters that are not numeric values
Known Rendering Issues
[edit]Some SVG elements will not be rendered properly, or at all, when the SVG image is shown on a Wikipedia page. In part this is because the displayed image is in fact a PNG (bitmap) image - the SVG code is not sent to the user's browser.
- Texts in the SVG may contain web addresses, but be aware the displayed PNG will not contain text that can be either clicked or copied-and-pasted. Actual links will need to be elsewhere.
- Texts in the SVG should not contain wiki-markup, as it will not be interpreted as such when the SVG image is rendered on a Wikipedia page.
- It appears the renderer simply ignores SVG xlink: references. Parameters where SVG code is supplied (eg: ImageForegroundSVG) should avoid them.
Examples
[edit]Consistent with the various chart types, these examples use almost identical data.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For more complex examples, see User:Innesw/sandbox/further examples.
Development
[edit]If you have questions or suggestions, share them at Module talk:Charts SVG.
Code
-- Module Charts SVG
local Args, Parms = {}, {}
local SeriesData, OriginalData = {}, {}
local SType, YAxis2, Labels, Color, LineShow, LineWidth, LineDash, Marker, MarkerFill, MarkerSize, FillPattern, FillPatternColor = {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
local SeriesText, GroupText, ChartText = {}, {}, {}
local SeriesCount, BarSeriesCount, SeriesMaxLen, GroupsCount, ChartTextCount = 0, 0, 0, 0, 0
local AutoDataPointsLimit, DataPointsCount = 100, 0
local DoPie, DoHorizontal, DoGroupsTopDown, DoArea, DoStack, DoStack100, DoYAxis2, DoChartAdjust = false, false, false, false, false, false, false, false
local Msgs = {}
local FontSiz, Siz, Pos, Mult, Adjusts = {}, {}, {}, {}, {}
local ImageWidth, ImageHeight
local GroupWidth, UnitWidth, BarWidth, BarSpace
local BaseUnit, BaseFontSize, BaseLineWidth = 3, 3, 1
local r, tr, t = {}
-- To translate parameters, translate the values in the KeyWords table. Do not translate the keys.
local KeyWords = {
barChart = 'barChart',
lineChart = 'lineChart',
scatterChart = 'scatterChart',
mixedChart = 'mixedChart',
pieChart = 'pieChart',
FileTitle = 'FileTitle',
FileDesc = 'FileDesc',
ImagePadding = 'ImagePadding',
ImagePaddingTop = 'ImagePaddingTop',
ImagePaddingBottom = 'ImagePaddingBottom',
ImagePaddingLeft = 'ImagePaddingLeft',
ImagePaddingRight = 'ImagePaddingRight',
ImageBackgroundColor = 'ImageBackgroundColor',
ImageBackgroundSVG = 'ImageBackgroundSVG',
ImageBorder = 'ImageBorder',
ImageForegroundSVG = 'ImageForegroundSVG',
Title = 'Title',
TitleX = 'TitleX',
TitleY = 'TitleY',
Footnote = 'Footnote',
FootnoteX = 'FootnoteX',
FootnoteY = 'FootnoteY',
Area = 'Area',
Stack = 'Stack',
Stack100 = 'Stack100',
HorizontalBarGraph = 'HorizontalBarGraph',
GroupsTopDown = 'GroupsTopDown',
ChartWidth = 'ChartWidth',
ChartHeight = 'ChartHeight',
ChartAdjust = 'ChartAdjust',
GreyScale = 'GreyScale',
GrayScale = 'GrayScale',
LineWidth = 'LineWidth',
ChartBackgroundColor = 'ChartBackgroundColor',
XMin = 'XMin',
XMax = 'XMax',
XAxisTitle = 'XAxisTitle',
XAxisValueStep = 'XAxisValueStep',
XAxisValueMultiplier = 'XAxisValueMultiplier',
XAxisValueRound = 'XAxisValueRound',
XAxisValueAbsolute = 'XAxisValueAbsolute',
XAxisValuePrefix = 'XAxisValuePrefix',
XAxisValueSuffix = 'XAxisValueSuffix',
XAxisValueFormat = 'XAxisValueFormat',
XAxisValueRotate = 'XAxisValueRotate',
XAxisValueSpace = 'XAxisValueSpace',
XAxisMark2Step = 'XAxisMark2Step',
XAxisArrows = 'XAxisArrows',
YMin = 'YMin',
YMax = 'YMax',
YAxisTitle = 'YAxisTitle',
YAxisValueStep = 'YAxisValueStep',
YAxisValueMultiplier = 'YAxisValueMultiplier',
YAxisValueRound = 'YAxisValueRound',
YAxisValueAbsolute = 'YAxisValueAbsolute',
YAxisValuePrefix = 'YAxisValuePrefix',
YAxisValueSuffix = 'YAxisValueSuffix',
YAxisValueFormat = 'YAxisValueFormat',
YAxisValueRotate = 'YAxisValueRotate',
YAxisValueSpace = 'YAxisValueSpace',
YAxisMark2Step = 'YAxisMark2Step',
YAxisColor = 'YAxisColor',
YAxisArrows = 'YAxisArrows',
Y2Min = 'Y2Min',
Y2Max = 'Y2Max',
YAxis2Title = 'YAxis2Title',
YAxis2ValueStep = 'YAxis2ValueStep',
YAxis2ValueMultiplier = 'YAxis2ValueMultiplier',
YAxis2ValueRound = 'YAxis2ValueRound',
YAxis2ValueAbsolute = 'YAxis2ValueAbsolute',
YAxis2ValuePrefix = 'YAxis2ValuePrefix',
YAxis2ValueSuffix = 'YAxis2ValueSuffix',
YAxis2ValueFormat = 'YAxis2ValueFormat',
YAxis2ValueRotate = 'YAxis2ValueRotate',
YAxis2ValueSpace = 'YAxis2ValueSpace',
YAxis2Mark2Step = 'YAxis2Mark2Step',
YAxis2Color = 'YAxis2Color',
XGrid = 'XGrid',
YGrid = 'YGrid',
LegendType = 'LegendType',
LegendX = 'LegendX',
LegendY = 'LegendY',
LegendTextWidth = 'LegendTextWidth',
LegendBorder = 'LegendBorder',
LegendSVG = 'LegendSVG',
FontSize = 'FontSize',
LabelsFontSize = 'LabelsFontSize',
TitleFontSize = 'TitleFontSize',
FootnoteFontSize = 'FootnoteFontSize',
LegendFontSize = 'LegendFontSize',
XAxisTitleFontSize = 'XAxisTitleFontSize',
YAxisTitleFontSize = 'YAxisTitleFontSize',
YAxis2TitleFontSize = 'YAxis2TitleFontSize',
XAxisValuesFontSize = 'XAxisValuesFontSize',
YAxisValuesFontSize = 'YAxisValuesFontSize',
YAxis2ValuesFontSize = 'YAxis2ValuesFontSize',
ChartTextFontSize = 'ChartTextFontSize',
GraphLineWidth = 'GraphLineWidth',
BarWidth = 'BarWidth',
BarSpace = 'BarSpace',
PieRadius = 'PieRadius',
Explode = 'Explode',
ExplodeRadius = 'ExplodeRadius',
DoughnutHole = 'DoughnutHole',
PieStartAngle = 'PieStartAngle',
PieSweepDir = 'PieSweepDir',
SegmentText = 'SegmentText',
SegmentTextWidth = 'SegmentTextWidth',
SegmentTextRadius = 'SegmentTextRadius',
BorderColor = 'BorderColor',
BorderWidth = 'BorderWidth',
Series = 'Series',
Segment = 'Segment',
Type = 'Type',
Values = 'Values',
YAxis2 = 'YAxis2',
Labels = 'Labels',
Color = 'Color',
Line = 'Line',
Width = 'Width',
Dash = 'Dash',
Marker = 'Marker',
MarkerSize = 'MarkerSize',
MarkerFill = 'MarkerFill',
Pattern = 'Pattern',
PatternColor = 'PatternColor',
Text = 'Text',
Group = 'Group',
ChartText = 'ChartText',
IncludeOriginalData = 'IncludeOriginalData',
Debug = 'Debug',
-- these are values that some parameters recognise, and can be translated
none = 'none',
-- series types
bar = 'bar',
line = 'line',
-- legend types
vertical = 'vertical',
horizontal = 'horizontal',
-- pie segment texts
text = 'text',
value = 'value',
percent = 'percent',
-- original data
auto = 'auto',
-- debug
parms = 'parms',
--
yes = 'yes',
no = 'no',
-- rem parameter is accepted but ignored, allows user to rem out a parameter
rem = 'rem',
-- these are special values that make the code simpler if they exist in the KeyWords table,
-- but they *must not* be translated
X = 'X',
Y = 'Y',
}
-- To translate messages, translate the values in the MessageTexts table. Do not translate the keys.
local MessageTexts = {
MsgHeading = "===== Charts SVG - Messages =====",
ParmsNotFound = "Unknown Parameters:",
NotNumeric = "%s: value \"%s\" is not numeric.",
BelowChartMinSize = "%s: value \"%d\" is below the minimum chart size of %d.",
NotInGroup = "Series%dValues: pair %d: X value \"%s\" is not for a defined group.",
NotInMarkers = "Series%dMarker: value \"%s\" is not in the markers table.",
NotInDashPatterns = "Series%dDash: value \"%s\" is not in the dash patterns table.",
NotInFillPatterns = "Series%dPattern: value \"%s\" is not in the fill patterns table.",
NoYAxis2 = "Series%dYAxis2 is set, but no 2nd Y axis is shown - which may be because Stack or Stack100 are set.",
CopyText = "Select and copy the following text. Paste it into a plain text file. The text file should have an svg extension, for example ''mychart.svg''.",
}
local DefColor = {
'rgb(0, 0, 255)', -- 1 = blue
'rgb(0, 192, 0)', -- 2 = mid green
'rgb(255, 255, 0)', -- 3 = yellow
'rgb(255, 0, 0)', -- 4 = red
'rgb(192, 192, 0)', -- 5 = light olive
'rgb(255, 0, 255)', -- 6 = magenta
'rgb(0, 255, 0)', -- 7 = lime
'rgb(128, 128, 128)', -- 8 = grey
'rgb(0, 255, 255)', -- 9 = cyan
'rgb(255, 165, 0)', -- 10 = orange
'rgb(0, 0, 192)', -- 11 = light navy
'rgb(128, 255, 128)', -- 12 = light green
'rgb(255, 255, 128)', -- 13 = sand yellow
'rgb(192, 0, 0)', -- 14 = red brown
'rgb(240, 240, 240)', -- 15 = pale grey
'rgb(255, 128, 255)', -- 16 = light magenta
'rgb(192, 255, 192)', -- 17 = pale green
'rgb(192, 0, 192)', -- 18 = dark mauve
'rgb(192, 192, 255)', -- 19 = blue-grey
'rgb(0, 192, 192)', -- 20 = dark cyan
'rgb(192, 192, 192)', -- 21 = light grey
'rgb(128, 128, 255)', -- 22 = dark blue-grey
'rgb(0, 128, 0)', -- 23 = green
'rgb(255, 255, 192)', -- 24 = light sand
'rgb(255, 192, 192)', -- 25 = pale red
'rgb(128, 128, 0)', -- 26 = olive
'rgb(255, 192, 255)', -- 27 = pale magenta
'rgb(255, 96, 0)', -- 28 = dark orange
'rgb(192, 255, 255)', -- 29 = light cyan
'rgb(255, 128, 128)', -- 30 = light red
}
-- grayscale equivalents of the above colours
local GrayColor = {
'rgb(18, 18, 18)', -- 1 = blue-g
'rgb(137, 137, 137)', -- 2 = midgreen-g
'rgb(237, 237, 237)', -- 3 = yellow-g
'rgb(54, 54, 54)', -- 4 = red-g
'rgb(178, 178, 178)', -- 5 = lightolive-g
'rgb(73, 73, 73)', -- 6 = magenta-g
'rgb(182, 182 ,182)', -- 7 = lime-g
'rgb(128, 128, 128)', -- 8 = grey-g
'rgb(201, 201 ,201)', -- 9 = cyan-g
'rgb(172, 172 172)', -- 10 = orange-g
'rgb(14, 14 ,14)', -- 11 = lightnavy-g
'rgb(219, 219, 219)', -- 12 = lightgreen-g
'rgb(246, 246, 246)', -- 13 = sandyellow-g
'rgb(41, 41, 41)', -- 14 = redbrown-g
'rgb(240, 240, 240)', -- 15 = palegrey-g
'rgb(164, 164, 164)', -- 16 = lightmagenta-g
'rgb(237, 237, 237)', -- 17 = palegreen-g
'rgb(55, 55, 55)', -- 18 = darkmauve-g
'rgb(197, 197, 197)', -- 19 = bluegrey-g
'rgb(151, 151, 151)', -- 20 = darkcyan-g
'rgb(192, 192, 192)', -- 21 = lightgrey-g
'rgb(137, 137, 137)', -- 22 = darkbluegrey-g
'rgb(92, 92, 92)', -- 23 = green-g
'rgb(250, 250, 250)', -- 24 = lightsand-g
'rgb(205, 205, 205)', -- 25 = palered-g
'rgb(119, 119, 119)', -- 26 = olive-g
'rgb(210, 210, 210)', -- 27 = palemagenta-g
'rgb(123, 123, 123)', -- 28 = darkorange-g
'rgb(242, 242, 242)', -- 29 = lightcyan-g
'rgb(155, 155, 155)' -- 30 = lightred-g
}
local DashPattern = {
"8,8", -- 1 = dashed, long
"4,4", -- 2 = dashed, short
"8,2", -- 3 = broken, long
"4,2", -- 4 = broken, short
"2,2", -- 5 = dots
"6,2,2,2", -- 6 = dash-dot
"6,2,2,2,2,2", -- 7 = dash-dot-dot
"6,2,2,2,2,2,2,2" -- 8 = dash-dot-dot-dot
}
--[[
The differences between the defaults for the 4 'graph' chart methods are:
barChart lineChart scatterChart mixedChart
series type bar* line* line* must be specified
line visibility -* yes none yes
marker -* nil series no. nil
group allowed allowed -* allowed
area -* nil -* -*
stack nil nil -* -*
stack100 nil nil -* -*
(* = cannot be changed by parameters)
]]
function barChart(frame)
Args = frame.args
getAllParms()
DoStack = (Parms["Stack"] ~= nil)
DoStack100 = (Parms["Stack100"] ~= nil)
DoYAxis2 = (Parms["Y2Max"] ~= nil)
DoChartAdjust = (Parms["ChartAdjust"] ~= nil)
DoHorizontal = (Parms["HorizontalBarGraph"] ~= nil)
DoGroupsTopDown = (Parms["HorizontalBarGraph"] ~= nil) and (Parms["GroupsTopDown"] ~= nil)
-- fill internal tables
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
SType[i] = KeyWords["bar"]
end
copyTable(SeriesData, OriginalData) -- preserve original data
BarSeriesCount = SeriesCount
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)
build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil)
build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if DoStack or DoStack100 then
calcStack()
DoYAxis2 = false
end
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
stylesAreas()
defsAreas()
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function lineChart(frame)
Args = frame.args
getAllParms()
DoArea = (Parms["Area"] ~= nil)
DoStack = (Parms["Stack"] ~= nil)
DoStack100 = (Parms["Stack100"] ~= nil)
DoYAxis2 = (Parms["Y2Max"] ~= nil)
DoChartAdjust = (Parms["ChartAdjust"] ~= nil)
-- fill internal tables
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
SType[i] = KeyWords["line"]
end
copyTable(SeriesData, OriginalData) -- preserve original data
BarSeriesCount = 0
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)
build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes'
build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)
build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)
build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil
build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)
build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)
build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil)
build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if DoStack or DoStack100 then
calcStack()
DoArea = true
DoYAxis2 = false
end
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
if DoArea then
stylesAreas()
defsAreas()
else
stylesLines()
defsMarkers()
end
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function scatterChart(frame)
Args = frame.args
getAllParms()
DoYAxis2 = (Parms["Y2Max"] ~= nil)
DoChartAdjust = (Parms["ChartAdjust"] ~= nil)
-- fill internal tables
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
SType[i] = KeyWords["line"]
end
copyTable(SeriesData, OriginalData) -- preserve original data
BarSeriesCount = 0
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, DefColor, nil, nil)
build("Series", "Line", SeriesCount, LineShow, KeyWords["none"], nil, nil) -- the default line visibility is 'none'
build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)
build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)
build("Series", "Marker", SeriesCount, Marker, nil, true, nil) -- the default marker type is the series number
build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)
build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
stylesLines()
defsMarkers()
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function mixedChart(frame)
Args = frame.args
getAllParms()
DoYAxis2 = (Parms["Y2Max"] ~= nil)
DoChartAdjust = (Parms["ChartAdjust"] ~= nil)
-- fill internal tables
build("Series", "Type", SeriesCount, SType, "line", nil, nil)
for i = 1, SeriesCount do
if SType[i] == KeyWords["bar"] then
BarSeriesCount = BarSeriesCount + 1
end
end
for i = 1, SeriesCount do
SeriesData[i] = {}
transfer(Parms["Series" .. i .. "Values"], SeriesData[i], 2)
DataPointsCount = DataPointsCount + #SeriesData[i]
end
copyTable(SeriesData, OriginalData) -- preserve original data
build("Series", "YAxis2", SeriesCount, YAxis2, nil, nil, nil)
build("Series", "Labels", SeriesCount, Labels, nil, nil, nil)
build("Series", "Color", SeriesCount, Color, nil, nil, DefColor)
build("Series", "Line", SeriesCount, LineShow, "yes", nil, nil) -- the default line visibility is 'yes'
build("Series", "Width", SeriesCount, LineWidth, Parms["GraphLineWidth"], nil, nil)
build("Series", "Dash", SeriesCount, LineDash, KeyWords["none"], nil, nil)
build("Series", "Marker", SeriesCount, Marker, nil, nil, nil) -- the default marker type is nil
build("Series", "MarkerSize", SeriesCount, MarkerSize, 100, nil, nil)
build("Series", "MarkerFill", SeriesCount, MarkerFill, nil, nil, Color)
build("Series", "Pattern", SeriesCount, FillPattern, KeyWords["none"], nil, nil)
build("Series", "PatternColor", SeriesCount, FillPatternColor, nil, nil, nil)
build("Series", "Text", SeriesCount, SeriesText, nil, nil, nil)
build("Group", "Text", GroupsCount, GroupText, nil, "Group ", nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsAxes()
commonTop()
codeAxisGrids()
stylesAreas()
defsAreas()
stylesLines()
defsMarkers()
elementsGraphs()
codeAxes()
commonBottom()
return table.concat(r, "\n")
end
function pieChart(frame)
Args = frame.args
getAllParms()
GroupsCount = 0 -- ensures any GroupNText parameters are ignored
-- fill internal tables
-- only transfer series 1
if SeriesCount > 0 then
transfer(Parms["Series" .. 1 .. "Values"], SeriesData, 2)
DataPointsCount = DataPointsCount + #SeriesData
end
copyTable(SeriesData, OriginalData) -- preserve original data
-- get segment parms for each element in the series
-- we do this here because transfer() (above) is where SeriesMaxLen is calculated
for i = 1, SeriesMaxLen do
Parms["Segment" .. i .. "Color"] = checkColor(getParm("Segment", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))
Parms["Segment" .. i .. "Pattern"] = getParm("Segment", i, "Pattern", "n")
if Parms["Segment" .. i .. "Pattern"] ~= nil then
Parms["Segment" .. i .. "PatternColor"] = checkColor(getParm("Segment", i, "PatternColor", "s", iif(Parms["GrayScale"] ~= nil, "white", "black")))
end
end
build("Segment", "Color", SeriesMaxLen, Color, nil, nil, iif(Parms["GrayScale"] ~= nil, GrayColor, DefColor))
build("Segment", "Pattern", SeriesMaxLen, FillPattern, KeyWords["none"], nil, nil)
build("Segment", "PatternColor", SeriesMaxLen, FillPatternColor, nil, nil, nil)
build("ChartText", "", ChartTextCount, ChartText, nil, nil, nil)
-- transfer the first value in each SeriesData pair to table SeriesText, for the legend
for k, v in ipairs(SeriesData) do
SeriesText[k] = v[1]
end
SeriesCount = SeriesMaxLen
DoPie = true
if not checkParms() then
-- messages, instead of SVG output
return table.concat(r, "\n")
end
if not outputDebugInfo() then
-- stop after debug output
return table.concat(r, "\n")
end
-- build the SVG
prelimsCommon()
prelimsPie()
commonTop()
stylesAreas()
defsAreas()
elementsPie()
commonBottom()
return table.concat(r, "\n")
end
function getAllParms()
-- get values for all parameters
-- Note that not all parameters have a default value, ie: it is valid for some parameters to be nil. These are generally switches for some behaviour.
-- SVG file metadata
Parms["FileTitle"] = getParm("FileTitle", nil, nil, "s", "SVG Chart")
Parms["FileDesc"] = getParm("FileDesc", nil, nil, "s", "SVG chart generated by Charts SVG")
-- general image
Parms["ImagePadding"] = getParm("ImagePadding", nil, nil, "n") -- switch for replacing default value with user setting for the size of the padding for all 4 spaces
Parms["ImagePaddingTop"] = getParm("ImagePaddingTop", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the top padding
Parms["ImagePaddingBottom"] = getParm("ImagePaddingBottom", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the bottom padding
Parms["ImagePaddingLeft"] = getParm("ImagePaddingLeft", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the left padding
Parms["ImagePaddingRight"] = getParm("ImagePaddingRight", nil, nil, "n", Parms["ImagePadding"]) -- switch for replacing default value with user setting for the size of the right padding
Parms["ImageBorder"] = checkColor(getParm("ImageBorder", nil, nil, "s", KeyWords["none"]))
Parms["ImageBackgroundColor"] = checkColor(getParm("ImageBackgroundColor", nil, nil, "s", "white"))
Parms["ImageBackgroundSVG"] = getParm("ImageBackgroundSVG", nil, nil, "s") -- switch for background SVG for the image
Parms["ImageForegroundSVG"] = getParm("ImageForegroundSVG", nil, nil, "s") -- switch for foreground SVG for the image
-- display title
Parms["Title"] = getParm("Title", nil, nil, "s") -- switch to show title text
Parms["TitleX"] = getParm("TitleX", nil, nil, "n") -- switch to move title from default position
Parms["TitleY"] = getParm("TitleY", nil, nil, "n", 0)
-- footnote
Parms["Footnote"] = getParm("Footnote", nil, nil, "s") -- switch for footnote text
Parms["FootnoteX"] = getParm("FootnoteX", nil, nil, "n") -- switch to move footnote from default position
Parms["FootnoteY"] = getParm("FootnoteY", nil, nil, "n", 0)
-- general chart
Parms["Area"] = getParm("Area", nil, nil, "s") -- switch for area graphs
Parms["Stack"] = getParm("Stack", nil, nil, "s") -- switch for stacked graphs
Parms["Stack100"] = getParm("Stack100", nil, nil, "s") -- switch for stacked-to-100% graphs
Parms["HorizontalBarGraph"] = getParm("HorizontalBarGraph", nil, nil, "s") -- switch for horizontal bar graphs
Parms["GroupsTopDown"] = getParm("GroupsTopDown", nil, nil, "s") -- switch for showing groups (and series) in top-down order on horizontal bar graphs
Parms["ChartWidth"] = getParm("ChartWidth", nil, nil, "n", 500)
Parms["ChartHeight"] = getParm("ChartHeight", nil, nil, "n", 350)
if Parms["ChartWidth"] < 200 then
table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartWidth", Parms["ChartWidth"], 200))
end
if Parms["ChartHeight"] < 200 then
table.insert(Msgs, string.format(MessageTexts.BelowChartMinSize, "ChartHeight", Parms["ChartHeight"], 200))
end
Parms["ChartAdjust"] = getParm("ChartAdjust", nil, nil, "s") -- switch for automatic chart size adjustment
Parms["GreyScale"] = getParm("GreyScale", nil, nil, "s")
Parms["GrayScale"] = getParm("GrayScale", nil, nil, "s", Parms["GreyScale"]) -- switch for grayscale instead of colours
Parms["LineWidth"] = getParm("LineWidth", nil, nil, "n", 100)
Parms["ChartBackgroundColor"] = checkColor(getParm("ChartBackgroundColor", nil, nil, "s")) -- switch for a background color for the chart
-- XAxis
Parms["XMin"] = getParm("XMin", nil, nil, "n", 0)
Parms["XMax"] = getParm("XMax", nil, nil, "n", 100)
Parms["XAxisTitle"] = getParm("XAxisTitle", nil, nil, "s") -- switch to show X axis title
Parms["XAxisValueStep"] = getParm("XAxisValueStep", nil, nil, "n", 10)
Parms["XAxisValueMultiplier"] = getParm("XAxisValueMultiplier", nil, nil, "n", 1)
Parms["XAxisValueRound"] = getParm("XAxisValueRound", nil, nil, "n", decPlaces(Parms["XAxisValueStep"]))
Parms["XAxisValueAbsolute"] = getParm("XAxisValueAbsolute", nil, nil, "s") -- switch for absolute values display
Parms["XAxisValuePrefix"] = getParm("XAxisValuePrefix", nil, nil, "s", "", "_", " ")
Parms["XAxisValueSuffix"] = getParm("XAxisValueSuffix", nil, nil, "s", "", "_", " ")
Parms["XAxisValueFormat"] = getParm("XAxisValueFormat", nil, nil, "s") -- switch to force values formatting on or off
Parms["XAxisValueRotate"] = getParm("XAxisValueRotate", nil, nil, "n") -- switch to rotate x-axis values
Parms["XAxisValueSpace"] = getParm("XAxisValueSpace", nil, nil, "n") -- switch to set x-axis values space
Parms["XAxisMark2Step"] = getParm("XAxisMark2Step", nil, nil, "n") -- switch for showing x-axis secondary marks
Parms["XAxisArrows"] = getParm("XAxisArrows", nil, nil, "s") -- switch for showing x-axis arrows
-- YAxis
if Parms["Stack100"] ~= nil then
Parms["YMin"] = 0
Parms["YMax"] = 100
else
Parms["YMin"] = getParm("YMin", nil, nil, "n", 0)
Parms["YMax"] = getParm("YMax", nil, nil, "n", 100)
end
Parms["YAxisTitle"] = getParm("YAxisTitle", nil, nil, "s") -- switch to show Y axis title
Parms["YAxisValueStep"] = getParm("YAxisValueStep", nil, nil, "n", 10)
Parms["YAxisValueMultiplier"] = getParm("YAxisValueMultiplier", nil, nil, "n", 1)
Parms["YAxisValueRound"] = getParm("YAxisValueRound", nil, nil, "n", decPlaces(Parms["YAxisValueStep"]))
Parms["YAxisValueAbsolute"] = getParm("YAxisValueAbsolute", nil, nil, "s") -- switch for absolute values display
Parms["YAxisValuePrefix"] = getParm("YAxisValuePrefix", nil, nil, "s", "", "_", " ")
if Parms["Stack100"] ~= nil then
Parms["YAxisValueSuffix"] = getParm("YAxisValueSuffix", nil, nil, "s", "%", "_", " ")
else
Parms["YAxisValueSuffix"] = getParm("YAxisValueSuffix", nil, nil, "s", "", "_", " ")
end
Parms["YAxisValueFormat"] = getParm("YAxisValueFormat", nil, nil, "s") -- switch to force values formatting on or off
Parms["YAxisValueRotate"] = getParm("YAxisValueRotate", nil, nil, "n") -- switch to rotate y-axis values
Parms["YAxisValueSpace"] = getParm("YAxisValueSpace", nil, nil, "n") -- switch to set y-axis values space
Parms["YAxisMark2Step"] = getParm("YAxisMark2Step", nil, nil, "n") -- switch for showing y-axis secondary marks
Parms["YAxisColor"] = checkColor(getParm("YAxisColor", nil, nil, "s", "black"))
Parms["YAxisArrows"] = getParm("YAxisArrows", nil, nil, "s") -- switch for showing y-axis arrows
-- YAxis2
Parms["YAxis2Title"] = getParm("YAxis2Title", nil, nil, "s") -- switch to show y-axis-2 title
Parms["Y2Min"] = getParm("Y2Min", nil, nil, "n", 0)
Parms["Y2Max"] = getParm("Y2Max", nil, nil, "n") -- switch to show y-axis-2
Parms["YAxis2ValueStep"] = getParm("YAxis2ValueStep", nil, nil, "n", 10)
Parms["YAxis2ValueMultiplier"] = getParm("YAxis2ValueMultiplier", nil, nil, "n", 1)
Parms["YAxis2ValueRound"] = getParm("YAxis2ValueRound", nil, nil, "n", decPlaces(Parms["YAxis2ValueStep"]))
Parms["YAxis2ValueAbsolute"] = getParm("YAxis2ValueAbsolute", nil, nil, "s") -- switch for absolute values display
Parms["YAxis2ValuePrefix"] = getParm("YAxis2ValuePrefix", nil, nil, "s", "", "_", " ")
Parms["YAxis2ValueSuffix"] = getParm("YAxis2ValueSuffix", nil, nil, "s", "", "_", " ")
Parms["YAxis2ValueFormat"] = getParm("YAxis2ValueFormat", nil, nil, "s") -- switch to force values formatting on or off
Parms["YAxis2ValueRotate"] = getParm("YAxis2ValueRotate", nil, nil, "n") -- switch to rotate y-axis-2 values
Parms["YAxis2ValueSpace"] = getParm("YAxis2ValueSpace", nil, nil, "n") -- switch to set y-axis-2 values space
Parms["YAxis2Mark2Step"] = getParm("YAxis2Mark2Step", nil, nil, "n") -- switch for showing y-axis-2 secondary marks
Parms["YAxis2Color"] = checkColor(getParm("YAxis2Color", nil, nil, "s", "black"))
-- grid lines
Parms["XGrid"] = getParm("XGrid", nil, nil, "s", Parms["XAxisValueStep"])
if Parms["XGrid"] ~= KeyWords["none"] then
Parms["XGrid"] = tonumber(Parms["XGrid"])
end
Parms["YGrid"] = getParm("YGrid", nil, nil, "s", Parms["YAxisValueStep"])
if Parms["YGrid"] ~= KeyWords["none"] then
Parms["YGrid"] = tonumber(Parms["YGrid"])
end
-- legend
Parms["LegendType"] = getParm("LegendType", nil, nil, "s", KeyWords["vertical"])
i = 1
Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N
while Parms["Series" .. i .. "Text"] ~= nil do
i = i + 1
Parms["Series" .. i .. "Text"] = getParm("Series", i, "Text", "s") -- switch for showing a legend for series N
end
Parms["LegendX"] = getParm("LegendX", nil, nil, "n") -- switch for moving the legend from the default position
Parms["LegendY"] = getParm("LegendY", nil, nil, "n", 0)
Parms["LegendTextWidth"] = getParm("LegendTextWidth", nil, nil, "n", 100)
Parms["LegendBorder"] = checkColor(getParm("LegendBorder", nil, nil, "s", "black"))
Parms["LegendSVG"] = getParm("LegendSVG", nil, nil, "s") -- switch for replacing all legend code
-- font sizes
Parms["FontSize"] = getParm("FontSize", nil, nil, "n", 100)
Parms["TitleFontSize"] = getParm("TitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["FootnoteFontSize"] = getParm("FootnoteFontSize", nil, nil, "n", Parms["FontSize"])
Parms["LegendFontSize"] = getParm("LegendFontSize", nil, nil, "n", Parms["FontSize"])
Parms["XAxisTitleFontSize"] = getParm("XAxisTitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxisTitleFontSize"] = getParm("YAxisTitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxis2TitleFontSize"] = getParm("YAxis2TitleFontSize", nil, nil, "n", Parms["FontSize"])
Parms["XAxisValuesFontSize"] = getParm("XAxisValuesFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxisValuesFontSize"] = getParm("YAxisValuesFontSize", nil, nil, "n", Parms["FontSize"])
Parms["YAxis2ValuesFontSize"] = getParm("YAxis2ValuesFontSize", nil, nil, "n", Parms["FontSize"])
Parms["ChartTextFontSize"] = getParm("ChartTextFontSize", nil, nil, "n", Parms["FontSize"])
Parms["LabelsFontSize"] = getParm("LabelsFontSize", nil, nil, "n", Parms["FontSize"])
-- bar width & spacing
Parms["BarWidth"] = getParm("BarWidth", nil, nil, "n", 20)
Parms["BarSpace"] = getParm("BarSpace", nil, nil, "n", 0) -- % of bar width
-- general graph line width
Parms["GraphLineWidth"] = getParm("GraphLineWidth", nil, nil, "n", 100)
-- pie chart
Parms["PieRadius"] = getParm("PieRadius", nil, nil, "n", 200)
Parms["Explode"] = getParm("Explode", nil, nil, "s") -- switch for exploding some or all pie segments
if Parms["Explode"] ~= nil then
if tonumber(Parms["Explode"]) ~= nil then
Parms["Explode"] = tonumber(Parms["Explode"])
-- default explode radius is 20%
Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 20)
else
-- any non-numeric value = all, default explode radius is 10%
Parms["ExplodeRadius"] = getParm("ExplodeRadius", nil, nil, "n", 10)
end
end
-- Parms[] SegmentNColor, SegmentNPattern and SegmentNPatternColor are done in pieChart()
Parms["SegmentText"] = getParm("SegmentText", nil, nil, "s") -- switch for showing series text and/or values on pie segments
Parms["SegmentTextWidth"] = getParm("SegmentTextWidth", nil, nil, "n", 100)
Parms["SegmentTextRadius"] = getParm("SegmentTextRadius", nil, nil, "n", 105)
Parms["DoughnutHole"] = getParm("DoughnutHole", nil, nil, "s") -- switch for a doughnut chart
if Parms["DoughnutHole"] ~= nil then
if tonumber(Parms["DoughnutHole"]) ~= nil then
-- if DoughnutHole is a number, ensure it is of numeric type
Parms["DoughnutHole"] = tonumber(Parms["DoughnutHole"])
else
-- any other value, hole size is 50%
Parms["DoughnutHole"] = 50
end
end
Parms["PieStartAngle"] = getParm("PieStartAngle", nil, nil, "n", 0) -- move the start angle
Parms["PieSweepDir"] = getParm("PieSweepDir", nil, nil, "s", "AntiClockwise") -- any value not "AntiClockwise" will switch the sweep direction
-- bar and pie-segment borders
Parms["BorderColor"] = checkColor(getParm("BorderColor", nil, nil, "s")) -- switch for showing borders on bars
Parms["BorderWidth"] = getParm("BorderWidth", nil, nil, "n", 100) -- % of standard line width
-- series
i = 1
Parms["Series" .. i .. "Values"] = getParm("Series", i, "Values", "s") -- switch for showing a series
while Parms["Series" .. i .. "Values"] ~= nil do
Parms["Series" .. i .. "Type"] = getParm("Series", i, "Type", "s")
Parms["Series" .. i .. "YAxis2"] = getParm("Series", i, "YAxis2") -- switch for series to use YAxis2 as scale for Y values
Parms["Series" .. i .. "Labels"] = getParm("Series", i, "Labels") -- switch for series to show data labels
Parms["Series" .. i .. "Color"] = checkColor(getParm("Series", i, "Color", "s", iif(Parms["GrayScale"] ~= nil, GrayColor[i], DefColor[i])))
Parms["Series" .. i .. "Line"] = getParm("Series", i, "Line", "s")
Parms["Series" .. i .. "Width"] = getParm("Series", i, "Width", "n", Parms["GraphLineWidth"])
Parms["Series" .. i .. "Dash"] = getParm("Series", i, "Dash", "s", KeyWords["none"])
t = tonumber(Parms["Series" .. i .. "Dash"])
if t ~= nil and t <= #DashPattern then
-- if SeriesNDash is a number in the table of default dashes, use the dash pattern for that number
Parms["Series" .. i .. "Dash"] = DashPattern[t]
end
Parms["Series" .. i .. "Marker"] = getParm("Series", i, "Marker", "s") -- switch for markers on the graph
if tonumber(Parms["Series" .. i .. "Marker"]) ~= nil then
-- if SeriesNMarker is a number, ensure it is of numeric type
Parms["Series" .. i .. "Marker"] = tonumber(Parms["Series" .. i .. "Marker"])
end
Parms["Series" .. i .. "MarkerSize"] = getParm("Series", i, "MarkerSize", "n", 100)
Parms["Series" .. i .. "MarkerFill"] = checkColor(getParm("Series", i, "MarkerFill", "s"))
Parms["Series" .. i .. "Pattern"] = getParm("Series", i, "Pattern", "n")
if Parms["Series" .. i .. "Pattern"] ~= nil then
Parms["Series" .. i .. "PatternColor"] = checkColor(getParm("Series", i, "PatternColor", "s", iif(Parms["GrayScale"] ~= nil, "white", "black")))
end
SeriesCount = SeriesCount + 1
i = i + 1
Parms["Series" .. i .. "Values"] = getParm("Series", i, "Values", "s") -- switch for showing a series
end
-- groups
i = 1
Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s") -- switch for grouping values
while Parms["Group" .. i .. "Text"] ~= nil do
GroupsCount = GroupsCount + 1
i = i + 1
Parms["Group" .. i .. "Text"] = getParm("Group", i, "Text", "s")
end
if GroupsCount > 0 then
Parms["XMax"] = GroupsCount
end
-- chart texts
i = 1
Parms["ChartText" .. i] = getParm("ChartText", i, nil, "s") -- switch for showing chart text N
while Parms["ChartText" .. i] ~= nil do
Parms["ChartText" .. i .. "X"] = getParm("ChartText", i, "X", "n", 0)
Parms["ChartText" .. i .. "Y"] = getParm("ChartText", i, "Y", "n", 0)
ChartTextCount = ChartTextCount + 1
i = i + 1
Parms["ChartText" .. i] = getParm("ChartText", i, nil, "s") -- switch for showing chart text N
end
Parms["IncludeOriginalData"] = getParm("IncludeOriginalData", nil, nil, "s", KeyWords["auto"])
-- debug
Parms["Debug"] = getParm("Debug", nil, nil, "s") -- switch to run debug code
end
function getParm(s1, num, s2, typ, def, subst, with)
-- returns the value of a named parameter (of a specified type) or a default
-- the name may be built from multiple parts (s1, num, s2)
local s = KeyWords[s1]
if num ~= nil then
s = s .. num
end
if s2 ~= nil then
s = s .. KeyWords[s2]
end
local result
if Args[s] ~= nil then
result = Args[s]
if typ == "n" then
result = tonumber(result)
if result == nil then
table.insert(Msgs, string.format(MessageTexts.NotNumeric, s, Args[s]))
end
end
else
result = def
end
-- optional substitution of characters within the parameter value
if result ~= nil and subst and with then
result = mw.ustring.gsub(result, subst, with)
end
return result
end
function checkColor(p)
-- checks if p is numeric and in the colors table(s)
-- if so returns the color from the table
-- otherwise returns the original parameter value
if p == nil then
return nil
end
local t = tonumber(p)
if t ~= nil and t <= #DefColor then
return iif(Parms["GrayScale"] ~= nil, GrayColor[t], DefColor[t])
else
return p
end
end
function transfer(Parm, Tab, SetSize)
-- transfer values from a space-delimited parameter to a table
local i = 0
Parm = mw.text.trim(Parm)
if SetSize > 1 then
local x = SetSize + 1 -- to force new table for first time
for s in mw.text.gsplit(Parm, "%s+") do
if x > SetSize then
i = i + 1
Tab[i] = {}
x = 1
end
Tab[i][x] = s
x = x + 1
end
else
for s in mw.text.gsplit(Parm, "%s+") do
i = i + 1
Tab[i] = s
end
end
SeriesMaxLen = math.max(SeriesMaxLen, i)
end
function build(ParmStart, ParmEnd, Size, Tab, DefaultValue, UseI, DefaultTable)
-- builds table of specified size from a series of StartNEnd parameters
-- any nil parameters in the sequence may be filled with a default value, or i (with a prefix if set), or a value from a default table
for i = 1, Size do
if Parms[ParmStart .. i .. ParmEnd] ~= nil then
Tab[i] = Parms[ParmStart .. i .. ParmEnd]
elseif DefaultValue ~= nil then
Tab[i] = DefaultValue
elseif type(UseI) == "boolean" then
Tab[i] = i
elseif type(UseI) == "string" then
Tab[i] = UseI .. i
elseif DefaultTable ~= nil then
Tab[i] = DefaultTable[i]
else
-- nothing
end
end
end
function calcStack()
-- calculate stacked totals for series
-- the current accumlated total overwrites the SeriesData Y value
local PosTotal, NegTotal, NegFlag = {}, {}, false
for j = 1, #GroupText do
PosTotal[j] = 0
NegTotal[j] = 0
for i = 1, #SeriesData do
if SeriesData[i][j] ~= nil and tonumber(SeriesData[i][j][1]) == j then
if tonumber(SeriesData[i][j][2]) < 0 then
NegTotal[j] = NegTotal[j] - SeriesData[i][j][2]
SeriesData[i][j][2] = -NegTotal[j]
NegFlag = true
else
PosTotal[j] = PosTotal[j] + SeriesData[i][j][2]
SeriesData[i][j][2] = PosTotal[j]
end
end
end
end
if DoStack100 then
for j = 1, #GroupText do
for i = 1, #SeriesData do
if SeriesData[i][j] ~= nil then
SeriesData[i][j][2] = SeriesData[i][j][2] / iif(tonumber(SeriesData[i][j][2]) < 0, NegTotal[j], PosTotal[j]) * 100
end
end
end
if NegFlag then
Parms["YMin"] = -100
end
end
end
function checkParms()
-- check for parameter issues
-- unknown parameters
local regexps = {}
-- add patterns to regexps
table.insert(regexps, "Series[%d][%d]*Values")
table.insert(regexps, "Series[%d][%d]*Type")
table.insert(regexps, "Series[%d][%d]*YAxis2")
table.insert(regexps, "Series[%d][%d]*Labels")
table.insert(regexps, "Series[%d][%d]*Color")
table.insert(regexps, "Series[%d][%d]*Line")
table.insert(regexps, "Series[%d][%d]*Width")
table.insert(regexps, "Series[%d][%d]*Dash")
table.insert(regexps, "Series[%d][%d]*Marker")
table.insert(regexps, "Series[%d][%d]*MarkerSize")
table.insert(regexps, "Series[%d][%d]*MarkerFill")
table.insert(regexps, "Series[%d][%d]*Pattern")
table.insert(regexps, "Series[%d][%d]*PatternColor")
table.insert(regexps, "Series[%d][%d]*Text")
table.insert(regexps, "Group[%d][%d]*Text")
table.insert(regexps, "Segment[%d][%d]*Color")
table.insert(regexps, "Segment[%d][%d]*Pattern")
table.insert(regexps, "Segment[%d][%d]*PatternColor")
table.insert(regexps, "ChartText[%d][%d]*")
table.insert(regexps, "ChartText[%d][%d]*X")
table.insert(regexps, "ChartText[%d][%d]*Y")
local unknowns = unknownParameters(KeyWords, regexps)
if #unknowns > 0 then
-- ensure parms-not-found are at the top of the list of messages
local tmp = {}
copyTable(Msgs, tmp)
Msgs = {}
table.insert(Msgs, MessageTexts.ParmsNotFound)
for k, v in spairs(unknowns) do
table.insert(Msgs, " " .. unknowns[k])
end
for i = 1, #tmp do
table.insert(Msgs, tmp[i])
end
end
if #GroupText > 0 then
for k1, v1 in ipairs(SeriesData) do
for k2, v2 in ipairs(v1) do
local t = tonumber(v2[1])
if t ~= math.floor(t)
or t < 1
or t > #GroupText then
table.insert(Msgs, string.format(MessageTexts.NotInGroup, k1, k2, v2[1]))
end
end
end
end
if not DoYAxis2 then
for k, v in pairs(YAxis2) do
table.insert(Msgs, string.format(MessageTexts.NoYAxis2, k))
end
end
for k, v in pairs(Marker) do
local t = tonumber(v)
if v == "none" then
-- OK
elseif t == nil then
table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v))
elseif (t >= 1 and t <= 7) then
-- OK
else
table.insert(Msgs, string.format(MessageTexts.NotInMarkers, k, v))
end
end
for k, v in pairs(LineDash) do
local t = tonumber(v)
if v == "none" then
-- OK
elseif t == nil then
-- non-numerics are OK for LineDash
elseif (t >= 1 and t <= #DashPattern) then
-- OK
else
table.insert(Msgs, string.format(MessageTexts.NotInDashPatterns, k, v))
end
end
for k, v in pairs(FillPattern) do
local t = tonumber(v)
if v == "none" then
-- OK
elseif t == nil then
table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v))
elseif (t >= 1 and t <= 8)
or (t >= 11 and t <= 18)
or (t >= 21 and t <= 24)
or (t >= 31 and t <= 34)
or (t >= 41 and t <= 49)
or (t >= 51 and t <= 56)
or (t >= 61 and t <= 64) then
-- OK
else
table.insert(Msgs, string.format(MessageTexts.NotInFillPatterns, k, v))
end
end
if #Msgs > 0 then
-- add messages to output
table.insert(r, " " .. MessageTexts.MsgHeading)
for i = 1, math.min(#Msgs, 10) do
table.insert(r, " " .. Msgs[i])
end
return false
end
return true
end
function prelimsCommon()
Siz.ImagePadding = {}
Siz.Legend = {}
Siz.Text = {}
-- the base unit for font sizes can be changed by the user
BaseFontSize = BaseFontSize * (Parms["FontSize"] / 100)
FontSiz.Title = 7 * BaseFontSize * Parms["TitleFontSize"] / 100
FontSiz.LegendText = 5 * BaseFontSize * Parms["LegendFontSize"] / 100
FontSiz.Labels = 4 * BaseFontSize * Parms["LabelsFontSize"] / 100
FontSiz.ChartText = 4 * BaseFontSize * Parms["ChartTextFontSize"] / 100
FontSiz.Footnote = 3 * BaseFontSize * Parms["FootnoteFontSize"] / 100
-- the base line width can be changed by the user
BaseLineWidth = BaseLineWidth * Parms["LineWidth"] / 100
-- define sizes of other image components from base spacing units
Siz.ImagePadding.Top = 2 * BaseUnit
if Parms["ImagePaddingTop"] ~= nil then
Siz.ImagePadding.Top = Parms["ImagePaddingTop"]
end
Siz.ImagePadding.Bottom = 2 * BaseUnit
if Parms["ImagePaddingBottom"] ~= nil then
Siz.ImagePadding.Bottom = Parms["ImagePaddingBottom"]
end
Siz.ImagePadding.Left = 2 * BaseUnit
if Parms["ImagePaddingLeft"] ~= nil then
Siz.ImagePadding.Left = Parms["ImagePaddingLeft"]
end
Siz.ImagePadding.Right = 2 * BaseUnit
if Parms["ImagePaddingRight"] ~= nil then
Siz.ImagePadding.Right = Parms["ImagePaddingRight"]
end
Siz.ChartMargin = 2 * BaseUnit
Siz.Text.Interline = 2 * BaseFontSize
Siz.Text.Labels = FontSiz.Labels
Siz.Text.Title = iif(Parms["Title"] ~= nil and Parms["TitleX"] == nil, FontSiz.Title + Siz.Text.Interline, 0)
Siz.Footnote = iif(Parms["Footnote"] ~= nil and Parms["FootnoteX"] == nil, FontSiz.Footnote, 0)
Siz.Legend.Text = FontSiz.LegendText
Siz.Legend.TextWidth = 5 * FontSiz.LegendText * Parms["LegendTextWidth"] / 100
Siz.Legend.Offset = 3 * BaseUnit
Siz.Text.Chart = FontSiz.ChartText
end
function prelimsAxes()
Pos.XAxis = {}
Pos.YAxis = {}
Pos.YAxis2 = {}
Pos.Origin = {}
Siz.Space = {}
Siz.XAxis = {}
Siz.YAxis = {}
Siz.YAxis2 = {}
Siz.ChartWidth = Parms["ChartWidth"]
Siz.ChartHeight = Parms["ChartHeight"]
if DoChartAdjust then
allChartAdjust()
end
if DoHorizontal then
Siz.XAxis.Length = Siz.ChartHeight
Siz.YAxis.Length = Siz.ChartWidth
Siz.YAxis2.Length = Siz.ChartWidth
else
Siz.XAxis.Length = Siz.ChartWidth
Siz.YAxis.Length = Siz.ChartHeight
Siz.YAxis2.Length = Siz.ChartHeight
end
prelimsLegend()
BarWidth = Parms["BarWidth"]
BarSpace = Parms["BarSpace"] / 100
-- The Mult values are the number of pixels per 1 unit on each axis
if #GroupText > 0 then
if BarSeriesCount > 0 then
GroupWidth = Siz.XAxis.Length / #GroupText
if DoStack or DoStack100 then
UnitWidth = GroupWidth / 2
else
UnitWidth = GroupWidth / (BarSeriesCount + 1)
end
BarWidth = UnitWidth / (1 + BarSpace)
BarSpace = UnitWidth - BarWidth
Mult.x = Siz.XAxis.Length / #GroupText
else
GroupWidth = Siz.XAxis.Length / #GroupText
Mult.x = GroupWidth
end
else
Mult.x = Siz.XAxis.Length / (math.abs(Parms["XMax"] - Parms["XMin"]))
end
Mult.y = Siz.YAxis.Length / (math.abs(Parms["YMax"] - Parms["YMin"]))
if DoYAxis2 then
Mult.y2 = Siz.YAxis2.Length / (math.abs(Parms["Y2Max"] - Parms["Y2Min"]))
else
Mult.y2 = 1
end
--
FontSiz.XAxisTitle = 5 * BaseFontSize * Parms["XAxisTitleFontSize"] / 100
FontSiz.XAxisValues = 4 * BaseFontSize * Parms["XAxisValuesFontSize"] / 100
FontSiz.YAxisTitle = 5 * BaseFontSize * Parms["YAxisTitleFontSize"] / 100
FontSiz.YAxisValues = 4 * BaseFontSize * Parms["YAxisValuesFontSize"] / 100
FontSiz.YAxis2Title = 5 * BaseFontSize * Parms["YAxis2TitleFontSize"] / 100
FontSiz.YAxis2Values = 4 * BaseFontSize * Parms["YAxis2ValuesFontSize"] / 100
Siz.AxisMark = 2 * BaseUnit
Siz.AxisMark2 = 1 * BaseUnit
Siz.XAxis.Title = iif(Parms["XAxisTitle"] ~= nil, FontSiz.XAxisTitle + Siz.Text.Interline, 0)
Siz.YAxis.Title = iif(Parms["YAxisTitle"] ~= nil, FontSiz.YAxisTitle + Siz.Text.Interline, 0)
if DoHorizontal then
Siz.XAxis.Values = iif(Parms["XAxisValueSpace"] ~= nil, Parms["XAxisValueSpace"], 3 * FontSiz.XAxisValues)
Siz.YAxis.Values = iif(Parms["YAxisValueSpace"] ~= nil, Parms["YAxisValueSpace"], FontSiz.YAxisValues + Siz.Text.Interline)
else
Siz.XAxis.Values = iif(Parms["XAxisValueSpace"] ~= nil, Parms["XAxisValueSpace"], FontSiz.XAxisValues + Siz.Text.Interline)
Siz.YAxis.Values = iif(Parms["YAxisValueSpace"] ~= nil, Parms["YAxisValueSpace"], 3 * FontSiz.YAxisValues)
end
Siz.YAxis2.Title = 0
Siz.YAxis2.Values = 0
if DoYAxis2 then
if Parms["YAxis2Title"] ~= nil then
Siz.YAxis2.Title = FontSiz.YAxis2Title + Siz.Text.Interline
end
if DoHorizontal then
Siz.YAxis2.Values = iif(Parms["YAxis2ValueSpace"] ~= nil, Parms["YAxis2ValueSpace"], FontSiz.YAxis2Values + Siz.Text.Interline)
else
Siz.YAxis2.Values = iif(Parms["YAxis2ValueSpace"] ~= nil, Parms["YAxis2ValueSpace"], 3 * FontSiz.YAxis2Values)
end
end
-- spaces around the chart (working out from the chart):
-- AxisMarks
-- ChartMargin
-- AxisValues
-- AxisTitle
-- Title
-- Legend
-- Footnote
-- ImagePadding
-- Top Space
Siz.Space.Top = Siz.ImagePadding.Top
Pos.Title = Siz.ImagePadding.Top + FontSiz.Title
Siz.Space.Top = Siz.Space.Top + Siz.Text.Title
if DoHorizontal and DoYAxis2 then
Pos.YAxis2.Title = Siz.Space.Top + FontSiz.YAxis2Title
Siz.Space.Top = Siz.Space.Top + Siz.YAxis2.Title
Siz.Space.Top = Siz.Space.Top + Siz.YAxis2.Values
Siz.Space.Top = Siz.Space.Top + Siz.ChartMargin + Siz.AxisMark
Pos.YAxis2.Line = -Parms["XMax"] * Mult.x
Pos.YAxis2.Values = Pos.YAxis2.Line - Siz.AxisMark - Siz.ChartMargin -- pos is bottom edge of values box
else
Siz.Space.Top = Siz.Space.Top + Siz.ChartMargin
end
-- Bottom Space
if DoHorizontal then
if Parms["XMin"] < 0 and #GroupText == 0 then
-- Y axis line is within chart
Pos.YAxis.Line = 0
Pos.YAxis.Values = Pos.YAxis.Line + Siz.AxisMark + Siz.ChartMargin + FontSiz.YAxisValues
Siz.Space.Bottom = Siz.ChartMargin
else
-- Y axis line is at bottom of chart
Pos.YAxis.Line = -Parms["XMin"] * Mult.x
Siz.Space.Bottom = Siz.AxisMark
Pos.YAxis.Values = Pos.YAxis.Line + Siz.Space.Bottom + Siz.ChartMargin + FontSiz.YAxisValues
Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartMargin + Siz.YAxis.Values
end
if Parms["YAxisTitle"] ~= nil then
Pos.YAxis.Title = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + FontSiz.YAxisTitle
Siz.Space.Bottom = Siz.Space.Bottom + Siz.YAxis.Title
end
else
if Parms["YMin"] < 0 then
-- X axis line is within chart
Pos.XAxis.Line = 0
Pos.XAxis.Values = Pos.XAxis.Line + Siz.AxisMark + Siz.ChartMargin + FontSiz.XAxisValues
Siz.Space.Bottom = Siz.ChartMargin
else
-- X axis line is at bottom of chart
Pos.XAxis.Line = -Parms["YMin"] * Mult.y
Siz.Space.Bottom = iif(#GroupText > 0, 0, Siz.AxisMark)
Pos.XAxis.Values = Pos.XAxis.Line + Siz.Space.Bottom + Siz.ChartMargin + FontSiz.XAxisValues
Siz.Space.Bottom = Siz.Space.Bottom + Siz.ChartMargin + Siz.XAxis.Values
end
if Parms["XAxisTitle"] ~= nil then
Pos.XAxis.Title = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + FontSiz.XAxisTitle
Siz.Space.Bottom = Siz.Space.Bottom + Siz.XAxis.Title
end
end
if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline
end
if Parms["Footnote"] ~= nil then
Pos.Footnote = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Footnote
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote
end
Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom
-- Left Space
Siz.Space.Left = Siz.ImagePadding.Left
if DoHorizontal then
if Parms["XAxisTitle"] ~= nil then
Pos.XAxis.Title = Siz.Space.Left + FontSiz.XAxisTitle -- pos is right edge of title
Siz.Space.Left = Siz.Space.Left + Siz.XAxis.Title
end
if Parms["YMin"] < 0 then
-- X axis line is within chart
Siz.Space.Left = Siz.Space.Left + Siz.ChartMargin
Pos.XAxis.Line = 0
Pos.XAxis.Values = Pos.XAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box
else
-- X axis line is at left of chart
Siz.Space.Left = Siz.Space.Left + Siz.XAxis.Values + Siz.ChartMargin + Siz.AxisMark
Pos.XAxis.Line = Parms["YMin"] * Mult.y
Pos.XAxis.Values = Pos.XAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box
end
else
if Parms["YAxisTitle"] ~= nil then
Pos.YAxis.Title = Siz.Space.Left + FontSiz.YAxisTitle -- pos is right edge of title
Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Title
end
if Parms["XMin"] < 0 and #GroupText == 0 then
-- Y axis line is within chart
Siz.Space.Left = Siz.Space.Left + Siz.ChartMargin
Pos.YAxis.Line = 0
Pos.YAxis.Values = Pos.YAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box
else
-- Y axis line is at left of chart
Siz.Space.Left = Siz.Space.Left + Siz.YAxis.Values + Siz.ChartMargin + Siz.AxisMark
Pos.YAxis.Line = Parms["XMin"] * Mult.x
Pos.YAxis.Values = Pos.YAxis.Line - Siz.AxisMark - Siz.ChartMargin -- pos is right edge of values box
end
end
-- Right Space
Siz.Space.Right = 0
if DoHorizontal then
Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin
else
if DoYAxis2 then
Pos.YAxis2.Line = Parms["XMax"] * Mult.x
Siz.Space.Right = Siz.Space.Right + Siz.AxisMark
Pos.YAxis2.Values = Pos.YAxis2.Line + Siz.Space.Right + Siz.ChartMargin -- pos is left edge of values box
Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin + Siz.YAxis2.Values
if Parms["YAxis2Title"] ~= nil then
Pos.YAxis2.Title = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + FontSiz.YAxis2Title
Siz.Space.Right = Siz.Space.Right + Siz.YAxis2.Title
end
else
Siz.Space.Right = Siz.Space.Right + Siz.ChartMargin
end
end
if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + Siz.Legend.Offset
Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width
end
Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right
if DoHorizontal then
Pos.XAxis.Zero = Siz.Space.Top + Siz.ChartHeight + (iif(#GroupText == 0, Parms["XMin"], 0) * Mult.x)
Pos.YAxis.Zero = Siz.Space.Left - (Parms["YMin"] * Mult.y)
else
Pos.XAxis.Zero = Siz.Space.Left - (iif(#GroupText == 0, Parms["XMin"], 0) * Mult.x)
Pos.YAxis.Zero = Siz.Space.Top + Siz.ChartHeight + (Parms["YMin"] * Mult.y)
end
-- Chart Origin offsets
if DoHorizontal then
Pos.Origin.v = round(Siz.Space.Top
+ (Parms["XMax"] * Mult.x), 2)
Pos.Origin.h = round(Siz.Space.Left
+ ((0 - Parms["YMin"]) * Mult.y), 2)
else
Pos.Origin.v = round(Siz.Space.Top
+ (Parms["YMax"] * Mult.y), 2)
Pos.Origin.h = round(Siz.Space.Left
+ ((0 - Parms["XMin"]) * Mult.x), 2)
end
-- Image size
ImageWidth = round(Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right, 0)
ImageHeight = round(Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom, 0)
end
function allChartAdjust()
Adjusts.ChartWidth = {}
Adjusts.ChartWidth.Old = Siz.ChartWidth
Adjusts.ChartHeight = {}
Adjusts.ChartHeight.Old = Siz.ChartHeight
Adjusts.XMax = {}
Adjusts.XMax.Old = Parms["XMax"]
Adjusts.XMin = {}
Adjusts.XMin.Old = Parms["XMin"]
Adjusts.YMax = {}
Adjusts.YMax.Old = Parms["YMax"]
Adjusts.YMin = {}
Adjusts.YMin.Old = Parms["YMin"]
if DoHorizontal then
Siz.ChartWidth = chartAdjust(Siz.ChartWidth,
"YAxisValueStep", "YAxisMark2Step",
"YMax", "YMin")
if #GroupText == 0 then
Siz.ChartHeight = chartAdjust(Siz.ChartHeight,
"XAxisValueStep", "XAxisMark2Step",
"XMax", "XMin")
end
else
if #GroupText == 0 then
Siz.ChartWidth = chartAdjust(Siz.ChartWidth,
"XAxisValueStep", "XAxisMark2Step",
"XMax", "XMin")
end
Siz.ChartHeight = chartAdjust(Siz.ChartHeight,
"YAxisValueStep", "YAxisMark2Step",
"YMax", "YMin")
end
Adjusts.ChartWidth.New = Siz.ChartWidth
Adjusts.ChartHeight.New = Siz.ChartHeight
Adjusts.XMax.New = Parms["XMax"]
Adjusts.XMin.New = Parms["XMin"]
Adjusts.YMax.New = Parms["YMax"]
Adjusts.YMin.New = Parms["YMin"]
end
function chartAdjust(userlength, mark, mark2, max, min)
local unit = Parms[mark]
if Parms[mark2] ~= nil then
unit = Parms[mark2]
end
Parms[max] = round((Parms[max] / unit) + 0.49, 0) * unit
Parms[min] = round((Parms[min] / unit) - 0.49, 0) * unit
local count = (Parms[max] - Parms[min]) / unit
return round(userlength / count, 0) * count
end
function outputAdjusts()
table.insert(r, " <!-- Adjustments made by Charts SVG:")
table.insert(r, " ChartWidth: " .. Adjusts.ChartWidth.Old .. " : " .. Adjusts.ChartWidth.New)
table.insert(r, " ChartHeight: " .. Adjusts.ChartHeight.Old .. " : " .. Adjusts.ChartHeight.New)
table.insert(r, " XMax: " .. Adjusts.XMax.Old .. " : " .. Adjusts.XMax.New)
table.insert(r, " XMin: " .. Adjusts.XMin.Old .. " : " .. Adjusts.XMin.New)
table.insert(r, " YMax: " .. Adjusts.YMax.Old .. " : " .. Adjusts.YMax.New)
table.insert(r, " YMin: " .. Adjusts.YMin.Old .. " : " .. Adjusts.YMin.New)
table.insert(r, " -->")
table.insert(r, " ")
end
function prelimsPie()
Siz.Space = {}
-- pie chart radius & origin
PieRadius = Parms["PieRadius"] -- not local
PieOriginX, PieOriginY = PieRadius, PieRadius -- not local
if Parms["Explode"] ~= nil then
PieOriginX = PieOriginX + (PieRadius * Parms["ExplodeRadius"] / 100)
PieOriginY = PieOriginY + (PieRadius * Parms["ExplodeRadius"] / 100)
end
if Parms["SegmentText"] ~= nil then
-- possibly add for segment texts outside the pie
local TextSpaceX = (PieRadius * Parms["SegmentTextRadius"] / 100)
+ (5 * Siz.Text.Chart * Parms["SegmentTextWidth"] / 100)
if Parms["Explode"] ~= nil then
TextSpaceX = TextSpaceX + (PieRadius * Parms["ExplodeRadius"] / 100)
end
PieOriginX = math.max(PieOriginX, TextSpaceX)
local TextSpaceY = (PieRadius * Parms["SegmentTextRadius"] / 100)
+ (Siz.Text.Chart)
if Parms["Explode"] ~= nil then
TextSpaceY = TextSpaceY + (PieRadius * Parms["ExplodeRadius"] / 100)
end
PieOriginY = math.max(PieOriginY, TextSpaceY)
end
-- for pie charts, chart size is calculated, not user-defined
Siz.ChartWidth = PieOriginX * 2
Siz.ChartHeight = PieOriginY * 2
prelimsLegend()
-- Top Space
Siz.Space.Top = Siz.ImagePadding.Top + Siz.Text.Title + Siz.ChartMargin
Pos.Title = Siz.ImagePadding.Top + FontSiz.Title
-- Bottom Space
Siz.Space.Bottom = Siz.ChartMargin
if Parms["LegendType"] == KeyWords["horizontal"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Legend.Offset
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Legend.Offset + Siz.Legend.Height + Siz.Text.Interline
end
if Parms["Footnote"] ~= nil then
Pos.Footnote = Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom + Siz.Footnote
Siz.Space.Bottom = Siz.Space.Bottom + Siz.Footnote
end
Siz.Space.Bottom = Siz.Space.Bottom + Siz.ImagePadding.Bottom
-- Left Space
Siz.Space.Left = Siz.ImagePadding.Left + Siz.ChartMargin
-- Right Space
Siz.Space.Right = Siz.ChartMargin
if Parms["LegendType"] == KeyWords["vertical"] and Parms["LegendX"] == nil then
Pos.Legend = Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right + Siz.Legend.Offset
Siz.Space.Right = Siz.Space.Right + Siz.Legend.Offset + Siz.Legend.Width
end
Siz.Space.Right = Siz.Space.Right + Siz.ImagePadding.Right
-- Image size
ImageWidth = round(Siz.Space.Left + Siz.ChartWidth + Siz.Space.Right, 0)
ImageHeight = round(Siz.Space.Top + Siz.ChartHeight + Siz.Space.Bottom, 0)
-- Mult.x, Mult.y and Pos.Axis.Zero are needed for positioning of Legends and Title, Footnote and Chart texts
Mult.x = Siz.ChartWidth / 100
Mult.y = Siz.ChartHeight / 100
Pos.XAxis = {}
Pos.YAxis = {}
Pos.XAxis.Zero = Siz.Space.Left
Pos.YAxis.Zero = Siz.Space.Top + Siz.ChartHeight
end
function prelimsLegend()
-- legend height and width
Siz.Legend.ElementWidth = Siz.ChartMargin + (2 * Siz.Legend.Text) + Siz.ChartMargin + Siz.Legend.TextWidth
Siz.Legend.ElementHeight = Siz.Legend.Text + Siz.Text.Interline
LegendElementsInWidth = 1 -- not local
if #SeriesText < 1 then
Parms["LegendType"] = KeyWords["none"]
elseif Parms["LegendType"] == KeyWords["horizontal"] then
LegendElementsInWidth = math.floor(Siz.ChartWidth / Siz.Legend.ElementWidth)
LegendElementsInWidth = math.min(LegendElementsInWidth, #SeriesText)
local LegendLines = math.ceil(#SeriesText / LegendElementsInWidth)
Siz.Legend.Width = (LegendElementsInWidth * Siz.Legend.ElementWidth) + Siz.ChartMargin
Siz.Legend.Height = Siz.ChartMargin + (LegendLines * Siz.Legend.ElementHeight) + Siz.ChartMargin
else
Siz.Legend.Width = Siz.Legend.ElementWidth + Siz.ChartMargin
Siz.Legend.Height = Siz.ChartMargin + (#SeriesText * Siz.Legend.ElementHeight) + Siz.ChartMargin
end
end
function commonTop()
-- output header
table.insert(r, MessageTexts.CopyText)
table.insert(r, " ")
-- SVG header stuff
table.insert(r, " <?xml version=\"1.0\" encoding=\"UTF-8\" ?>")
table.insert(r, " <!-- Generator: commons.wikipedia.org/wiki/" .. mw.getCurrentFrame():getTitle() .. " -->")
table.insert(r, " <!-- Generator Version: 3.0 -->")
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, " ")
if not DoPie and DoChartAdjust then
outputAdjusts()
end
table.insert(r, " <!-- == Backgrounds == -->")
table.insert(r, " ")
-- image background rectangle
table.insert(r, " <!-- image background -->")
table.insert(r, " <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=\"" .. Siz.ChartWidth .. "\""
.. " height=\"" .. Siz.ChartHeight .. "\""
.. " fill=\"" .. Parms["ChartBackgroundColor"] .. "\"/>")
table.insert(r, " ")
end
end
function codeAxisGrids()
table.insert(r, " <!-- == Axis Chart - Translate == -->")
table.insert(r, " <g id=\"graphs\" transform=\"translate(" .. Pos.Origin.h .. ", " .. Pos.Origin.v .. ")\">")
table.insert(r, " ")
-- the 'horizontal' grid is on the horizontal axis, and has vertical lines
-- and vice versa
local HGridInterval, VGridInterval
local HLabel = 'x'
local VLabel = 'y'
if DoHorizontal then
HLabel = 'y'
VLabel = 'x'
end
if (HLabel == 'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"] == KeyWords["none"] then
-- no horizontal grid
else
HGridInterval = Parms[string.upper(HLabel) .. "Grid"] * Mult[HLabel]
end
if (VLabel == 'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"] == KeyWords["none"] then
-- no vertical grid
else
VGridInterval = Parms[string.upper(VLabel) .. "Grid"] * Mult[VLabel]
end
if Parms["XGrid"] ~= KeyWords["none"] or Parms["YGrid"] ~= KeyWords["none"] then
table.insert(r, " <!-- == Axis Chart - Grids == -->")
table.insert(r, " ")
table.insert(r, " <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 (HLabel == 'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"] == KeyWords["none"] then
-- no horizontal grid
else
table.insert(r, " <!-- " .. HLabel .. "-axis grid, vertical lines -->")
table.insert(r, " <pattern id=\"" .. HLabel .. "-gridline\""
.. " height=\"10\""
.. " width=\"" .. round(HGridInterval, 2) .. "\""
.. " patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\""
.. " y1=\"0\""
.. " x2=\"0\""
.. " y2=\"10\""
.. " class=\"gridline\"/>")
table.insert(r, " </pattern>")
end
if (VLabel == 'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"] == KeyWords["none"] then
-- no vertical grid
else
table.insert(r, " <!-- " .. VLabel .. "-axis grid, horizontal lines -->")
table.insert(r, " <pattern id=\"" .. VLabel .. "-gridline\""
.. " height=\"" .. round(VGridInterval, 2) .. "\""
.. " width=\"10\""
.. " patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\""
.. " y1=\"0\""
.. " x2=\"10\""
.. " y2=\"0\""
.. " class=\"gridline\"/>")
table.insert(r, " </pattern>")
end
table.insert(r, " </defs>")
table.insert(r, " ")
local XStart = Parms[string.upper(HLabel) .. "Min"] * Mult[HLabel]
local YStart = -Parms[string.upper(VLabel) .. "Max"] * Mult[VLabel]
if (HLabel == 'x' and #GroupText > 0) or Parms[string.upper(HLabel) .. "Grid"] == KeyWords["none"] then
-- no horizontal grid
else
table.insert(r, " <rect id=\"" .. HLabel .. "-gridline-area\""
.. " x=\"" .. round(XStart, 2) .. "\""
.. " y=\"" .. round(YStart, 2) .. "\""
.. " width=\"" .. Siz.ChartWidth + BaseLineWidth .. "\""
.. " height=\"" .. Siz.ChartHeight .. "\""
.. " fill=\"url(#" .. HLabel .. "-gridline)\"/>")
end
if (VLabel == 'x' and #GroupText > 0) or Parms[string.upper(VLabel) .. "Grid"] == KeyWords["none"] then
-- no vertical grid
else
table.insert(r, " <rect id=\"" .. VLabel .. "-gridline-area\""
.. " x=\"" .. round(XStart, 2) .. "\""
.. " y=\"" .. round(YStart, 2) .. "\""
.. " width=\"" .. Siz.ChartWidth .. "\""
.. " height=\"" .. Siz.ChartHeight + BaseLineWidth .. "\""
.. " fill=\"url(#" .. VLabel .. "-gridline)\"/>")
end
table.insert(r, " ")
end
end
function stylesAreas()
table.insert(r, " <!-- == Graph - Area Styles == -->")
table.insert(r, " ")
table.insert(r, " <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 DoLabels = false
local LabelPos = {}
table.insert(r, " <!-- == Graph - Bars and Lines == -->")
table.insert(r, " ")
for i = #SeriesData, 1, -1 do
if SeriesText[i] == nil then
table.insert(r, " <!-- " .. "Series" .. i .. " -->")
else
table.insert(r, " <!-- " .. SeriesText[i] .. " -->")
end
if DoYAxis2 and YAxis2[i] ~= nil then
table.insert(r, " <!-- Y values are on second Y axis -->")
end
if Parms["IncludeOriginalData"] == KeyWords["no"]
or (Parms["IncludeOriginalData"] == KeyWords["auto"] and DataPointsCount > AutoDataPointsLimit) then
table.insert(r, " <!-- original data: not included -->")
else
table.insert(r, " <!-- original data:")
for k, v in ipairs(OriginalData[i]) do
table.insert(r, " " .. v[1] .. " " .. v[2])
end
table.insert(r, " -->")
end
LabelPos[i] = {}
if SType[i] == KeyWords["bar"] then
BarNumber = BarNumber - 1
for k, v in ipairs(SeriesData[i]) do
if v[2] ~= nil then
local px = tonumber(v[1])
if #GroupText > 0 then
if DoStack or DoStack100 then
px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth
+ UnitWidth / 2
+ (BarSpace / 2)
+ iif(DoHorizontal, BarWidth, 0)
else
px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth
+ UnitWidth / 2
+ UnitWidth * (iif(DoGroupsTopDown, BarSeriesCount - BarNumber + 1, BarNumber) - iif(DoHorizontal, 0, 1))
+ (BarSpace / 2)
end
else
px = (px * Mult.x) + (iif(DoHorizontal, 1, -1) * (BarWidth / 2))
end
local Min, Multiply = Parms["YMin"], Mult.y
if DoYAxis2 and YAxis2[i] ~= nil then
Min = Parms["Y2Min"]
Multiply = Mult.y2
end
local val = tonumber(v[2])
local posn, size = 0, 0
if DoHorizontal then
if Min >= 0 then
posn = 0
size = val - Min
elseif val >= 0 then
posn = 0
size = val
else
posn = -val
size = -val
end
posn = Pos.XAxis.Line - (posn * Multiply)
size = size * Multiply
table.insert(r, " <rect id=\"series" .. i .. "-" .. k .. "\" class=\"series-areas-general series" .. i .. "\""
.. " x=\"" .. round(posn, 2) .. "\" y=\"" .. round(-px, 2) .. "\""
.. " width=\"" .. round(size, 2) .. "\" height=\"" .. round(BarWidth, 2) .. "\" />")
else
if Min >= 0 then
posn = val - Min
size = val - Min
elseif val >= 0 then
posn = val
size = val
else
posn = 0
size = -val
end
posn = Pos.XAxis.Line - (posn * Multiply)
size = size * Multiply
table.insert(r, " <rect id=\"series" .. i .. "-" .. k .. "\" class=\"series-areas-general series" .. i .. "\""
.. " x=\"" .. round(px, 2) .. "\" y=\"" .. round(posn, 2) .. "\""
.. " width=\"" .. round(BarWidth, 2) .. "\" height=\"" .. round(size, 2) .. "\" />")
end
if Labels[i] ~= nil then
LabelPos[i][k] = {}
if DoHorizontal then
LabelPos[i][k]["y"] = -(px - (BarWidth / 2))
if val <= 0 then
LabelPos[i][k]["x"] = posn - Siz.Text.Interline
else
LabelPos[i][k]["x"] = posn + size + Siz.Text.Interline
end
else
LabelPos[i][k]["x"] = px + (BarWidth / 2)
if val <= 0 then
LabelPos[i][k]["y"] = posn + size + Siz.Text.Interline + Siz.Text.Labels
else
LabelPos[i][k]["y"] = posn - Siz.Text.Interline
end
end
DoLabels = true
end
end
end
else
-- line
tr = " <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 not DoArea and Marker[i] ~= nil and Marker[i] ~= KeyWords["none"] then
-- set the line to have markers
table.insert(r, " marker-start=\"url(#series" .. i .. "marker)\" marker-mid=\"url(#series" .. i .. "marker)\" marker-end=\"url(#series" .. i .. "marker)\"")
end
table.insert(r, " points=\"")
local lastx = 0
-- multiply numeric values as necessary
for k, v in ipairs(SeriesData[i]) do
local px = tonumber(v[1])
if #GroupText > 0 then
px = iif(DoGroupsTopDown, #GroupText - px, px - 1) * GroupWidth
+ (GroupWidth / 2)
else
px = px * Mult.x
end
local py = tonumber(v[2])
if DoYAxis2 and YAxis2[i] ~= nil then
py = py * Mult.y2
else
py = py * Mult.y
end
if DoHorizontal then
if DoArea and k == 1 then
table.insert(r, " " .. round(iif(Parms["YMin"] > 0, Parms["YMin"] * Mult.y, 0), 2) .. ", " .. round(-px, 2) .. "")
end
table.insert(r, " " .. round(py, 2) .. ", " .. round(-px, 2) .. "")
lastx = px
if Labels[i] ~= nil then
LabelPos[i][k] = {}
LabelPos[i][k]["x"] = px + Siz.Text.Interline
LabelPos[i][k]["y"] = py + Siz.Text.Interline
DoLabels = true
end
else
if DoArea and k == 1 then
table.insert(r, " " .. round(px, 2) .. ", " .. round(iif(Parms["YMin"] > 0, -Parms["YMin"] * Mult.y, 0), 2) .. "")
end
table.insert(r, " " .. round(px, 2) .. ", " .. round(-py, 2) .. "")
lastx = px
if Labels[i] ~= nil then
LabelPos[i][k] = {}
LabelPos[i][k]["x"] = px + Siz.Text.Interline
LabelPos[i][k]["y"] = py + Siz.Text.Interline
DoLabels = true
end
end
end
if DoArea then
if DoHorizontal then
table.insert(r, " " .. round(iif(Parms["YMin"] > 0, Parms["YMin"] * Mult.y, 0), 2) .. ", " .. round(-lastx, 2) .. "")
else
table.insert(r, " " .. round(lastx, 2) .. ", " .. round(iif(Parms["YMin"] > 0, -Parms["YMin"] * Mult.y, 0), 2) .. "")
end
end
table.insert(r, " \"/>")
end
table.insert(r, " ")
end
if not DoStack100 and DoLabels then
table.insert(r, " <!-- == Data Labels == -->")
table.insert(r, " ")
table.insert(r, " <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
l = " <text id=\"series" .. i .. "-" .. k .. "-label\" class=\"labeltext\""
.. " x=\"" .. round(LabelPos[i][k]["x"], 2) .. "\""
.. " y=\"" .. round(LabelPos[i][k]["y"], 2) .. "\""
if DoHorizontal then
l = l .. " text-anchor=\"" .. iif(tonumber(v[2]) <= 0, "end", "start") .. "\""
.. " transform=\"translate(" .. 0 .. ", " .. round(Siz.Text.Labels / 3) .. ")\""
else
l = l .. " text-anchor=\"middle\""
end
l = l .. ">" .. v[2]
.. "</text>"
table.insert(r, l)
else
if DoHorizontal then
table.insert(r, " <text id=\"series" .. i .. "-" .. k .. "-label\" class=\"labeltext\""
.. " x=\"" .. round(LabelPos[i][k]["y"], 2) .. "\""
.. " y=\"" .. round(-LabelPos[i][k]["x"], 2) .. "\""
.. " text-anchor=\"left\">"
.. v[2]
.. "</text>")
else
table.insert(r, " <text id=\"series" .. i .. "-" .. k .. "-label\" class=\"labeltext\""
.. " x=\"" .. round(LabelPos[i][k]["x"], 2) .. "\""
.. " y=\"" .. round(-LabelPos[i][k]["y"], 2) .. "\""
.. " text-anchor=\"left\">"
.. v[2]
.. "</text>")
end
end
end
table.insert(r, " ")
end
end
end
end
function elementsPie()
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 = math.cos(Radian) * InnerRadius
InnerY = math.sin(Radian) * InnerRadius
end
-- outer arcs start at the pie radius
local OuterX = math.cos(Radian) * PieRadius
local OuterY = math.sin(Radian) * PieRadius
local Mid = {}
for k, v in ipairs(SeriesData) do
local Arc = 0 -- default is short arc (<= 180 degrees)
if (v[2] / Total * TwoPi) > math.pi then
Arc = 1 -- long arc
end
Mid[k] = Radian + ((v[2] / Total * TwoPi) / 2 * iif(Sweep == 0, 1, -1))
-- adjust X and Y for explode
local DX, DY = 0, 0
if Parms["Explode"] == nil or (type(Parms["Explode"]) == "number" and k > Parms["Explode"]) then
-- no explode for this segment
else
DX = math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)
DY = math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)
end
Radian = Radian + (v[2] / Total * TwoPi * iif(Sweep == 0, 1, -1))
local NextOuterX = math.cos(Radian) * PieRadius
local NextOuterY = math.sin(Radian) * PieRadius
t = " <path id=\"segment" .. k .. "\" class=\"series" .. k .. "\" d=\"M " .. round(0 + InnerX + DX, 2) .. ", " .. -round(0 + InnerY + DY, 2)
.. " l " .. round(OuterX - InnerX, 2) .. ", " .. -round(OuterY - InnerY, 2)
.. " a " .. PieRadius .. ", " .. PieRadius .. " 0 " .. Arc .. " " .. Sweep .. " " .. round(NextOuterX - OuterX, 2) .. ", " .. -round(NextOuterY - OuterY, 2)
if Parms["DoughnutHole"] == nil then
t = t .. " z\" />"
else
local NextInnerX = math.cos(Radian) * InnerRadius
local NextInnerY = math.sin(Radian) * InnerRadius
t = t .. " l " .. round(NextInnerX - NextOuterX, 2).. ", " .. -round(NextInnerY - NextOuterY, 2)
.. " a " .. InnerRadius .. ", " .. InnerRadius .. " 0 " .. Arc .. " " .. iif(Sweep == 0, 1, 0) .. " " .. round(InnerX - NextInnerX, 2) .. ", " .. -round(InnerY - NextInnerY, 2) .. "\" />"
InnerX = NextInnerX
InnerY = NextInnerY
end
table.insert(r, t)
OuterX = NextOuterX
OuterY = NextOuterY
end
table.insert(r, " </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 = math.cos(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)
DY = math.sin(Mid[k]) * (PieRadius * Parms["ExplodeRadius"] / 100)
end
local TextX = math.cos(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100)
local TextY = math.sin(Mid[k]) * (PieRadius * Parms["SegmentTextRadius"] / 100)
if TextY < 0 then
TextY = TextY - FontSiz.LegendText
end
t = " <text id=\"segment" .. k .. "text\""
.. " x=\"" .. round(TextX + DX, 2) .. "\" y=\"" .. -round(TextY + DY, 2) .. "\""
t = t .. " text-anchor=\"" .. iif(TextX < 0, "end", "start") .. "\">"
local tt = ""
if string.find(Parms["SegmentText"], KeyWords["text"], 1, true) ~= nil then
tt = SeriesText[k]
end
if string.find(Parms["SegmentText"], KeyWords["value"], 1, true) ~= nil then
tt = tt .. iif(string.len(tt) > 0, " ", "") .. v[2]
end
if string.find(Parms["SegmentText"], KeyWords["percent"], 1, true) ~= nil then
tt = tt .. iif(string.len(tt) > 0, " ", "") .. round(v[2] / Total * 100, 0) .. "%"
end
t = t .. tt
table.insert(r, t .. "</text>")
end
table.insert(r, " </g>")
table.insert(r, " ")
end
end
function codeAxes()
if Parms["Debug"] ~= nil and string.find(Parms["Debug"], "pos") ~= nil then
-- show positions tables
table.insert(r, "<!--")
listTable(Mult, " ", "Mult")
listTable(FontSiz, " ", "FontSiz")
table.sort(Pos)
listTable(Pos, " ", "Pos")
table.sort(Siz)
listTable(Siz, " ", "Siz")
table.insert(r, "-->")
end
-- axis styles
table.insert(r, " <!-- == Axis Styles == -->")
table.insert(r, " ")
table.insert(r, " <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, " }")
if DoYAxis2 then
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, " }")
end
if Parms["XAxisArrows"] ~= nil or Parms["YAxisArrows"] ~= nil then
table.insert(r, " .axis-arrow {")
table.insert(r, " stroke-width: " .. BaseLineWidth .. ";")
table.insert(r, " fill: black;")
table.insert(r, " stroke-linejoin: miter;")
table.insert(r, " }")
if Parms["XAxisArrows"] ~= nil then
-- marker stroke and fill color is the same as the axis
table.insert(r, " .axis-arrow-x {")
table.insert(r, " stroke: black;")
table.insert(r, " fill: black;")
table.insert(r, " }")
end
if Parms["YAxisArrows"] ~= nil then
-- marker stroke and fill color is the same as the axis
table.insert(r, " .axis-arrow-y {")
table.insert(r, " stroke: " .. Parms["YAxisColor"] .. ";")
table.insert(r, " fill: " .. Parms["YAxisColor"] .. ";")
table.insert(r, " }")
end
end
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, " ")
table.insert(r, " <!-- == Axis Marks == -->")
table.insert(r, " ")
if DoHorizontal then
codeAxisMarks('x',
'y',
-1,
Pos.XAxis.Line,
-Parms["XMax"],
Siz.ChartHeight)
codeAxisMarks('y',
'x',
0,
Pos.YAxis.Line,
Parms["YMin"],
Siz.ChartWidth)
if DoYAxis2 then
codeAxisMarks('y2',
'x',
-1,
Pos.YAxis2.Line,
Parms["Y2Min"],
Siz.ChartWidth)
end
else
codeAxisMarks('x',
'x',
0,
Pos.XAxis.Line,
Parms["XMin"],
Siz.ChartWidth)
codeAxisMarks('y',
'y',
-1,
Pos.YAxis.Line,
-Parms["YMax"],
Siz.ChartHeight)
if DoYAxis2 then
codeAxisMarks('y2',
'y',
0,
Pos.YAxis2.Line,
-Parms["Y2Max"],
Siz.ChartHeight)
end
end
if Parms["XAxisArrows"] ~= nil or Parms["YAxisArrows"] ~= nil then
table.insert(r, " <!-- == Axis Arrows == -->")
table.insert(r, " ")
local ArrowSize = BaseLineWidth * 12
table.insert(r, " <defs>")
-- start arrow, for axes
if Parms["XMin"] < 0 or Parms["YMin"] < 0 then
table.insert(r, " <g id=\"axis-arrow-start-shape\">")
t = round(ArrowSize / 2, 2)
table.insert(r, " <polygon points=\"" .. t .. "," .. -t .. " " .. 0 .. "," .. 0 .. " " .. t .. "," .. t .. " " .. -t .. "," .. 0 .. "\"/>")
table.insert(r, " </g>")
end
table.insert(r, " <g id=\"axis-arrow-end-shape\">")
t = round(ArrowSize / 2, 2)
table.insert(r, " <polygon points=\"" .. -t .. "," .. -t .. " " .. 0 .. "," .. 0 .. " " .. -t .. "," .. t .. " " .. t .. "," .. 0 .. "\"/>")
table.insert(r, " </g>")
table.insert(r, " ")
if Parms["XAxisArrows"] ~= nil then
if Parms["XMin"] < 0 then
l = " <marker id=\"axis-arrow-x-start\""
l = l .. " class=\"axis-arrow axis-arrow-x\""
l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""
.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""
.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""
.. " overflow=\"visible\""
.. " orient=\"auto\""
.. " markerUnits=\"userSpaceOnUse\">"
table.insert(r, l)
table.insert(r, " <use xlink:href=\"#axis-arrow-start-shape\"/>")
table.insert(r, " </marker>")
end
l = " <marker id=\"axis-arrow-x-end\""
l = l .. " class=\"axis-arrow axis-arrow-x\""
l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""
.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""
.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""
.. " overflow=\"visible\""
.. " orient=\"auto\""
.. " markerUnits=\"userSpaceOnUse\">"
table.insert(r, l)
table.insert(r, " <use xlink:href=\"#axis-arrow-end-shape\"/>")
table.insert(r, " </marker>")
end
if Parms["YAxisArrows"] ~= nil then
if Parms["YMin"] < 0 then
l = " <marker id=\"axis-arrow-y-start\""
l = l .. " class=\"axis-arrow axis-arrow-y\""
l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""
.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""
.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""
.. " overflow=\"visible\""
.. " orient=\"auto\""
.. " markerUnits=\"userSpaceOnUse\">"
table.insert(r, l)
table.insert(r, " <use xlink:href=\"#axis-arrow-start-shape\"/>")
table.insert(r, " </marker>")
end
l = " <marker id=\"axis-arrow-y-end\""
l = l .. " class=\"axis-arrow axis-arrow-y\""
l = l .. " viewBox=\"0 0 " .. round(ArrowSize, 2) .. " " .. round(ArrowSize, 2) .. "\""
.. " markerWidth=\"" .. round(ArrowSize, 2) .. "\""
.. " markerHeight=\"" .. round(ArrowSize, 2) .. "\""
.. " overflow=\"visible\""
.. " orient=\"auto\""
.. " markerUnits=\"userSpaceOnUse\">"
table.insert(r, l)
table.insert(r, " <use xlink:href=\"#axis-arrow-end-shape\"/>")
table.insert(r, " </marker>")
end
table.insert(r, " </defs>")
table.insert(r, " ")
end
table.insert(r, " <!-- == Axis Lines == -->")
table.insert(r, " ")
if DoHorizontal then
codeAxisLine('x', 'y', Pos.XAxis.Line, -Parms["XMin"], -Siz.ChartHeight)
codeAxisLine('y', 'x', Pos.YAxis.Line, Parms["YMin"], Siz.ChartWidth)
if DoYAxis2 then
codeAxisLine('y2', 'x', Pos.YAxis2.Line, Parms["Y2Min"], Siz.ChartWidth)
end
else
codeAxisLine('x', 'x', Pos.XAxis.Line, Parms["XMin"], Siz.ChartWidth)
codeAxisLine('y', 'y', Pos.YAxis.Line, -Parms["YMin"], -Siz.ChartHeight)
if DoYAxis2 then
codeAxisLine('y2', 'y', Pos.YAxis2.Line, -Parms["Y2Min"], -Siz.ChartHeight)
end
end
table.insert(r, " ")
Format = {}
if #GroupText == 0 then
Format.x = (Parms["XMax"] >= 10000)
if Parms["XAxisValueFormat"] ~= nil then
Format.x = not (Parms["XAxisValueFormat"] == 'none')
end
end
Format.y = (Parms["YMax"] >= 10000)
if Parms["YAxisValueFormat"] ~= nil then
Format.y = not (Parms["YAxisValueFormat"] == 'none')
end
if DoYAxis2 then
Format.y2 = (Parms["Y2Max"] >= 10000)
if Parms["YAxis2ValueFormat"] ~= nil then
Format.y2 = not (Parms["YAxis2ValueFormat"] == 'none')
end
end
table.insert(r, " <!-- == Axis Values == -->")
table.insert(r, " ")
if DoHorizontal then
codeAxisValues('x', 'y',
Parms["XAxisValueStep"],
-1, -1,
Parms["XMin"], Parms["XMax"],
FontSiz["XAxisValues"] / 3, Pos.XAxis.Values,
Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"], Parms["XAxisValueAbsolute"],
Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"],
Format.x)
codeAxisValues('y', 'x',
Parms["YAxisValueStep"],
0, 1,
Parms["YMin"], Parms["YMax"],
0, Pos.YAxis.Values,
Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"], Parms["YAxisValueAbsolute"],
Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"],
Format.y)
if DoYAxis2 then
codeAxisValues('y2', 'x',
Parms["YAxis2ValueStep"],
-1, 1,
Parms["Y2Min"], Parms["Y2Max"],
0, Pos.YAxis2.Values,
Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"], Parms["YAxis2ValueAbsolute"],
Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"],
Format.y2)
end
else
codeAxisValues('x', 'x',
Parms["XAxisValueStep"],
0, 1,
Parms["XMin"], Parms["XMax"],
0, Pos.XAxis.Values,
Parms["XAxisValueMultiplier"], Parms["XAxisValueRound"], Parms["XAxisValueAbsolute"],
Parms["XAxisValuePrefix"], Parms["XAxisValueSuffix"],
Format.x)
codeAxisValues('y', 'y',
Parms["YAxisValueStep"],
-1, -1,
Parms["YMin"], Parms["YMax"],
FontSiz["YAxisValues"] / 3, Pos.YAxis.Values,
Parms["YAxisValueMultiplier"], Parms["YAxisValueRound"], Parms["YAxisValueAbsolute"],
Parms["YAxisValuePrefix"], Parms["YAxisValueSuffix"],
Format.y)
if DoYAxis2 then
codeAxisValues('y2', 'y',
Parms["YAxis2ValueStep"],
0, -1,
Parms["Y2Min"], Parms["Y2Max"],
FontSiz["YAxis2Values"] / 3, Pos.YAxis2.Values,
Parms["YAxis2ValueMultiplier"], Parms["YAxis2ValueRound"], Parms["YAxis2ValueAbsolute"],
Parms["YAxis2ValuePrefix"], Parms["YAxis2ValueSuffix"],
Format.y2)
end
end
table.insert(r, " <!-- End Axis Chart Translate== -->")
table.insert(r, " </g>")
table.insert(r, " ")
-- axis titles are always outside the chart, so are outside the chart translate
if Parms["XAxisTitle"] ~= nil
or Parms["YAxisTitle"] ~= nil
or (DoYAxis2 and Parms["YAxis2Title"] ~= nil) then
table.insert(r, " <!-- == Axis Titles == -->")
table.insert(r, " ")
if DoHorizontal then
if Parms["XAxisTitle"] ~= nil then
codeAxisTitle('x', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5))
end
if Parms["YAxisTitle"] ~= nil then
codeAxisTitle('y', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5))
end
if DoYAxis2 and Parms["YAxis2Title"] ~= nil then
codeAxisTitle('y2', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5))
end
else
if Parms["XAxisTitle"] ~= nil then
codeAxisTitle('x', 'x', Siz.Space.Left + (Siz.ChartWidth * 0.5))
end
if Parms["YAxisTitle"] ~= nil then
codeAxisTitle('y', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5))
end
if DoYAxis2 and Parms["YAxis2Title"] ~= nil then
codeAxisTitle('y2', 'y', Siz.Space.Top + (Siz.ChartHeight * 0.5))
end
end
table.insert(r, " ")
end
end
function codeAxisMarks(AxisName,
ChangeDim,
MarkOffset,
AxisLinePos,
AxisStart,
LineLength)
-- AxisName, text: the name of the axis being coded, "x", "y" or "y2"
-- ChangeDim, text: 'x' or 'y' - the dimension the axis actually changes in
-- MarkOffset, numeric: direction of mark start away from the line, -1 or 0
-- AxisLinePos, numeric: position of the axis, ie: distance of the line from the chart origin point
-- AxisStart, numeric: position of the start of the axis line
-- LineLength, numeric: the length of the axis line
local OtherDim = "y"
if ChangeDim == "y" then
OtherDim = "x"
end
local AxisParmName = string.upper(AxisName) .. "Axis"
if AxisName == "y2" then
AxisParmName = "YAxis2"
end
local MarkGapDim = "width"
local MarkSizDim = "height"
if ChangeDim == "y" then
MarkGapDim = "height"
MarkSizDim = "width"
end
if AxisName == "x" and #GroupText ~= 0 then
-- no marks for x axis with groups
else
table.insert(r, " <defs>")
table.insert(r, " <pattern id=\"" .. AxisName .. "-axismark-main\""
.. " " .. MarkGapDim .. "=\"" .. round(Parms[AxisParmName .. "ValueStep"] * Mult[AxisName], 2) .. "\""
.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" " .. ChangeDim .. "2=\"0\" " .. OtherDim .. "2=\"" .. Siz.AxisMark .. "\" class=\"axismark-main\"/>")
table.insert(r, " </pattern>")
if Parms[AxisParmName .. "Mark2Step"] ~= nil then
table.insert(r, " <pattern id=\"" .. AxisName .. "-axismark-second\""
.. " " .. MarkGapDim .. "=\"" .. round(Parms[AxisParmName .. "Mark2Step"] * Mult[AxisName], 2) .. "\""
.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark2 .. "\" patternUnits=\"userSpaceOnUse\">")
table.insert(r, " <line x1=\"0\" y1=\"0\" " .. ChangeDim .. "2=\"0\" " .. OtherDim .. "2=\"" .. Siz.AxisMark2 .. "\" class=\"axismark-second\"/>")
table.insert(r, " </pattern>")
end
table.insert(r, " </defs>")
table.insert(r, " ")
if Parms[AxisParmName .. "Mark2Step"] ~= nil then
table.insert(r, " <rect id=\"" .. AxisName .. "-axismark2\""
.. " " .. ChangeDim .. "=\"" .. round(AxisStart * Mult[AxisName], 2) .. "\""
.. " " .. OtherDim .. "=\"" .. round(AxisLinePos + (Siz.AxisMark2 * MarkOffset), 2) .. "\""
.. " " .. MarkGapDim .. "=\"" .. LineLength + BaseLineWidth .. "\""
.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark2 .. "\""
.. " fill=\"url(#" .. AxisName .. "-axismark-second)\"/>")
end
table.insert(r, " <rect id=\"" .. AxisName .. "-axismark\""
.. " " .. ChangeDim .. "=\"" .. round(AxisStart * Mult[AxisName], 2) .. "\""
.. " " .. OtherDim .. "=\"" .. round(AxisLinePos + (Siz.AxisMark * MarkOffset), 2) .. "\""
.. " " .. MarkGapDim .. "=\"" .. LineLength + BaseLineWidth .. "\""
.. " " .. MarkSizDim .. "=\"" .. Siz.AxisMark .. "\""
.. " fill=\"url(#" .. AxisName .. "-axismark-main)\"/>")
table.insert(r, " ")
end
end
function codeAxisLine(AxisName,
ChangeDim,
AxisLinePos,
AxisStart, LineLength)
local OtherDim = "y"
if ChangeDim == "y" then
OtherDim = "x"
end
local l = " <line id=\"" .. AxisName .. "-axis\""
if Parms[string.upper(AxisName) .. "AxisArrows"] ~= nil then
if Parms[string.upper(AxisName) .. "Min"] < 0 then
l = l .. " marker-start=\"url(#axis-arrow-" .. AxisName .. "-start)\""
end
l = l .. " marker-end=\"url(#axis-arrow-" .. AxisName .. "-end)\""
end
l = l .. " " .. ChangeDim .. "1=\"" .. round(AxisStart * Mult[AxisName], 2) .. "\""
.. " " .. OtherDim .. "1=\"" .. round(AxisLinePos, 2) .. "\""
.. " " .. ChangeDim .. "2=\"" .. round((AxisStart * Mult[AxisName]) + LineLength, 2) .. "\""
.. " " .. OtherDim .. "2=\"" .. round(AxisLinePos, 2) .. "\" class=\"axisline-" .. AxisName .. "\"/>"
table.insert(r, l)
end
function codeAxisValues(AxisName,
ChangeDim,
ValueStep,
MarkOffset,
PosChangeSign,
ValueMin, ValueMax,
TextShift, OtherValuesPos,
ValueMultiplier, ValueRound, ValueAbsolute,
Prefix, Suffix,
Format)
-- AxisName, text: the name of the axis being coded, "x", "y" or "y2"
-- Parms for location
-- ChangeDim, text: 'x' or 'y' - the dimension the axis actually changes in
-- ValueStep, numeric: the step between values (and major marks)
-- MarkOffset, numeric: direction of mark start away from the line, -1 or 0
-- PosChangeSign, numeric: the position direction for +ve changes in value, 1 or -1
-- ValueMin, numeric: the minimum value shown
-- ValueMax, numeric: the maximum value shown
-- TextShift, numeric: amount to shift text, to get it centered with its marks
-- OtherValuesPos, numeric: alignment position for all values
-- Parms for values format
-- ValueMultiplier, numeric
-- ValueRound, numeric
-- ValueAbsulute
-- Prefix, text
-- Suffix, text
-- Format, boolean
local AxisParmName = string.upper(AxisName) .. "Axis"
if AxisName == "y2" then
AxisParmName = "YAxis2"
end
local Anchor = "middle"
local RotateText = ""
if ChangeDim == "y" then
if MarkOffset == -1 then
-- text is left of line
Anchor = "end"
else
-- text is right of line
Anchor = "start"
end
end
if Parms[AxisParmName .. "ValueRotate"] ~= nil then
RotateText = " transform=\"rotate(" .. Parms[AxisParmName .. "ValueRotate"] .. ", "
if ChangeDim == "x" then
Anchor = "start"
if Parms[AxisParmName .. "ValueRotate"] < 0 and MarkOffset == 0 then
Anchor = "end"
elseif Parms[AxisParmName .. "ValueRotate"] >= 0 and MarkOffset == -1 then
Anchor = "end"
end
end
end
local Format = (Parms[string.upper(AxisName) .. "Max"] >= 10000)
if Parms[AxisParmName .. "ValueFormat"] ~= nil then
Format = not (Parms[AxisParmName .. "ValueFormat"] == 'none')
end
local l = ""
local Position = 0
if AxisName ~= 'x' or #GroupText == 0 then
-- numeric values
table.insert(r, " <g id=\"" .. AxisName .. "-axis-values\""
.. " class=\"axisnumber-" .. AxisName .. "\""
.. " text-anchor=\"" .. Anchor .. "\""
.. " transform=\"translate("
.. iif(ChangeDim == "x", 0, round(OtherValuesPos, 2)) .. ", "
.. iif(ChangeDim == "x", round(OtherValuesPos, 2), TextShift) .. ")\""
.. ">")
local ValueStart = 0
if ValueMin ~= 0 then
ValueStart = math.ceil(ValueMin / ValueStep) * ValueStep
end
local Value = ValueStart
while Value <= ValueMax do
Position = Value * Mult[AxisName] * PosChangeSign
l = " <text " .. ChangeDim .. "=\"" .. round(Position, 2) .. "\""
if ChangeDim == 'x' then
if string.len(RotateText) > 0 then
l = l .. RotateText .. round(Position, 2) .. ", 0)\""
end
else
if string.len(RotateText) > 0 then
l = l .. RotateText .. "0, " .. round(Position - TextShift, 2) .. ")\""
end
end
l = l .. ">"
l = l .. Prefix
local v = Value
if ValueAbsolute ~= nil then
v = math.abs(v)
end
if Format then
l = l .. mw.getContentLanguage():formatNum(round(v * ValueMultiplier, ValueRound))
else
l = l .. round(v * ValueMultiplier, ValueRound)
end
l = l .. Suffix .. "</text>"
table.insert(r, l)
Value = Value + ValueStep
end
else
-- group name values
table.insert(r, " <g id=\"" .. AxisName .. "-axis-values\""
.. " class=\"axisnumber-" .. AxisName .. "\""
.. " text-anchor=\"" .. Anchor .. "\""
.. " transform=\"translate("
.. iif(ChangeDim == "x", 0, round(OtherValuesPos, 2)) .. ", "
.. iif(ChangeDim == "x", round(OtherValuesPos, 2), TextShift) .. ")\""
.. ">")
for k, v in ipairs(GroupText) do
Position = ((iif(DoGroupsTopDown, #GroupText - k, k - 1) * GroupWidth)
+ (GroupWidth / 2)) * PosChangeSign
l = " <text " .. ChangeDim .. "=\"" .. round(Position, 2) .. "\""
if ChangeDim == 'x' then
if string.len(RotateText) > 0 then
l = l .. RotateText .. round(Position, 2) .. ", 0)\""
end
else
if string.len(RotateText) > 0 then
l = l .. RotateText .. "0, " .. round(Position - TextShift, 2) .. ")\""
end
end
l = l .. ">" .. v .. "</text>"
table.insert(r, l)
end
end
table.insert(r, " </g>")
table.insert(r, " ")
end
function codeAxisTitle(AxisName,
ChangeDim,
TitleCentre)
local OtherDim = "y"
if ChangeDim == "y" then
OtherDim = "x"
end
local AxisParmName = string.upper(AxisName) .. "Axis"
if AxisName == "y2" then
AxisParmName = "YAxis2"
end
local l = " <text id=\"title-" .. AxisName .. "\" class=\"axistitle-" .. AxisName .. "\""
.. " " .. ChangeDim .. "=\"" .. round(TitleCentre, 2) .. "\""
.. " " .. OtherDim .. "=\"" .. round(Pos[AxisParmName]["Title"], 2) .. "\""
if ChangeDim == 'y' then
l = l .. " transform = \"rotate(-90, "
.. round(Pos[AxisParmName]["Title"], 2)
.. ", "
.. round(TitleCentre, 2)
.. ")\""
end
l = l .. " text-anchor=\"middle\">"
.. Parms[AxisParmName .. "Title"]
.. "</text>"
table.insert(r, l)
end
function commonBottom()
if Parms["LegendType"] == KeyWords["none"]
and Parms["Title"] == nil
and Parms["Footnote"] == nil
and #ChartText <= 0 then
-- no common-element styles
else
table.insert(r, " <!-- == Common-element Styles == -->")
table.insert(r, " ")
table.insert(r, " <style type=\"text/css\"> <![CDATA[")
if 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 #ChartText > 0 then
table.insert(r, " .charttext {")
table.insert(r, " font-size: " .. FontSiz.ChartText .. "px;")
table.insert(r, " }")
end
if Parms["Title"] ~= nil then
table.insert(r, " .titletext {")
table.insert(r, " font-size: " .. FontSiz.Title .. "px;")
table.insert(r, " }")
end
if Parms["Footnote"] ~= nil then
table.insert(r, " .footnotetext {")
table.insert(r, " font-size: " .. FontSiz.Footnote .. "px;")
table.insert(r, " }")
end
table.insert(r, " ]]>")
table.insert(r, " </style>")
table.insert(r, " ")
end
-- legend
if Parms["LegendType"] == KeyWords["none"] then
-- no legend
else
table.insert(r, " <!-- == Legend == -->")
table.insert(r, " ")
if Parms["LegendSVG"] ~= nil then
-- replace all of legend with user-supplied SVG code
table.insert(r, " " .. Parms["LegendSVG"])
table.insert(r, " ")
else
tr = " <g id=\"legend\" transform=\"translate("
if Parms["LegendType"] == KeyWords["horizontal"] then
-- horizontal legend
if Parms["LegendX"] ~= nil then
if DoHorizontal then
tr = tr .. round(Pos.YAxis.Zero + (Parms["LegendY"] * Mult.y), 2) .. ""
.. ", " .. round(Pos.XAxis.Zero - (Parms["LegendX"] * Mult.x), 2)
else
tr = tr .. round(Pos.XAxis.Zero + (Parms["LegendX"] * Mult.x), 2) .. ""
.. ", " .. round(Pos.YAxis.Zero - (Parms["LegendY"] * Mult.y), 2)
end
else
tr = tr .. round(Siz.Space.Left, 2) .. ", " .. round(Pos.Legend, 2)
end
table.insert(r, tr .. ")\">")
table.insert(r, " <rect id=\"legend-background\" class=\"legendbox\" x=\"0\" y=\"0\""
.. " width=\"" .. Siz.Legend.Width .. "\""
.. " height=\"" .. Siz.Legend.Height .. "\"/>")
local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin
for k, v in ipairs(SeriesText) do
table.insert(r, " ")
codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Marker[k])
PosX = PosX + Siz.Legend.ElementWidth
if math.fmod(k, LegendElementsInWidth) == 0 then
-- new line of legend elements
PosX = Siz.ChartMargin
PosY = PosY + Siz.Legend.ElementHeight
end
end
else
-- vertical legend
if Parms["LegendX"] ~= nil then
if DoHorizontal then
tr = tr .. round(Pos.YAxis.Zero + (Parms["LegendY"] * Mult.y), 2)
.. ", " .. round(Pos.XAxis.Zero - (Parms["LegendX"] * Mult.x), 2)
else
tr = tr .. round(Pos.XAxis.Zero + (Parms["LegendX"] * Mult.x), 2)
.. " " .. round(Pos.YAxis.Zero - (Parms["LegendY"] * Mult.y), 2)
end
else
tr = tr .. round(Pos.Legend, 2) .. ", " .. round(Siz.Space.Top + (0.1 * Siz.ChartHeight), 2)
end
table.insert(r, tr .. ")\">")
table.insert(r, " <rect id=\"legend-background\" class=\"legendbox\" x=\"0\" y=\"0\""
.. " width=\"" .. Siz.Legend.Width .. "\""
.. " height=\"" .. Siz.Legend.Height .. "\"/>")
local PosX, PosY = Siz.ChartMargin, Siz.ChartMargin
for k, v in ipairs(SeriesText) do
table.insert(r, " ")
codeLegendElement((SType[k] == KeyWords["line"] and not DoArea), k, PosX, PosY, Siz.ChartMargin, Siz.Legend.Text, v, Marker[k])
PosY = PosY + Siz.Legend.ElementHeight
end
end
table.insert(r, " </g>")
table.insert(r, " ")
end
end
-- chart texts
if #ChartText > 0 then
table.insert(r, " <!-- == Chart Texts == -->")
table.insert(r, " ")
table.insert(r, " <g id=\"charttexts\" class=\"charttext\">")
for i = 1, #ChartText do
if ChartText[i] ~= nil then
if DoHorizontal then
table.insert(r, " <text "
.. "x=\"" .. round(Pos.YAxis.Zero + (Parms["ChartText" .. i .. "Y"] * Mult.y), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.XAxis.Zero - (Parms["ChartText" .. i .. "X"] * Mult.x), 2) .. "\">"
.. ChartText[i] .. "</text>")
else
table.insert(r, " <text "
.. "x=\"" .. round(Pos.XAxis.Zero + (Parms["ChartText" .. i .. "X"] * Mult.x), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["ChartText" .. i .. "Y"] * Mult.y), 2) .. "\">"
.. ChartText[i] .. "</text>")
end
end
end
table.insert(r, " </g>")
table.insert(r, " ")
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, "left", "middle") .. "\""
if Parms["TitleX"] ~= nil then
if DoHorizontal then
tr = tr .. " " .. "x=\"" .. round(Pos.YAxis.Zero + (Parms["TitleY"] * Mult.y), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.XAxis.Zero - (Parms["TitleX"] * Mult.x), 2) .. "\">"
else
tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms["TitleX"] * Mult.x), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["TitleY"] * Mult.y), 2) .. "\">"
end
else
tr = tr .. " x=\"" .. round(Siz.Space.Left + Siz.ChartWidth * 0.5, 2) .. "\""
.. " y=\"" .. round(Pos.Title, 2) .. "\">"
end
table.insert(r, tr .. Parms["Title"] .. "</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, "left", "end") .. "\""
if Parms["FootnoteX"] ~= nil then
if DoHorizontal then
tr = tr .. " " .. "x=\"" .. round(Pos.YAxis.Zero + (Parms["FootnoteY"] * Mult.y), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.XAxis.Zero - (Parms["FootnoteX"] * Mult.x), 2) .. "\">"
else
tr = tr .. " " .. "x=\"" .. round(Pos.XAxis.Zero + (Parms["FootnoteX"] * Mult.x), 2) .. "\""
.. " " .. "y=\"" .. round(Pos.YAxis.Zero - (Parms["FootnoteY"] * Mult.y), 2) .. "\">"
end
else
tr = tr .. " x=\"" .. round(ImageWidth - Siz.ImagePadding.Right, 2) .. "\""
.. " y=\"" .. round(Pos.Footnote, 2) .. "\">"
end
table.insert(r, tr .. Parms["Footnote"] .. "</text>")
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, 2)
l = " <polyline id=\"legend-line" .. SeriesNumber .. "\" class=\"series-lines-general series" .. SeriesNumber .. "\""
.. " points=\""
.. round(PosX, 2) .. "," .. lineY .. " "
.. round(PosX + (TextSize * 1), 2) .. "," .. lineY .. " "
.. round(PosX + (TextSize * 2), 2) .. "," .. lineY .. "\""
if Marker ~= nil and Marker ~= 0 then
l = l .. " marker-mid=\"url(#series" .. SeriesNumber .. "marker)\""
end
table.insert(r, l .. "/>")
table.insert(r, " <text id=\"legend-text" .. SeriesNumber .. "\" class=\"legendtext\""
.. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 2) .. "\""
.. " y=\"" .. round(PosY + TextSize, 2) .. "\">"
.. Text .. "</text>")
else
table.insert(r, " <rect id=\"legend-area" .. SeriesNumber .. "\" class=\"series-areas-general series" .. SeriesNumber .. "\""
.. " x=\"" .. round(PosX, 2) .. "\""
.. " y=\"" .. round(PosY, 2) .. "\""
.. " width=\"" .. TextSize * 2 .. "\""
.. " height=\"" .. TextSize .. "\"/>")
table.insert(r, " <text id=\"legend-text" .. SeriesNumber .. "\" class=\"legendtext\""
.. " x=\"" .. round(PosX + (TextSize * 2) + Padding, 2) .. "\""
.. " y=\"" .. round(PosY + TextSize, 2) .. "\">"
.. Text .. "</text>")
end
end
function outputDebugInfo()
if Parms["Debug"] ~= nil and string.find(Parms["Debug"], KeyWords["parms"]) ~= nil then
-- list all parameters
table.insert(r, " All Parameters :")
for k, v in pairs(Args) do
table.insert(r, " " .. k .. "=" .. v)
end
table.insert(r, " ")
-- list all recognised parameters sorted by key
table.insert(r, " Parameters :")
for k, v in spairs(Parms) do
table.insert(r, " " .. k .. "=" .. v .. " (" .. type(v) .. ")")
end
table.insert(r, " ")
if string.find(Parms["Debug"], "parmsstop") ~= nil then
return false
end
end
if Parms["Debug"] ~= nil and string.find(Parms["Debug"], "tables") ~= nil then
-- list contents of internal tables
if DoStack or DoStack100 then
listTable(OriginalData, " ", "OriginalData")
end
listTable(SeriesData, " ", "SeriesData")
listTable(SType, " ", "SType")
listTable(YAxis2, " ", "YAxis2")
listTable(Labels, " ", "Labels")
listTable(Color, " ", "Color")
listTable(LineShow, " ", "LineShow")
listTable(LineWidth, " ", "LineWidth")
listTable(LineDash, " ", "LineDash")
listTable(Marker, " ", "Marker")
listTable(MarkerFill, " ", "MarkerFill")
listTable(MarkerSize, " ", "MarkerSize")
listTable(FillPattern, " ", "FillPattern")
listTable(FillPatternColor, " ", "FillPatternColor")
listTable(SeriesText, " ", "SeriesText")
listTable(GroupText, " ", "GroupText")
listTable(ChartText, " ", "ChartText")
-- listTable(FontSiz, " ", "FontSiz")
-- listTable(Siz, " ", "Siz")
-- listTable(Pos, " ", "Pos")
-- listTable(Mult, " ", "Mult")
-- listTable(Adjusts, " ", "Adjusts")
table.insert(r, " ")
if string.find(Parms["Debug"], "tablesstop") ~= nil then
return false
end
end
table.insert(r, "----------")
table.insert(r, " ")
return true
end
function listTable(Tab, Indent, Title)
-- lists all contents of a table, iterating over all sub-tables
if Title ~= nil then
table.insert(r, " " .. Title .. ":")
end
for k, v in pairs(Tab) do
if type(v) == "table" then
table.insert(r, Indent .. k .. ":")
listTable(v, Indent .. " ", nil)
else
table.insert(r, Indent .. k .. ": " .. v .. " (" .. type(v) .. ")")
end
end
end
function unknownParameters(knowns, regexps)
-- outputs a table of parameters either not in knowns, or not matching a pattern in regexps
-- knowns - a table where the values are the known parameters
-- regexps - a table where the values are lua patterns that parameters may match
-- modified from module at en.wikipedia.org/wiki/Module:Check_for_unknown_parameters
local knownparms = {}
local output = {}
-- create the lists of known parms and regular expressions
for k, v in spairs(knowns) do
v = trim(v)
knownparms[v] = 1
end
for k, v in pairs(regexps) do
regexps[k] = '^' .. v .. '$'
end
-- check each entry in Args
for k, v in pairs(Args) do
if type(k) == 'string' and knownparms[k] == nil then
local knownflag = false
for i, regexp in ipairs(regexps) do
if mw.ustring.match(k, regexp) then
knownflag = true
break
end
end
if not knownflag then
local vlen = mw.ustring.len(v)
v = mw.ustring.sub(v, 1, (vlen < 25) and vlen or 25)
table.insert(output, k .. "=" .. v .. ((vlen >= 25) and ' ...' or ''))
end
elseif type(k) == 'number' and knownparms[tostring(k)] == nil then
local vlen = mw.ustring.len(v)
v = mw.ustring.sub(v, 1, (vlen < 25) and vlen or 25)
table.insert(output, k .. "=" .. v .. ((vlen >= 25) and ' ...' or ''))
end
end
return output
end
-- utility functions
function iif(test, tret, fret)
-- if test is true, returns tret if defined, otherwise returns true
-- if test is false, returns fret if defined, otherwise returns false
-- note that this function cannot be used to avoid evaluating either tret or fret, as they are both evaluated in the function call
if test then
if tret == nil then
return true
else
return tret
end
end
if fret == nil then
return false
end
return fret
end
function trim(s)
-- the match this pattern returns is the string with any spaces (%s) at the start(^) and end ($) not included
return s:match('^%s*(.-)%s*$')
end
function round(x, p)
-- round number x to precision p
-- p > 0 rounds to p decimal places, eg: round(4.57, 1) = 4.6
-- p = 0 (or not given) rounds to nearest integer, eg: round(6.6) = 7, round(6.5) = 6
-- p < 0 rounds to p places above zero, eg: round(147, -1) = 150
local res
if type(x) ~= "number" then
return nil
end
if type(p) == "number" then
-- any decimal places in p are ignored
p = math.floor(p)
else
p = nil
end
if p == nil or p == 0 then
return math.floor(x + 0.5)
else
res = x * 10^p
res = math.floor(res + 0.5)
res = res * 10^-p
return res
end
end
function decPlaces(val)
-- returns the number of decimal places in a numeric value, or a string convertible to a number
val = tonumber(val)
if val == nil then
return 0
end
val = tostring(val)
local point = string.find(val, ".")
if point == 0 then
return 0
end
return string.len(val) - point
end
function copyTable(from, to)
-- copies a table to another table
local k, v
for k, v in ipairs(from) do
if type(v) == "table" then
to[k] = {}
copyTable(v, to[k])
else
to[k] = v
end
end
end
function spairs(t, order)
-- returns an iterator function that in turn returns table t in the order of the keys
-- sort is done using an optional order function
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
return {
[KeyWords.barChart] = barChart,
[KeyWords.lineChart] = lineChart,
[KeyWords.scatterChart] = scatterChart,
[KeyWords.mixedChart] = mixedChart,
[KeyWords.pieChart] = pieChart,
}