Module:Graph
Apparence
[voir] [modifier] [historique] [purger]
Utilisation
Fonctions exportables :
fonction(frame)
– description (courte description defonction(frame)
et autres informations pertinentes).fonction2()
– description2 (courte description defonction2()
et autres informations pertinentes).
Autres fonctions :
fonction()
– description2 (courte description defonction()
et autres informations pertinentes).
Modules externes et autres éléments dont ce module a besoin pour fonctionner :
mw.title
– description (courte description expliquant la dépendance de ce module externe).
Exemples
Pour des exemples, voir la page de test[Où ?] permettant de tester diverses modifications apportées. Voir aussi {{Graph:Lines}} et {{Graph:Chart}}, qui sont les modèles utilisant le module.
Test 1
La documentation de ce module est générée par le modèle {{Documentation module}}.
Elle est incluse depuis sa sous-page de documentation. Veuillez placer les catégories sur cette page-là.
Les éditeurs peuvent travailler dans le bac à sable (modifier).
Voir les statistiques d'appel depuis le wikicode sur l'outil wstat et les appels depuis d'autres modules.
local p = {}
local function numericArray(csv)
if not csv then return end
local list = mw.text.split(mw.ustring.gsub(csv, "%s", ""), ",")
local result = {}
for i = 1, #list do
result[i] = tonumber(list[i])
end
return result
end
local function stringArray(csv)
if not csv then return end
return mw.text.split(mw.ustring.gsub(csv, "%s", ""), ",")
end
local function isTable(t) return type(t) == "table" end
function p.map(frame)
-- Kartendaten
local basemap = mw.title.new(frame.args.basemap or "Wikipedia:Graph/WorldMap-iso2.json")
-- Kartenskalierung (hängt von den Kartendaten ab)
local scale = tonumber(frame.args.scale) or 100
-- Kartenprojektion: siehe https://github.com/mbostock/d3/wiki/Geo-Projections
local projection = frame.args.projection or "equirectangular"
-- Standardfarbe für Staaten ohne Daten
local defaultColor = frame.args.defaultColor or "silver"
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"
-- Legende
local legend = frame.args.legend
-- Kartenmarkierungen als Schlüssel-Wert-Paare: Schlüssel müssen Großbuchstaben, Ziffern oder "-" sein (idealerweise ISO-Codes) und den "id"-Werten der Kartendaten entsprechen
local values = {}
local isNumbers = nil
for name, value in pairs(frame.args) do
local startPos, endPos = string.find(name, "[A-Z0-9\-]+")
if startPos == 1 and endPos == string.len(name) 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
-- Skalen
local scales
if isNumbers then
if colorScale == "category10" or colorScale == "category20" then else colorScale = mw.text.split(colorScale, ",") end
scales =
{
{
name = "color",
type = scaleType,
domain = { data = "highlights", field = "data.v" },
range = colorScale,
nice = true
}
}
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+)") -- auf Exponenten für exponentielle Darstellung prüfen
if exponent then
scales[1].type = "pow"
scales[1].exponent = exponent
end
end
-- Legende
if legend then
legend =
{
{
fill = "color",
properties =
{
title = { fontSize = { value = 14 } },
labels = { fontSize = { value = 12 } },
legend =
{
stroke = { value = "silver" },
strokeWidth = { value = 1.5 }
}
}
}
}
end
local mapNamespace = basemap.nsText
if string.len(mapNamespace) > 0 then mapNamespace = mapNamespace .. ":" end
local output =
{
width = 1, -- Dummywert, da Ausgabegröße von den Kartendaten und der Skalierung abhängt
height = 1, -- dito
data =
{
{
-- Datenquelle der Kartenmarkierungen
name = "highlights",
values = values
},
{
-- Datenquelle der Kartendaten
name = "countries",
url = "/w/index.php?title=" .. mapNamespace .. basemap:partialUrl() .. "&action=raw",
format = { type = "topojson", feature = "countries" },
transform =
{
{
-- geografische Transformation ("geopath") der Kartendaten
type = "geopath",
value = "data", -- Datenquelle
scale = scale, -- Skalierungsfaktor
translate = { 0, 0 }, -- Translation
projection = projection -- Projektionsmethode
},
{
-- Zusammenführung ("zip") mehrerer Datenquellen: hier Kartendaten mit Kartenmarkierungen ("highlights")
type = "zip",
key = "data.id", -- Schlüssel der Kartendaten
with = "highlights", -- Name der Datenquelle der Kartenmarkierungen
withKey = "data.id", -- Schlüssel der Kartenmarkierungen
as = "zipped", -- Ergebnistabellenname
default = { data = { v = defaultColor } } -- Standardaktion für geografische Objekte ohne Entsprechung in unserem Datensatz
}
}
}
},
marks =
{
-- Markierungen ausgeben
{
type = "path",
from = { data = "countries" },
properties =
{
enter = { path = { field = "path" } },
update = { fill = { field = "zipped.data.v" } },
hover = { fill = { value = "darkgrey" } } -- hat außerhalb der Vorschau keine Wirkung, daher kein Extra-Parameter
}
}
}
}
if (scales) then
output.scales = scales
output.marks[1].properties.update.fill.scale = "color"
end
-- Legende
if legend then output.legends = legend end
return mw.text.jsonEncode(output)
end
function p.chart(frame)
-- chart width
local graphwidth = tonumber(frame.args.width)
-- chart height
local graphheight = tonumber(frame.args.height)
-- chart type
local type = frame.args.type or "line"
-- interpolation mode: 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 colors = stringArray(frame.args.colors) or "category10"
-- x and y axis caption
local xTitle = frame.args.xAxisTitle
local yTitle = frame.args.yAxisTitle
-- override x and y axis minimum and maximum
local xMin = tonumber(frame.args.xAxisMin)
local xMax = tonumber(frame.args.xAxisMax)
local yMin = tonumber(frame.args.yAxisMin)
local yMax = tonumber(frame.args.yAxisMax)
-- show legend, optionally caption
local legend = frame.args.legend
-- format JSON output
local formatJSON = frame.args.formatjson
-- get x values
local x = numericArray(frame.args.x)
-- get y values (series)
local y = {}
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
y[yNum] = numericArray(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
-- create data tuples, consisting of series index, x value, y value
local data = { name = "chart", values = {} }
for i = 1, #y do
for j = 1, #x do
if j <= #y[i] then data.values[#data.values + 1] = { series = seriesTitles[i], x = x[j], y = y[i][j] } end
end
end
-- use stacked charts
local stacked = false
local stats
if string.sub(type, 1, 7) == "stacked" then
type = string.sub(type, 8)
if #y > 1 then -- ignore stacked charts if there is only one series
stacked = true
-- calculate statistics of data as stacking requires cumulative y values
stats =
{
name = "stats", source = "chart", transform =
{
{ type = "facet", keys = { "data.x" } },
{ type = "stats", value = "data.y" }
}
}
end
end
-- create scales
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 = "data.x" }
}
if xMin then xscale.domainMin = xMin end
if xMax then xscale.domainMax = xMax end
if xMin or xMax then xscale.clamp = true end
if type == "rect" then xscale.type = "ordinal" end
local yscale =
{
name = "y",
type = "linear",
range = "height",
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 stacked then
yscale.domain = { data = "stats", field = "sum" }
else
yscale.domain = { data = "chart", field = "data.y" }
end
local colorScale =
{
name = "color",
type = "ordinal",
range = colors
}
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
-- decide if lines (strokes) or areas (fills) should be drawn
local colorField
if type == "line" then colorField = "stroke" else colorField = "fill" end
-- create chart markings
local marks =
{
type = type,
properties =
{
-- chart creation event handler
enter =
{
x = { scale = "x", field = "data.x" },
y = { scale = "y", field = "data.y" }
},
-- chart update event handler
update = { },
-- chart hover event handler
hover = { }
}
}
marks.properties.update[colorField] = { scale = "color" }
marks.properties.hover[colorField] = { value = "red" }
if alphaScale then marks.properties.update[colorField .. "Opacity"] = { scale = "transparency" } end
-- for bars and area charts set the lower bound of their areas
if type == "rect" or type == "area" then
if stacked then
-- for stacked charts this lower bound is cumulative/stacking
marks.properties.enter.y2 = { scale = "y", field = "y2" }
else
-- for non-stacking charts the lower bound is the lower bound of the y axis
marks.properties.enter.y2 = { scale = "y", value = 0 }
end
end
-- for bar charts set the width between the bars
if type == "rect" then marks.properties.enter.width = { scale = "x", band = true, offset = -1 } end
-- stacked charts have their own (stacked) y values
if stacked then marks.properties.enter.y.field = "y" end
-- set interpolation mode
if interpolate then marks.properties.enter.interpolate = { value = interpolate } end
if #y == 1 then marks.from = { data = "chart" } else
-- if there are multiple series, connect colors to series
marks.properties.update[colorField].field = "data.series"
if alphaScale then marks.properties.update[colorField .. "Opacity"].field = "data.series" end
-- apply a grouping (facetting) transformation
marks =
{
type = "group",
marks = { marks },
from =
{
data = "chart",
transform =
{
{
type = "facet",
keys = { "data.series" }
}
}
}
}
-- for stacked charts apply a stacking transformation
if stacked then
marks.from.transform[2] = { type = "stack", point = "data.x", height = "data.y" }
end
end
-- create legend
if legend then
legend =
{
{
fill = "color",
stroke = "color",
title = legend
}
}
end
-- construct final output object
local output =
{
width = graphwidth,
height = graphheight,
data = { data, stats },
scales = { xscale, yscale, colorScale, alphaScale },
axes =
{
{
type = "x",
scale = "x",
title = xTitle
},
{
type = "y",
scale = "y",
title = yTitle
}
},
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
return p