Module:Graph
![]() | This Lua module is used on approximately 6,900 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
Related pages |
---|
Module with helper functions for the Graph extension to display graphs and maps. From de:Modul:Graph.
Functions for templates
map
Creates a JSON object for <graph>
to display a political map with colored highlights. In the article namespace the template {{Graph:Map}} should be used instead. See its page for use cases.
Maps can be found at Special:Prefixindex/Template:Graph:Map/Inner/ (for example Worldmap2c-json with country borders) and new maps should also be saved under Module:Graph/.
Parameters:
- basemap: sets the base map. The map definitions must follow the TopoJSON format and if saved in Wikipedia are available for this module. Maps in the default directory Special:Prefixindex/Template:Graph:Map/Inner/ like Worldmap2c-json should only be referenced by their name while omitting the Module:Graph/ prefix to allow better portability. The parameter also accepts URLs, e.g. maps from other Wikipedia versions (the link should follow the scheme of
//en.wikipedia.org/w/index.php?title=mapname&action=raw
, i.e. protocol-relative without leading http/s and a trailing action=raw to fetch the raw content only). URLs to maps on external sites should be avoided for the sake of link stability, performance, security, and she be assumed to be blocked by the software or browser anyway. - scale: the scaling factor of the map (default: 100)
- projection: the map projection to use. Supported values are listed at https://github.com/d3/d3-geo-projection. The default value is
equirectangular
for an equirectangular projection. - center: map center (corresponds in the map data to both comma-separated values of the
scale
field) - feature: which geographic objects should be displayed (corresponds in the map data to the name of the field under the
objects
field). The default is valuecountries
. - ids of geographic entities: The actual parameter names depend on the base map and the selected feature. For example, for the above mentioned world map the ids are ISO country codes. The values can be either colors or numbers in case the geographic entities should be associated with numeric data:
DE=lightblue
marks Germany in light blue color, andDE=80.6
assigns Germany the value 80.6 (population in millions). In the latter case, the actual color depends on the following parameters. - colorScale: the color palette to use for the color scale. The palette must be provided as a comma-separated list of color values. The color values must be given either as
#rgb
/#rrggbb
or by a CSS color name. Instead of a list, the built-in color palettescategory10
andcategory20
can also be used. - scaleType: supported values are
linear
for a linear mapping between the data values and the color scale,log
for a log mapping,pow
for a power mapping (the exponent can be provided aspow 0.5
),sqrt
for a square-root mapping, andquantize
for a quantized scale, i.e. the data is grouped in as many classes as the color palette has colors. - domainMin: lower boundary of the data values, i.e. smaller data values are mapped to the lower boundary
- domainMax: upper boundary of the data values, i.e. larger data values are mapped to the upper boundary
- legend: show color legend (does not work with
quantize
) - defaultValue: default value for unused geographic entities. In case the id values are colors the default value is
silver
, in case of numbers it is 0. - formatjson: format JSON object for better legibility
chart
Creates a JSON object for <graph>
to display charts. In the article namespace the template Template:Graph:Chart should be used instead. See its page for use cases.
Parameters:
- width: width of the chart
- height: height of the chart
- type: type of the chart:
line
for line charts,area
for area charts, andrect
for (column) bar charts, andpie
for pie charts. Multiple series can stacked using thestacked
prefix, e.g.stackedarea
. - interpolate: interpolation method for line and area charts. It is recommended to use
monotone
for a monotone cubic interpolation – further supported values are listed at https://github.com/nyurik/vega/wiki/Marks#line. - colors: color palette of the chart as a comma-separated list of colors. The color values must be given either as
#rgb
/#rrggbb
/#aarrggbb
or by a CSS color name. For#aarrggbb
theaa
component denotes the alpha channel, i.e. FF=100% opacity, 80=50% opacity/transparency, etc. (The default color palette if n <= 10 is Category10: else is Category20: ). See Template:ChartColors for details. - xAxisTitle and yAxisTitle: captions of the x and y axes
- xAxisMin, xAxisMax, yAxisMin, and yAxisMax: minimum and maximum values of the x and y axes (not yet supported for bar charts). These parameters can be used to invert the scale of a numeric axis by setting the lowest value to the Max and highest value to the Min.
- xAxisFormat and yAxisFormat: changes the formatting of the axis labels. Supported values are listed at https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#numbers for numbers. For example, the format
%
can be used to output percentages. For date/time specification of supported values is https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md , e.g.xAxisFormat=%d-%m-%Y
for result 13-01-1977. - xAxisAngle: rotates the x axis labels by the specified angle. Recommended values are: -45, +45, -90, +90
- xType and yType: data types of the values, e.g.
integer
for integers,number
for real numbers,date
for dates (e.g. YYYY-MM-DD), andstring
for ordinal values (usestring
to prevent axis values from being repeated when there are only a few values). Remarks:Date
type doesn't work for bar graphs. Fordate
data input please use ISO date format (e.g. YYYY-MM-DD) acc. to date and time formats used in HTML. Other date formats may work but not in all browsers. Date is unfortunately displayed only in en-US format for all Wikipedia languages. Workaround is to use xAxisFormat and yAxisFormat with numerical dates format. - xScaleType and yScaleType: scale types of the x and y axes, e.g.
linear
for linear scale (default),log
for logarithmic scale andsqrt
for square root scale.- A logarithmic chart allows only positive values to be plotted. A square root scale chart cannot show negative values.
- x: the x-values as a comma-separated list, for dates and time see remark in xType and yType
- y or y1, y2, …: the y-values for one or several data series, respectively. For pie charts
y2
denotes the radius of the corresponding sectors. For dates and time see remark in xType and yType - legend: show legend (only works in case of multiple data series)
- y1Title, y2Title, …: defines the label of the respective data series in the legend
- linewidth: line width for line charts or distance between the pie segments for pie charts. Setting to 0 with
type=line
creates a scatter plot. - linewidths: different line widths may be defined for each series of data with csv, if set to 0 with "showSymbols" results with points graph, eg.:
linewidths=1, 0, 5, 0.2
- showSymbols: show symbol on data point for line graphs, if a number is provided, the symbol size (default 2.5) may be defined for each data series, eg.:
showSymbols=1, 2, 3, 4
- symbolsShape: custom shape for symbol: circle, x, square, cross, diamond, triangle_up, triangle_down, triangle_right, triangle_left. May be defined for each series of data with csv, eg.:
symbolsShape= circle, cross, square
- symbolsNoFill: if true symbol will be without fill (only stroke),
- symbolsStroke: if "x" symbol is used or option "symbolsNoFill" symbol stroke width, default 2.5
- showValues: Additionally, output the y values as text. (Currently, only (non-stacked) bar and pie charts are supported.) The output can be configured used the following parameters provided as
name1:value1, name2:value2
(e.g.|showValues=fontcolor:blue,angle:0
).- format: Format the output according to https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#numbers for numbers and https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md for date/time.
- fontcolor: text color
- fontsize: text size
- offset: move text by the given offset. For bar charts and pie charts with
midangle
this also defines if the text is inside or outside the chart. - angle (pie charts only): text angle in degrees or
midangle
(default) for dynamic angles based on the mid-angle of the pie sector.
- innerRadius: For pie charts: defines the inner radius to create a doughnut chart.
- xGrid and yGrid: display grid lines on the x and y axes.
- Annotations
- vAnnotatonsLine and hAnnotatonsLine: display vertical or horizontal annotation lines on specific values e.g.
hAnnotatonsLine=4, 5, 6
- vAnnotatonsLabel and hAnnotatonsLabel: display vertical or horizontal annotation labels for lines e.g.
hAnnotatonsLabel = label1, label2, label3
- vAnnotatonsLine and hAnnotatonsLine: display vertical or horizontal annotation lines on specific values e.g.
- 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.
Note: In the editor preview the graph extension creates a canvas element with vector graphics. However, when saving the page a PNG raster graphics is generated instead. {{#invoke:Graph|function_wrapper_name}}
local p = {}
local baseMapDirectory = "Module:Graph/"
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)
-- map path data for geographic objects
local basemap = frame.args.basemap or "WorldMap-iso2.json"
-- 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]+$") 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 == "category10" or colorScale == "category20" then else colorScale = stringArray(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+)") -- check for exponent
if exponent then
scales[1].type = "pow"
scales[1].exponent = exponent
end
end
-- create legend
if legend then
legend =
{
{
fill = "color",
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, 7) == "http://") or (string.sub(basemap, 1, 8) == "https://") or (string.sub(basemap, 1, 2) == "//") 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 = mw.title.new(basemap):fullUrl("action=raw")
end
local output =
{
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 = "zip",
key = "data.id", -- key for map paths data
with = "highlights", -- name of highlight data source
withKey = "data.id", -- key for highlight data source
as = "zipped", -- name of resulting table
default = { data = { 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 = "path" } },
update = { fill = { field = "zipped.data.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
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)
-- override x and y axis label formatting
local xFormat = frame.args.xAxisFormat
local yFormat = frame.args.yAxisFormat
-- 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",
-- 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 = type ~= "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 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
-- for bar charts with multiple series: each series is grouped by the x value, therefore the series need their own scale within each x group
local groupScale
if type == "rect" and not stacked and #y > 1 then
groupScale =
{
name = "series",
type = "ordinal",
range = "width",
domain = { field = "data.series" }
}
xscale.padding = 0.2 -- pad each bar group
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 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 ]]
marks.properties.enter.y2 = { scale = "y", value = 0 }
end
end
-- for bar charts ...
if type == "rect" then
-- set 1 pixel width between the bars
marks.properties.enter.width = { scale = "x", band = true, offset = -1 }
-- for multiple series the bar marking need to use the "inner" series scale, whereas the "outer" x scale is used by the grouping
if not stacked and #y > 1 then
marks.properties.enter.x.scale = "series"
marks.properties.enter.x.field = "data.series"
marks.properties.enter.width.scale = "series"
end
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" }
else
-- for bar charts the series are side-by-side grouped by x
if type == "rect" then
marks.from.transform[1].keys = "data.x"
marks.scales = { groupScale }
marks.properties = { enter = { x = { field = "key", scale = "x" }, width = { scale = "x", band = true } } }
end
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,
format = xFormat
},
{
type = "y",
scale = "y",
title = yTitle,
format = yFormat
}
},
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