Module:Graph: verschil tussen versies
Uiterlijk
Verwijderde inhoud Toegevoegde inhoud
updated from de.wikipedia.org |
update vanuit de:Modul:Graph |
||
Regel 2: | Regel 2: | ||
-- This way all wiki languages can stay in sync. Thank you! |
-- This way all wiki languages can stay in sync. Thank you! |
||
-- |
-- |
||
-- Version History: |
-- Version History (_PLEASE UPDATE when modifying anything_): |
||
-- 2019-11-08 Apply color-inversion-friendliness to legend title, labels, and xGrid |
|||
-- 2016-01-09 _PLEASE UPDATE when modifying anything_ |
|||
-- 2019-01-24 Allow comma-separated lists to contain values with commas |
|||
-- 2016-01-28 For maps, always use wikiraw:// protocol. https:// will be disabled soon. |
|||
-- 2018-10-13 Fix browser color-inversion issues via #54595d per [[mw:Template:Graph:PageViews]] |
|||
-- 2016-03-20 Allow omitted data for charts, labels for line charts with string (ordinal) scale at point location |
|||
-- 2018-09-16 Allow disabling the legend for templates |
|||
-- 2018-09-10 Allow grid lines |
|||
-- 2018-08-26 Use user-defined order for stacked charts |
|||
-- 2018-02-11 Force usage of explicitely provided x minimum and/or maximum values, rotation of x labels |
|||
-- 2017-08-08 Added showSymbols param to show symbols on line charts |
|||
-- 2016-05-16 Added encodeTitleForPath() to help all path-based APIs graphs like pageviews |
-- 2016-05-16 Added encodeTitleForPath() to help all path-based APIs graphs like pageviews |
||
-- 2016-03-20 Allow omitted data for charts, labels for line charts with string (ordinal) scale at point location |
|||
-- 2016-01-28 For maps, always use wikiraw:// protocol. https:// will be disabled soon. |
|||
local p = {} |
local p = {} |
||
local baseMapDirectory = "Module:Graph/" |
local baseMapDirectory = "Module:Graph/" |
||
local persistentGrey = "#54595d" |
|||
local function numericArray(csv) |
local function numericArray(csv) |
||
Regel 33: | Regel 41: | ||
end |
end |
||
local function stringArray( |
local function stringArray(text) |
||
if not |
if not text then return end |
||
local list = mw.text.split(mw.ustring.gsub(tostring(text), "\\,", "<COMMA>"), ",", true) |
|||
for i = 1, #list do |
|||
list[i] = mw.ustring.gsub(mw.text.trim(list[i]), "<COMMA>", ",") |
|||
end |
|||
return list |
|||
end |
end |
||
Regel 53: | Regel 65: | ||
function p.map(frame) |
function p.map(frame) |
||
-- map path data for geographic objects |
-- map path data for geographic objects |
||
local basemap = frame.args.basemap or "WorldMap-iso2.json" |
local basemap = frame.args.basemap or "WorldMap-iso2.json" -- WorldMap name and/or location may vary from wiki to wiki |
||
-- scaling factor |
-- scaling factor |
||
local scale = tonumber(frame.args.scale) or 100 |
local scale = tonumber(frame.args.scale) or 100 |
||
Regel 76: | Regel 88: | ||
local isNumbers = nil |
local isNumbers = nil |
||
for name, value in pairs(frame.args) do |
for name, value in pairs(frame.args) do |
||
if mw.ustring.find(name, "^[^%l]+$") then |
if mw.ustring.find(name, "^[^%l]+$") and value and value ~= "" then |
||
if isNumbers == nil then isNumbers = tonumber(value) end |
if isNumbers == nil then isNumbers = tonumber(value) end |
||
local data = { id = name, v = value } |
local data = { id = name, v = value } |
||
Regel 90: | Regel 102: | ||
local scales |
local scales |
||
if isNumbers then |
if isNumbers then |
||
if colorScale then colorScale = string.lower(colorScale) end |
|||
if colorScale == "category10" or colorScale == "category20" then else colorScale = stringArray(colorScale) end |
if colorScale == "category10" or colorScale == "category20" then else colorScale = stringArray(colorScale) end |
||
scales = |
scales = |
||
Regel 98: | Regel 111: | ||
domain = { data = "highlights", field = "v" }, |
domain = { data = "highlights", field = "v" }, |
||
range = colorScale, |
range = colorScale, |
||
nice = true |
nice = true, |
||
zero = false |
|||
} |
} |
||
} |
} |
||
Regel 313: | Regel 327: | ||
if xMin then xscale.domainMin = xMin end |
if xMin then xscale.domainMin = xMin end |
||
if xMax then xscale.domainMax = xMax end |
if xMax then xscale.domainMax = xMax end |
||
if xMin or xMax then |
if xMin or xMax then |
||
xscale.clamp = true |
|||
xscale.nice = false |
|||
end |
|||
if chartType == "rect" then |
if chartType == "rect" then |
||
xscale.type = "ordinal" |
xscale.type = "ordinal" |
||
Regel 526: | Regel 543: | ||
-- for stacked charts apply a stacking transformation |
-- for stacked charts apply a stacking transformation |
||
if stacked then |
if stacked then |
||
table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { " |
table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "-_id" }, field = "y" } ) |
||
else |
else |
||
-- for bar charts the series are side-by-side grouped by x |
-- for bar charts the series are side-by-side grouped by x |
||
Regel 637: | Regel 654: | ||
end |
end |
||
local function getSymbolMarks(chartvis) |
|||
local function getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType) |
|||
local symbolmarks = |
|||
{ |
|||
type = "symbol", |
|||
properties = |
|||
{ |
|||
enter = |
|||
{ |
|||
x = { scale = "x", field = "x" }, |
|||
y = { scale = "y", field = "y" }, |
|||
fill = { scale = "color", field = "series" }, |
|||
shape = "circle", |
|||
size = { value = 49 } |
|||
} |
|||
} |
|||
} |
|||
if chartvis.from then symbolmarks.from = copy(chartvis.from) end |
|||
return symbolmarks |
|||
end |
|||
local function getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType) |
|||
local xAxis, yAxis |
local xAxis, yAxis |
||
if chartType ~= "pie" then |
if chartType ~= "pie" then |
||
Regel 646: | Regel 684: | ||
scale = "x", |
scale = "x", |
||
title = xTitle, |
title = xTitle, |
||
format = xAxisFormat |
format = xAxisFormat, |
||
grid = xGrid ~= "0" |
|||
} |
} |
||
if xAxisAngle then |
|||
local xAxisAlign |
|||
if xAxisAngle < 0 then xAxisAlign = "right" else xAxisAlign = "left" end |
|||
xAxis.properties = |
|||
{ |
|||
title = |
|||
{ |
|||
fill = { value = persistentGrey } |
|||
}, |
|||
labels = |
|||
{ |
|||
angle = { value = xAxisAngle }, |
|||
align = { value = xAxisAlign }, |
|||
fill = { value = persistentGrey } |
|||
}, |
|||
ticks = |
|||
{ |
|||
stroke = { value = persistentGrey } |
|||
}, |
|||
axis = |
|||
{ |
|||
stroke = { value = persistentGrey }, |
|||
strokeWidth = { value = 2 } |
|||
}, |
|||
grid = |
|||
{ |
|||
stroke = { value = persistentGrey } |
|||
} |
|||
} |
|||
else |
|||
xAxis.properties = |
|||
{ |
|||
title = |
|||
{ |
|||
fill = { value = persistentGrey } |
|||
}, |
|||
labels = |
|||
{ |
|||
fill = { value = persistentGrey } |
|||
}, |
|||
ticks = |
|||
{ |
|||
stroke = { value = persistentGrey } |
|||
}, |
|||
axis = |
|||
{ |
|||
stroke = { value = persistentGrey }, |
|||
strokeWidth = { value = 2 } |
|||
}, |
|||
grid = |
|||
{ |
|||
stroke = { value = persistentGrey } |
|||
} |
|||
} |
|||
end |
|||
if yType == "integer" and not yAxisFormat then yAxisFormat = "d" end |
if yType == "integer" and not yAxisFormat then yAxisFormat = "d" end |
||
Regel 655: | Regel 749: | ||
scale = "y", |
scale = "y", |
||
title = yTitle, |
title = yTitle, |
||
format = yAxisFormat |
format = yAxisFormat, |
||
grid = yGrid ~= "0" |
|||
} |
|||
yAxis.properties = |
|||
{ |
|||
title = |
|||
{ |
|||
fill = { value = persistentGrey } |
|||
}, |
|||
labels = |
|||
{ |
|||
fill = { value = persistentGrey } |
|||
}, |
|||
ticks = |
|||
{ |
|||
stroke = { value = persistentGrey } |
|||
}, |
|||
axis = |
|||
{ |
|||
stroke = { value = persistentGrey }, |
|||
strokeWidth = { value = 2 } |
|||
}, |
|||
grid = |
|||
{ |
|||
stroke = { value = persistentGrey } |
|||
} |
|||
} |
} |
||
end |
end |
||
Regel 668: | Regel 787: | ||
stroke = "color", |
stroke = "color", |
||
title = legendTitle, |
title = legendTitle, |
||
} |
|||
legend.properties = { |
|||
title = { |
|||
fill = { value = persistentGrey }, |
|||
}, |
|||
labels = { |
|||
fill = { value = persistentGrey }, |
|||
}, |
|||
} |
} |
||
if chartType == "pie" then |
if chartType == "pie" then |
||
-- move legend from center position to top |
legend.properties = { |
||
-- move legend from center position to top |
|||
legend = { |
|||
legend.properties = { legend = { y = { value = -outerRadius } } } |
|||
y = { value = -outerRadius }, |
|||
}, |
|||
title = { |
|||
fill = { value = persistentGrey } |
|||
}, |
|||
labels = { |
|||
fill = { value = persistentGrey }, |
|||
}, |
|||
} |
|||
end |
end |
||
return legend |
return legend |
||
Regel 685: | Regel 822: | ||
local interpolate = frame.args.interpolate |
local interpolate = frame.args.interpolate |
||
-- mark colors (if no colors are given, the default 10 color palette is used) |
-- mark colors (if no colors are given, the default 10 color palette is used) |
||
local |
local colorString = frame.args.colors |
||
if colorString then colorString = string.lower(colorString) end |
|||
local colors = stringArray(colorString) |
|||
-- for line charts, the thickness of the line; for pie charts the gap between each slice |
-- for line charts, the thickness of the line; for pie charts the gap between each slice |
||
local linewidth = tonumber(frame.args.linewidth) |
local linewidth = tonumber(frame.args.linewidth) |
||
Regel 702: | Regel 841: | ||
local xAxisFormat = frame.args.xAxisFormat |
local xAxisFormat = frame.args.xAxisFormat |
||
local yAxisFormat = frame.args.yAxisFormat |
local yAxisFormat = frame.args.yAxisFormat |
||
local xAxisAngle = tonumber(frame.args.xAxisAngle) |
|||
-- show grid |
|||
local xGrid = frame.args.xGrid or "0" |
|||
local yGrid = frame.args.yGrid or "0" |
|||
-- for line chart, show a symbol at each data point |
|||
local showSymbols = frame.args.showSymbols |
|||
-- show legend with given title |
-- show legend with given title |
||
local legendTitle = frame.args.legend |
local legendTitle = frame.args.legend |
||
Regel 788: | Regel 933: | ||
-- create chart markings |
-- create chart markings |
||
local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate) |
local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate) |
||
local marks = { chartvis } |
|||
-- text marks |
-- text marks |
||
local textmarks |
|||
if showValues then |
if showValues then |
||
if type(showValues) == "string" then -- deserialize as table |
if type(showValues) == "string" then -- deserialize as table |
||
Regel 803: | Regel 948: | ||
local chartmarks = chartvis |
local chartmarks = chartvis |
||
if chartmarks.marks then chartmarks = chartmarks.marks[1] end |
if chartmarks.marks then chartmarks = chartmarks.marks[1] end |
||
textmarks = getTextMarks(chartmarks, chartType, outerRadius, scales, radiusScale, yType, showValues) |
local textmarks = getTextMarks(chartmarks, chartType, outerRadius, scales, radiusScale, yType, showValues) |
||
if chartmarks ~= chartvis then |
if chartmarks ~= chartvis then |
||
table.insert(chartvis.marks, textmarks) |
table.insert(chartvis.marks, textmarks) |
||
else |
|||
textmarks = nil |
|||
table.insert(marks, textmarks) |
|||
end |
|||
end |
|||
-- symbol marks |
|||
if chartType == "line" and showSymbols then |
|||
local chartmarks = chartvis |
|||
if chartmarks.marks then chartmarks = chartmarks.marks[1] end |
|||
local symbolmarks = getSymbolMarks(chartmarks) |
|||
if chartmarks ~= chartvis then |
|||
table.insert(chartvis.marks, symbolmarks) |
|||
else |
|||
table.insert(marks, symbolmarks) |
|||
end |
end |
||
end |
end |
||
-- axes |
-- axes |
||
local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType) |
local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType) |
||
-- legend |
-- legend |
||
local legend |
local legend |
||
if legendTitle then legend = getLegend(legendTitle, chartType, outerRadius) end |
if legendTitle and tonumber(legendTitle) ~= 0 then legend = getLegend(legendTitle, chartType, outerRadius) end |
||
-- construct final output object |
-- construct final output object |
||
Regel 826: | Regel 984: | ||
scales = scales, |
scales = scales, |
||
axes = { xAxis, yAxis }, |
axes = { xAxis, yAxis }, |
||
marks = |
marks = marks, |
||
legends = { legend } |
legends = { legend } |
||
} |
} |
||
Regel 847: | Regel 1.005: | ||
-- This function is critical for any graph that uses path-based APIs, e.g. PageViews graph |
-- This function is critical for any graph that uses path-based APIs, e.g. PageViews graph |
||
function p.encodeTitleForPath(frame) |
function p.encodeTitleForPath(frame) |
||
return mw.uri.encode(mw.text.decode(mw.text.trim(frame.args[1]) |
return mw.uri.encode(mw.text.decode(mw.text.trim(frame.args[1])), 'PATH') |
||
end |
end |
||
Versie van 8 mrt 2020 15:38
Module with helper functions for the Graph extension to display graphs and maps. From de:Modul:Graph.
Functions for templates
chart
Creates a JSON object for <graph>
to display charts. In the article namespace the template {{GraphChart}} should be used instead. See its page for use cases.
Parameters:
- colors
- kleurenpalet van de grafiek, een lijst van kleuren door komma's gescheiden. De kleuren kunnen zijn:
#rgb
/#rrggbb
/#aarrggbb
of een Engelse CSS kleurennaam. Bij#aarrggbb
geeft deaa
het alpha channel aan, bijvoorbeeld FF=100% doorzichtig, 80=50% doorzichtig, etc. - hAnnotatonsLabel
- toon horizontale annotatielabels bij annotatielijnen, bijvoorbeeld
hAnnotatonsLabel = label1, label2, label3
- hAnnotatonsLine
- toon horizontale annotatielijnen bij specifieke waarden, bijvoorbeeld
hAnnotatonsLine=4, 5, 6
- height
- hoogte van de grafiek in pixels, bij de meeste grafieken de hoogte van de Y-as
- innerRadius
- Alleen geldig voor een taartdiagram, de binnenste straal in pixels om een donut diagram te maken.
- interpolate
- Interpolatie methode voor lijn- en vlakdiagrammen.
monotone
is aanbevolen voor een monotone cubic interpolation. Op https://github.com/nyurik/vega/wiki/Marks#line staan de andere ondersteunde interpolatiemethoden. - legend
- Toon legenda/legende. Dit werkt alleen bij meerdere datareeksen.
- linewidth
- Voor een lijndiagram: de dikte van de lijnen, een lijndikte van 0 resulteert in een spreidingsdiagram. Voor een taartdiagram: de ruimte tussen de taartpunten.
- linewidths
- Voor verschillende lijndiktes per datareeks. Een lijndikte van 0 in combinatie met "showSymbols" laat die lijn weg, toont slechts de punten, bijvoorbeeld
linewidths=1, 0, 5, 0.2
- showSymbols
- voor lijngrafieken: toon een symbool op data punten. Een getal geeft de symboolgrootte aan in pixels, bijvoorbeeld
showSymbols=3
.showSymbols=
geeft punten met een standaardgrootte van 2.5 pixel. Verschillende groottes zijn mogelijk per datareeks:showSymbols=1, 2, 3, 4
- showValues
- Voor een taartdiagram en een ongestapeld staafdiagram: Toon y waarden als tekst in de grafiek. De volgende parameters, gescheiden door komma's, definiëren hoe de tekst er uit ziet, bijvoorbeeld
showValues = fontcolor:silver, fontsize:10, offset:10
.- format: Formatteer numerieken zoals op https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#numbers en datum/tijd zoals op https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md .
- fontcolor: de kleur van de tekst
- fontsize: de grootte van de tekst
- offset: verplaats de tekst. Voor een staafdiagram en een taartdiagram met
midangle
definieert dit of de tekst binnen of buiten het diagram valt. - angle voor taartdiagrammen: de hoek in graden, gebaseerd op de mid-angle van de taartpunt.
midangle
is de standaardwaarde.
- symbolsNoFill
symbolsNoFill =
vul het symbool niet, tekent alleen de omtrek.- symbolsShape
- de vorm van het symbool, mogelijke waarden: circle, x, square, cross, diamond, triangle_up, triangle_down, triangle_right, triangle_left. Elke data reeks kan een eigen symbool krijgen, gebruik door komma's gescheiden waarden, bijvoorbeeld:
symbolsShape= circle, cross, square
- symbolsStroke
- Voor "x" symbol en "symbolsNoFill": de dikte van de lijn in pixels, standaard waarde is 2.5.
- type
- het soort diagram:
area
, Vlakdiagramline
, Lijndiagram of Spreidingsdiagrampie
, taartdiagramrect
, Staafdiagramstackedarea
, Gestapeld Vlakdiagramstackedrect
, Gestapeld Staafdiagram
- vAnnotatonsLabel
- toon verticale annotatielabels bij annotatielijnen, bijvoorbeeld
vAnnotatonsLabel = label1, label2, label3
- vAnnotatonsLine
- toon verticale annotatielijnen bij specifieke waardes, bijvoorbeeld
vAnnotatonsLine=4, 5, 6
- width
- breedte van de grafiek in pixels, bij de meeste grafieken de lengte van de x-as
- x
- de x-waarden, gescheiden door komma's,
x=a,b,c
- xAxisAngle
- draaiing van de x-as labels in graden, aanbevolen waarden: -45, +45, -90, +90
- xAxisFormat
- Formatteer de labels op de x-as.
- Voor de formattering van numerieken zie https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#numbers, bijvoorbeeld
xAxisFormat=%
voor percentages, -0.1=-10%, 0=0%, .25=25%, 1=100%, 1.5=150%. xAxisFormat=.1% geeft percentages met 1 cijfer achter de 'komma'.xAxisFormat=03
alles 3 lang, met voorloopnullenxAxisFormat=.2f
2 cijfers achter de 'komma'xAxisFormat=d
geheel getal
- Noot: Het is nog niet mogelijk om getallen 'Europees' te formatteren als 583.231.475.923,00
- Zie https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md voor datum/tijd.
- xAxisMin, xAxisMax
- voor lijn, spreidings en vlakdiagrammen: minimum en maximum waarden van de x-as. Met deze parameters kan x-as gespiegeld worden door min en max om te draaien. Zet de Max op de laagste waarde, de min op de hoogste.
- xAxisTitle
- Beschrijving van de variabele op de x-as
- xGrid
- toon verticale rasterlijnen voor x-as waarden.
- xScaleType
- het soort van schaal van de x-as.
linear
: lineaire schaal, de standaardlog
logaritmische schaalsqrt
vierkantswortel-schaal
- xType
- datatype van x-as waarden.
date
: datums, bijvoorbeeld 2021/02/14integer
: geheel getalnumber
: getal met cijfers achter de kommastring
: tekst. Als er maar een paar waardes zijn, gebruik danxType=string
om herhaling van x-as waarden te voorkomen.
- y
- De y waarden van een enkele datareeks, gescheiden door komma's, bijvoorbeeld
y=1,2,4,9
- y1, y2, …
- de y-waarden van meerdere datareeksen, bijvoorbeeld
y1=1,2,4,9
,y2=1,8,32,27
. Bij een taartdiagram geefty2
de straal van de taartpunten. - yAxisFormat
- formatteer de labels op de y-as, de toegestane waarden zijn hetzelfde als bij xAxisFormat.
- yAxisMin, and yAxisMax
- voor lijn, spreidings en vlakdiagrammen: minimum en maximum waarden van de y-as. Met deze parameters kan y-as ondersteboven gezet worden door min en max om te draaien. Zet de Max op de laagste waarde, de min op de hoogste.
- yAxisTitle
- beschrijving van de variabele op de y-as
- yGrid
- toon horizontale rasterlijnen voor y-as waarden.
- yScaleType
- het soort van schaal op de y-as. Zie xScaleType voor mogelijke schalen.
- yType
- data type van y-as waarden,
date
: datums, bijvoorbeeld 2021/02/14integer
: geheel getalnumber
: getal met cijfers achter de kommastring
: tekst, alleen mogelijk bij een lijndiagram.
- y1Title, y2Title, …
- beschrijving van de datareeksen in de legenda/legende.
- formatjson
- format JSON object for better legibility
Template wrappers
The functions mapWrapper
and chartWrapper
are wrappers to pass all parameters of the calling template to the respective map
and chart
functions.
Opmerking: In de voorvertoning bij het bewerken is de grafiek een canvas element, een Vectorafbeelding. Echter, na publiceren van de pagina wordt de grafiek een PNG bestand. {{#invoke:Graph|function_wrapper_name}}
-- ATTENTION: Please edit this code at https://de.wikipedia.org/wiki/Modul:Graph
-- This way all wiki languages can stay in sync. Thank you!
--
-- Version History (_PLEASE UPDATE when modifying anything_):
-- 2019-11-08 Apply color-inversion-friendliness to legend title, labels, and xGrid
-- 2019-01-24 Allow comma-separated lists to contain values with commas
-- 2018-10-13 Fix browser color-inversion issues via #54595d per [[mw:Template:Graph:PageViews]]
-- 2018-09-16 Allow disabling the legend for templates
-- 2018-09-10 Allow grid lines
-- 2018-08-26 Use user-defined order for stacked charts
-- 2018-02-11 Force usage of explicitely provided x minimum and/or maximum values, rotation of x labels
-- 2017-08-08 Added showSymbols param to show symbols on line charts
-- 2016-05-16 Added encodeTitleForPath() to help all path-based APIs graphs like pageviews
-- 2016-03-20 Allow omitted data for charts, labels for line charts with string (ordinal) scale at point location
-- 2016-01-28 For maps, always use wikiraw:// protocol. https:// will be disabled soon.
local p = {}
local baseMapDirectory = "Module:Graph/"
local persistentGrey = "#54595d"
local function numericArray(csv)
if not csv then return end
local list = mw.text.split(csv, "%s*,%s*")
local result = {}
local isInteger = true
for i = 1, #list do
if list[i] == "" then
result[i] = nil
else
result[i] = tonumber(list[i])
if not result[i] then return end
if isInteger then
local int, frac = math.modf(result[i])
isInteger = frac == 0.0
end
end
end
return result, isInteger
end
local function stringArray(text)
if not text then return end
local list = mw.text.split(mw.ustring.gsub(tostring(text), "\\,", "<COMMA>"), ",", true)
for i = 1, #list do
list[i] = mw.ustring.gsub(mw.text.trim(list[i]), "<COMMA>", ",")
end
return list
end
local function isTable(t) return type(t) == "table" end
local function copy(x)
if type(x) == "table" then
local result = {}
for key, value in pairs(x) do result[key] = copy(value) end
return result
else
return x
end
end
function p.map(frame)
-- map path data for geographic objects
local basemap = frame.args.basemap or "WorldMap-iso2.json" -- WorldMap name and/or location may vary from wiki to wiki
-- scaling factor
local scale = tonumber(frame.args.scale) or 100
-- map projection, see https://github.com/mbostock/d3/wiki/Geo-Projections
local projection = frame.args.projection or "equirectangular"
-- defaultValue for geographic objects without data
local defaultValue = frame.args.defaultValue
local scaleType = frame.args.scaleType or "linear"
-- minimaler Wertebereich (nur für numerische Daten)
local domainMin = tonumber(frame.args.domainMin)
-- maximaler Wertebereich (nur für numerische Daten)
local domainMax = tonumber(frame.args.domainMax)
-- Farbwerte der Farbskala (nur für numerische Daten)
local colorScale = frame.args.colorScale or "category10"
-- show legend
local legend = frame.args.legend
-- format JSON output
local formatJson = frame.args.formatjson
-- map data are key-value pairs: keys are non-lowercase strings (ideally ISO codes) which need to match the "id" values of the map path data
local values = {}
local isNumbers = nil
for name, value in pairs(frame.args) do
if mw.ustring.find(name, "^[^%l]+$") and value and value ~= "" then
if isNumbers == nil then isNumbers = tonumber(value) end
local data = { id = name, v = value }
if isNumbers then data.v = tonumber(data.v) end
table.insert(values, data)
end
end
if not defaultValue then
if isNumbers then defaultValue = 0 else defaultValue = "silver" end
end
-- create highlight scale
local scales
if isNumbers then
if colorScale then colorScale = string.lower(colorScale) end
if colorScale == "category10" or colorScale == "category20" then else colorScale = stringArray(colorScale) end
scales =
{
{
name = "color",
type = scaleType,
domain = { data = "highlights", field = "v" },
range = colorScale,
nice = true,
zero = false
}
}
if domainMin then scales[1].domainMin = domainMin end
if domainMax then scales[1].domainMax = domainMax end
local exponent = string.match(scaleType, "pow%s+(%d+%.?%d+)") -- check for exponent
if exponent then
scales[1].type = "pow"
scales[1].exponent = exponent
end
end
-- create legend
if legend then
legend =
{
{
fill = "color",
offset = 120,
properties =
{
title = { fontSize = { value = 14 } },
labels = { fontSize = { value = 12 } },
legend =
{
stroke = { value = "silver" },
strokeWidth = { value = 1.5 }
}
}
}
}
end
-- get map url
local basemapUrl
if (string.sub(basemap, 1, 10) == "wikiraw://") then
basemapUrl = basemap
else
-- if not a (supported) url look for a colon as namespace separator. If none prepend default map directory name.
if not string.find(basemap, ":") then basemap = baseMapDirectory .. basemap end
basemapUrl = "wikiraw:///" .. mw.uri.encode(mw.title.new(basemap).prefixedText, "PATH")
end
local output =
{
version = 2,
width = 1, -- generic value as output size depends solely on map size and scaling factor
height = 1, -- ditto
data =
{
{
-- data source for the highlights
name = "highlights",
values = values
},
{
-- data source for map paths data
name = "countries",
url = basemapUrl,
format = { type = "topojson", feature = "countries" },
transform =
{
{
-- geographic transformation ("geopath") of map paths data
type = "geopath",
value = "data", -- data source
scale = scale,
translate = { 0, 0 },
projection = projection
},
{
-- join ("zip") of mutiple data source: here map paths data and highlights
type = "lookup",
keys = { "id" }, -- key for map paths data
on = "highlights", -- name of highlight data source
onKey = "id", -- key for highlight data source
as = { "zipped" }, -- name of resulting table
default = { v = defaultValue } -- default value for geographic objects that could not be joined
}
}
}
},
marks =
{
-- output markings (map paths and highlights)
{
type = "path",
from = { data = "countries" },
properties =
{
enter = { path = { field = "layout_path" } },
update = { fill = { field = "zipped.v" } },
hover = { fill = { value = "darkgrey" } }
}
}
},
legends = legend
}
if (scales) then
output.scales = scales
output.marks[1].properties.update.fill.scale = "color"
end
local flags
if formatJson then flags = mw.text.JSON_PRETTY end
return mw.text.jsonEncode(output, flags)
end
local function deserializeXData(serializedX, xType, xMin, xMax)
local x
if not xType or xType == "integer" or xType == "number" then
local isInteger
x, isInteger = numericArray(serializedX)
if x then
xMin = tonumber(xMin)
xMax = tonumber(xMax)
if not xType then
if isInteger then xType = "integer" else xType = "number" end
end
else
if xType then error("Numbers expected for parameter 'x'") end
end
end
if not x then
x = stringArray(serializedX)
if not xType then xType = "string" end
end
return x, xType, xMin, xMax
end
local function deserializeYData(serializedYs, yType, yMin, yMax)
local y = {}
local areAllInteger = true
for yNum, value in pairs(serializedYs) do
local yValues
if not yType or yType == "integer" or yType == "number" then
local isInteger
yValues, isInteger = numericArray(value)
if yValues then
areAllInteger = areAllInteger and isInteger
else
if yType then
error("Numbers expected for parameter '" .. name .. "'")
else
return deserializeYData(serializedYs, "string", yMin, yMax)
end
end
end
if not yValues then yValues = stringArray(value) end
y[yNum] = yValues
end
if not yType then
if areAllInteger then yType = "integer" else yType = "number" end
end
if yType == "integer" or yType == "number" then
yMin = tonumber(yMin)
yMax = tonumber(yMax)
end
return y, yType, yMin, yMax
end
local function convertXYToManySeries(x, y, xType, yType, seriesTitles)
local data =
{
name = "chart",
format =
{
type = "json",
parse = { x = xType, y = yType }
},
values = {}
}
for i = 1, #y do
local yLen = table.maxn(y[i])
for j = 1, #x do
if j <= yLen and y[i][j] then table.insert(data.values, { series = seriesTitles[i], x = x[j], y = y[i][j] }) end
end
end
return data
end
local function convertXYToSingleSeries(x, y, xType, yType, yNames)
local data = { name = "chart", format = { type = "json", parse = { x = xType } }, values = {} }
for j = 1, #y do data.format.parse[yNames[j]] = yType end
for i = 1, #x do
local item = { x = x[i] }
for j = 1, #y do item[yNames[j]] = y[j][i] end
table.insert(data.values, item)
end
return data
end
local function getXScale(chartType, stacked, xMin, xMax, xType)
if chartType == "pie" then return end
local xscale =
{
name = "x",
type = "linear",
range = "width",
zero = false, -- do not include zero value
nice = true, -- force round numbers for y scale
domain = { data = "chart", field = "x" }
}
if xMin then xscale.domainMin = xMin end
if xMax then xscale.domainMax = xMax end
if xMin or xMax then
xscale.clamp = true
xscale.nice = false
end
if chartType == "rect" then
xscale.type = "ordinal"
if not stacked then xscale.padding = 0.2 end -- pad each bar group
else
if xType == "date" then xscale.type = "time"
elseif xType == "string" then
xscale.type = "ordinal"
xscale.points = true
end
end
return xscale
end
local function getYScale(chartType, stacked, yMin, yMax, yType)
if chartType == "pie" then return end
local yscale =
{
name = "y",
type = "linear",
range = "height",
-- area charts have the lower boundary of their filling at y=0 (see marks.properties.enter.y2), therefore these need to start at zero
zero = chartType ~= "line",
nice = true
}
if yMin then yscale.domainMin = yMin end
if yMax then yscale.domainMax = yMax end
if yMin or yMax then yscale.clamp = true end
if yType == "date" then yscale.type = "time"
elseif yType == "string" then yscale.type = "ordinal" end
if stacked then
yscale.domain = { data = "stats", field = "sum_y" }
else
yscale.domain = { data = "chart", field = "y" }
end
return yscale
end
local function getColorScale(colors, chartType, xCount, yCount)
if not colors then
if (chartType == "pie" and xCount > 10) or yCount > 10 then colors = "category20" else colors = "category10" end
end
local colorScale =
{
name = "color",
type = "ordinal",
range = colors,
domain = { data = "chart", field = "series" }
}
if chartType == "pie" then colorScale.domain.field = "x" end
return colorScale
end
local function getAlphaColorScale(colors, y)
local alphaScale
-- if there is at least one color in the format "#aarrggbb", create a transparency (alpha) scale
if isTable(colors) then
local alphas = {}
local hasAlpha = false
for i = 1, #colors do
local a, rgb = string.match(colors[i], "#(%x%x)(%x%x%x%x%x%x)")
if a then
hasAlpha = true
alphas[i] = tostring(tonumber(a, 16) / 255.0)
colors[i] = "#" .. rgb
else
alphas[i] = "1"
end
end
for i = #colors + 1, #y do alphas[i] = "1" end
if hasAlpha then alphaScale = { name = "transparency", type = "ordinal", range = alphas } end
end
return alphaScale
end
local function getValueScale(fieldName, min, max, type)
local valueScale =
{
name = fieldName,
type = type or "linear",
domain = { data = "chart", field = fieldName },
range = { min, max }
}
return valueScale
end
local function addInteractionToChartVisualisation(plotMarks, colorField, dataField)
-- initial setup
if not plotMarks.properties.enter then plotMarks.properties.enter = {} end
plotMarks.properties.enter[colorField] = { scale = "color", field = dataField }
-- action when cursor is over plot mark: highlight
if not plotMarks.properties.hover then plotMarks.properties.hover = {} end
plotMarks.properties.hover[colorField] = { value = "red" }
-- action when cursor leaves plot mark: reset to initial setup
if not plotMarks.properties.update then plotMarks.properties.update = {} end
plotMarks.properties.update[colorField] = { scale = "color", field = dataField }
end
local function getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale)
local chartvis =
{
type = "arc",
from = { data = "chart", transform = { { field = "y", type = "pie" } } },
properties =
{
enter = {
innerRadius = { value = innerRadius },
outerRadius = { },
startAngle = { field = "layout_start" },
endAngle = { field = "layout_end" },
stroke = { value = "white" },
strokeWidth = { value = linewidth or 1 }
}
}
}
if radiusScale then
chartvis.properties.enter.outerRadius.scale = radiusScale.name
chartvis.properties.enter.outerRadius.field = radiusScale.domain.field
else
chartvis.properties.enter.outerRadius.value = outerRadius
end
addInteractionToChartVisualisation(chartvis, "fill", "x")
return chartvis
end
local function getChartVisualisation(chartType, stacked, colorField, yCount, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate)
if chartType == "pie" then return getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale) end
local chartvis =
{
type = chartType,
properties =
{
-- chart creation event handler
enter =
{
x = { scale = "x", field = "x" },
y = { scale = "y", field = "y" }
}
}
}
addInteractionToChartVisualisation(chartvis, colorField, "series")
if colorField == "stroke" then
chartvis.properties.enter.strokeWidth = { value = linewidth or 2.5 }
end
if interpolate then chartvis.properties.enter.interpolate = { value = interpolate } end
if alphaScale then chartvis.properties.update[colorField .. "Opacity"] = { scale = "transparency" } end
-- for bars and area charts set the lower bound of their areas
if chartType == "rect" or chartType == "area" then
if stacked then
-- for stacked charts this lower bound is the end of the last stacking element
chartvis.properties.enter.y2 = { scale = "y", field = "layout_end" }
else
--[[
for non-stacking charts the lower bound is y=0
TODO: "yscale.zero" is currently set to "true" for this case, but "false" for all other cases.
For the similar behavior "y2" should actually be set to where y axis crosses the x axis,
if there are only positive or negative values in the data ]]
chartvis.properties.enter.y2 = { scale = "y", value = 0 }
end
end
-- for bar charts ...
if chartType == "rect" then
-- set 1 pixel width between the bars
chartvis.properties.enter.width = { scale = "x", band = true, offset = -1 }
-- for multiple series the bar marking needs to use the "inner" series scale, whereas the "outer" x scale is used by the grouping
if not stacked and yCount > 1 then
chartvis.properties.enter.x.scale = "series"
chartvis.properties.enter.x.field = "series"
chartvis.properties.enter.width.scale = "series"
end
end
-- stacked charts have their own (stacked) y values
if stacked then chartvis.properties.enter.y.field = "layout_start" end
-- if there are multiple series group these together
if yCount == 1 then
chartvis.from = { data = "chart" }
else
-- if there are multiple series, connect colors to series
chartvis.properties.update[colorField].field = "series"
if alphaScale then chartvis.properties.update[colorField .. "Opacity"].field = "series" end
-- apply a grouping (facetting) transformation
chartvis =
{
type = "group",
marks = { chartvis },
from =
{
data = "chart",
transform =
{
{
type = "facet",
groupby = { "series" }
}
}
}
}
-- for stacked charts apply a stacking transformation
if stacked then
table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "-_id" }, field = "y" } )
else
-- for bar charts the series are side-by-side grouped by x
if chartType == "rect" then
-- for bar charts with multiple series: each serie is grouped by the x value, therefore the series need their own scale within each x group
local groupScale =
{
name = "series",
type = "ordinal",
range = "width",
domain = { field = "series" }
}
chartvis.from.transform[1].groupby = "x"
chartvis.scales = { groupScale }
chartvis.properties = { enter = { x = { field = "key", scale = "x" }, width = { scale = "x", band = true } } }
end
end
end
return chartvis
end
local function getTextMarks(chartvis, chartType, outerRadius, scales, radiusScale, yType, showValues)
local properties
if chartType == "rect" then
properties =
{
x = { scale = chartvis.properties.enter.x.scale, field = chartvis.properties.enter.x.field },
y = { scale = chartvis.properties.enter.y.scale, field = chartvis.properties.enter.y.field, offset = -(tonumber(showValues.offset) or -4) },
--dx = { scale = chartvis.properties.enter.x.scale, band = true, mult = 0.5 }, -- for horizontal text
dy = { scale = chartvis.properties.enter.x.scale, band = true, mult = 0.5 }, -- for vertical text
align = { },
baseline = { value = "middle" },
fill = { },
angle = { value = -90 },
fontSize = { value = tonumber(showValues.fontsize) or 11 }
}
if properties.y.offset >= 0 then
properties.align.value = "right"
properties.fill.value = showValues.fontcolor or "white"
else
properties.align.value = "left"
properties.fill.value = showValues.fontcolor or "black"
end
elseif chartType == "pie" then
properties =
{
x = { group = "width", mult = 0.5 },
y = { group = "height", mult = 0.5 },
radius = { offset = tonumber(showValues.offset) or -4 },
theta = { field = "layout_mid" },
fill = { value = showValues.fontcolor or "black" },
baseline = { },
angle = { },
fontSize = { value = tonumber(showValues.fontsize) or math.ceil(outerRadius / 10) }
}
if (showValues.angle or "midangle") == "midangle" then
properties.align = { value = "center" }
properties.angle = { field = "layout_mid", mult = 180.0 / math.pi }
if properties.radius.offset >= 0 then
properties.baseline.value = "bottom"
else
if not showValues.fontcolor then properties.fill.value = "white" end
properties.baseline.value = "top"
end
elseif tonumber(showValues.angle) then
-- qunatize scale for aligning text left on right half-circle and right on left half-circle
local alignScale = { name = "align", type = "quantize", domainMin = 0.0, domainMax = math.pi * 2, range = { "left", "right" } }
table.insert(scales, alignScale)
properties.align = { scale = alignScale.name, field = "layout_mid" }
properties.angle = { value = tonumber(showValues.angle) }
properties.baseline.value = "middle"
if not tonumber(showValues.offset) then properties.radius.offset = 4 end
end
if radiusScale then
properties.radius.scale = radiusScale.name
properties.radius.field = radiusScale.domain.field
else
properties.radius.value = outerRadius
end
end
if properties then
if showValues.format then
local template = "datum.y"
if yType == "integer" or yType == "number" then template = template .. "|number:'" .. showValues.format .. "'"
elseif yType == "date" then template = template .. "|time:" .. showValues.format .. "'"
end
properties.text = { template = "{{" .. template .. "}}" }
else
properties.text = { field = "y" }
end
local textmarks =
{
type = "text",
properties =
{
enter = properties
}
}
if chartvis.from then textmarks.from = copy(chartvis.from) end
return textmarks
end
end
local function getSymbolMarks(chartvis)
local symbolmarks =
{
type = "symbol",
properties =
{
enter =
{
x = { scale = "x", field = "x" },
y = { scale = "y", field = "y" },
fill = { scale = "color", field = "series" },
shape = "circle",
size = { value = 49 }
}
}
}
if chartvis.from then symbolmarks.from = copy(chartvis.from) end
return symbolmarks
end
local function getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType)
local xAxis, yAxis
if chartType ~= "pie" then
if xType == "integer" and not xAxisFormat then xAxisFormat = "d" end
xAxis =
{
type = "x",
scale = "x",
title = xTitle,
format = xAxisFormat,
grid = xGrid ~= "0"
}
if xAxisAngle then
local xAxisAlign
if xAxisAngle < 0 then xAxisAlign = "right" else xAxisAlign = "left" end
xAxis.properties =
{
title =
{
fill = { value = persistentGrey }
},
labels =
{
angle = { value = xAxisAngle },
align = { value = xAxisAlign },
fill = { value = persistentGrey }
},
ticks =
{
stroke = { value = persistentGrey }
},
axis =
{
stroke = { value = persistentGrey },
strokeWidth = { value = 2 }
},
grid =
{
stroke = { value = persistentGrey }
}
}
else
xAxis.properties =
{
title =
{
fill = { value = persistentGrey }
},
labels =
{
fill = { value = persistentGrey }
},
ticks =
{
stroke = { value = persistentGrey }
},
axis =
{
stroke = { value = persistentGrey },
strokeWidth = { value = 2 }
},
grid =
{
stroke = { value = persistentGrey }
}
}
end
if yType == "integer" and not yAxisFormat then yAxisFormat = "d" end
yAxis =
{
type = "y",
scale = "y",
title = yTitle,
format = yAxisFormat,
grid = yGrid ~= "0"
}
yAxis.properties =
{
title =
{
fill = { value = persistentGrey }
},
labels =
{
fill = { value = persistentGrey }
},
ticks =
{
stroke = { value = persistentGrey }
},
axis =
{
stroke = { value = persistentGrey },
strokeWidth = { value = 2 }
},
grid =
{
stroke = { value = persistentGrey }
}
}
end
return xAxis, yAxis
end
local function getLegend(legendTitle, chartType, outerRadius)
local legend =
{
fill = "color",
stroke = "color",
title = legendTitle,
}
legend.properties = {
title = {
fill = { value = persistentGrey },
},
labels = {
fill = { value = persistentGrey },
},
}
if chartType == "pie" then
legend.properties = {
-- move legend from center position to top
legend = {
y = { value = -outerRadius },
},
title = {
fill = { value = persistentGrey }
},
labels = {
fill = { value = persistentGrey },
},
}
end
return legend
end
function p.chart(frame)
-- chart width and height
local graphwidth = tonumber(frame.args.width) or 200
local graphheight = tonumber(frame.args.height) or 200
-- chart type
local chartType = frame.args.type or "line"
-- interpolation mode for line and area charts: linear, step-before, step-after, basis, basis-open, basis-closed (type=line only), bundle (type=line only), cardinal, cardinal-open, cardinal-closed (type=line only), monotone
local interpolate = frame.args.interpolate
-- mark colors (if no colors are given, the default 10 color palette is used)
local colorString = frame.args.colors
if colorString then colorString = string.lower(colorString) end
local colors = stringArray(colorString)
-- for line charts, the thickness of the line; for pie charts the gap between each slice
local linewidth = tonumber(frame.args.linewidth)
-- x and y axis caption
local xTitle = frame.args.xAxisTitle
local yTitle = frame.args.yAxisTitle
-- x and y value types
local xType = frame.args.xType
local yType = frame.args.yType
-- override x and y axis minimum and maximum
local xMin = frame.args.xAxisMin
local xMax = frame.args.xAxisMax
local yMin = frame.args.yAxisMin
local yMax = frame.args.yAxisMax
-- override x and y axis label formatting
local xAxisFormat = frame.args.xAxisFormat
local yAxisFormat = frame.args.yAxisFormat
local xAxisAngle = tonumber(frame.args.xAxisAngle)
-- show grid
local xGrid = frame.args.xGrid or "0"
local yGrid = frame.args.yGrid or "0"
-- for line chart, show a symbol at each data point
local showSymbols = frame.args.showSymbols
-- show legend with given title
local legendTitle = frame.args.legend
-- show values as text
local showValues = frame.args.showValues
-- pie chart radiuses
local innerRadius = tonumber(frame.args.innerRadius) or 0
local outerRadius = math.min(graphwidth, graphheight)
-- format JSON output
local formatJson = frame.args.formatjson
-- get x values
local x
x, xType, xMin, xMax = deserializeXData(frame.args.x, xType, xMin, xMax)
-- get y values (series)
local yValues = {}
local seriesTitles = {}
for name, value in pairs(frame.args) do
local yNum
if name == "y" then yNum = 1 else yNum = tonumber(string.match(name, "^y(%d+)$")) end
if yNum then
yValues[yNum] = value
-- name the series: default is "y<number>". Can be overwritten using the "y<number>Title" parameters.
seriesTitles[yNum] = frame.args["y" .. yNum .. "Title"] or name
end
end
local y
y, yType, yMin, yMax = deserializeYData(yValues, yType, yMin, yMax)
-- create data tuples, consisting of series index, x value, y value
local data
if chartType == "pie" then
-- for pie charts the second second series is merged into the first series as radius values
data = convertXYToSingleSeries(x, y, xType, yType, { "y", "r" })
else
data = convertXYToManySeries(x, y, xType, yType, seriesTitles)
end
-- configure stacked charts
local stacked = false
local stats
if string.sub(chartType, 1, 7) == "stacked" then
chartType = string.sub(chartType, 8)
if #y > 1 then -- ignore stacked charts if there is only one series
stacked = true
-- aggregate data by cumulative y values
stats =
{
name = "stats", source = "chart", transform =
{
{
type = "aggregate",
groupby = { "x" },
summarize = { y = "sum" }
}
}
}
end
end
-- create scales
local scales = {}
local xscale = getXScale(chartType, stacked, xMin, xMax, xType)
table.insert(scales, xscale)
local yscale = getYScale(chartType, stacked, yMin, yMax, yType)
table.insert(scales, yscale)
local colorScale = getColorScale(colors, chartType, #x, #y)
table.insert(scales, colorScale)
local alphaScale = getAlphaColorScale(colors, y)
table.insert(scales, alphaScale)
local radiusScale
if chartType == "pie" and #y > 1 then
radiusScale = getValueScale("r", 0, outerRadius)
table.insert(scales, radiusScale)
end
-- decide if lines (strokes) or areas (fills) should be drawn
local colorField
if chartType == "line" then colorField = "stroke" else colorField = "fill" end
-- create chart markings
local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate)
local marks = { chartvis }
-- text marks
if showValues then
if type(showValues) == "string" then -- deserialize as table
local keyValues = mw.text.split(showValues, "%s*,%s*")
showValues = {}
for _, kv in ipairs(keyValues) do
local key, value = mw.ustring.match(kv, "^%s*(.-)%s*:%s*(.-)%s*$")
if key then showValues[key] = value end
end
end
local chartmarks = chartvis
if chartmarks.marks then chartmarks = chartmarks.marks[1] end
local textmarks = getTextMarks(chartmarks, chartType, outerRadius, scales, radiusScale, yType, showValues)
if chartmarks ~= chartvis then
table.insert(chartvis.marks, textmarks)
else
table.insert(marks, textmarks)
end
end
-- symbol marks
if chartType == "line" and showSymbols then
local chartmarks = chartvis
if chartmarks.marks then chartmarks = chartmarks.marks[1] end
local symbolmarks = getSymbolMarks(chartmarks)
if chartmarks ~= chartvis then
table.insert(chartvis.marks, symbolmarks)
else
table.insert(marks, symbolmarks)
end
end
-- axes
local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType)
-- legend
local legend
if legendTitle and tonumber(legendTitle) ~= 0 then legend = getLegend(legendTitle, chartType, outerRadius) end
-- construct final output object
local output =
{
version = 2,
width = graphwidth,
height = graphheight,
data = { data, stats },
scales = scales,
axes = { xAxis, yAxis },
marks = marks,
legends = { legend }
}
local flags
if formatJson then flags = mw.text.JSON_PRETTY end
return mw.text.jsonEncode(output, flags)
end
function p.mapWrapper(frame)
return p.map(frame:getParent())
end
function p.chartWrapper(frame)
return p.chart(frame:getParent())
end
-- Given an HTML-encoded title as first argument, e.g. one produced with {{ARTICLEPAGENAME}},
-- convert it into a properly URL path-encoded string
-- This function is critical for any graph that uses path-based APIs, e.g. PageViews graph
function p.encodeTitleForPath(frame)
return mw.uri.encode(mw.text.decode(mw.text.trim(frame.args[1])), 'PATH')
end
return p