Відмінності між версіями «Модуль:Graph»

нема опису редагування
м (update from enwiki)
--
-- Version History (_PLEASE UPDATE when modifying anything_):
-- 2020-03-11 Allow user-defined scale types, e.g. logarithmic scale
-- 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-09 Add showSymbols param to show symbols on line charts
-- 2016-01-28 For maps, always use wikiraw:// protocol. https:// will be disabled soon.
-- 2016-03-20 Allow omitted data for charts, labels for line charts with string (ordinal) scale at point location
-- 2016-05-16 Added encodeTitleForPath() to help all path-based APIs graphs like pageviews
-- 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)
end
 
local function stringArray(csvtext)
if not csvtext then return end
 
returnlocal list = mw.text.split(csvmw.ustring.gsub(tostring(text), "%s*\\,%s*", "<COMMA>"), ",", true)
for i = 1, #list do
list[i] = mw.ustring.gsub(mw.text.trim(list[i]), "<COMMA>", ",")
end
return list
end
 
return x
end
end
 
function p.map(frame)
-- map path data for geographic objects
local basemap = frame.args.basemap or "Template:Graph:Map/Inner/Worldmap2c-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
 
end
 
local function getXScale(chartType, stacked, xMin, xMax, xType, xScaleType)
if chartType == "pie" then return end
 
{
name = "x",
type = "linear",
range = "width",
zero = false, -- do not include zero value
domain = { data = "chart", field = "x" }
}
if xScaleType then xscale.type = xScaleType else xscale.type = "linear" end
if xMin then xscale.domainMin = xMin end
if xMax then xscale.domainMax = xMax end
end
 
local function getYScale(chartType, stacked, yMin, yMax, yType, yScaleType)
if chartType == "pie" then return end
 
{
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
nice = true
}
if yScaleType then yscale.type = yScaleType else yscale.type = "linear" end
if yMin then yscale.domainMin = yMin end
if yMax then yscale.domainMax = yMax end
end
 
local function getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType)
local xAxis, yAxis
if chartType ~= "pie" then
scale = "x",
title = xTitle,
format = xAxisFormat,
grid = xGrid ~= "0"
}
if xAxisAngle then
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 =
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
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
-- move legend from center position to top
legend.properties = { legend = { y = { value = -outerRadius } } }
legend = {
y = { value = -outerRadius },
},
title = {
fill = { value = persistentGrey }
},
labels = {
fill = { value = persistentGrey },
},
}
end
return legend
local interpolate = frame.args.interpolate
-- mark colors (if no colors are given, the default 10 color palette is used)
local colorscolorString = stringArray(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)
local yAxisFormat = frame.args.yAxisFormat
local xAxisAngle = tonumber(frame.args.xAxisAngle)
-- x and y scale types
local xScaleType = frame.args.xScaleType
local yScaleType = frame.args.yScaleType
-- 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
local scales = {}
 
local xscale = getXScale(chartType, stacked, xMin, xMax, xType, xScaleType)
table.insert(scales, xscale)
local yscale = getYScale(chartType, stacked, yMin, yMax, yType, yScaleType)
table.insert(scales, yscale)
 
 
-- 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
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
 
-- 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
 
32 721

редагування