تضامنًا مع حق الشعب الفلسطيني |
وحدة:Graph2
صُنفت هذه الوحدة على أنها في مرحلة ألفا. تكون الوحدة في هذه المرحلة جاهزة نوعا ما لأن تستخدم من طرف ثالث، أي بمعنى أخر على النطاقات في صفحات التجارب. لكن يجب أن تستخدم في صفحات محددة للغاية وأن لا يوسع إستخدامها ابدا، حتى تنتقل للمرحلة التي بعدها. يجب أن يضاف لهذه الوحدة صفحة توثيق تشرح مدخلاتها وطريقة استخدامها. يجب أن تراقب هذه الوحدة جيدا وملاحظة أي أخطاء قد تحدث والعمل على عزلها وإصلاحها. |
وحدة مع الوظائف المساعدة لاستخدام Graph .
وظائف القوالب
map
ينشىء عنصر JSON لاستخدام <graph> لعرض خريطة سياسية مع الضوء الملونة. في نطاق المقالات يستخدم قالب:Graph:Chart بدلاً من ذلك. انظر صفحة توثيق القالب لحالات الاستخدام.
يمكن إيجاد الخرائط في خاص:Prefixindex/Module:Graph/ (مثال WorldMap-iso2.json لحدود البلدان) وينبغي أيضا حفظ الخرائط الجديدة تحت وحدة:Graph/.
الوسائط:
- 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 خاص:Prefixindex/Module:Graph/ such as WorldMap-iso2.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: حجم الخريطة (افتراضي: 100)
- projection: the إسقاط الخرائط to use. Supported values are listed at https://github.com/mbostock/d3/wiki/Geo-Projections. The default value is equirectangular for an إسقاط متساوي المستطيلات.
- ids of geographic entities: The actual parameter names depend on the base map. For example, for the above mentioned world map the ids are قائمة الدول حسب المعيار الدولي أيزو 3166-1. 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, and DE=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 ألوان الويب. Instead of a list, the built-in color palettes category10 and category20 can also be used.
- scaleType: القيم المدعومة هي:
- 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 as pow 0.5)
- sqrt for a square-root mapping
- quantize for a quantized scale, i.e. the data is grouped in as many classes as the color palette has colors.
- domainMin: الحد الأدنى لقيم البيانات، أي أن قيم البيانات الأصغر حجما يتم تعيينها إلى الحد الأدنى
- domainMax: الحد الأعلى لقيم البيانات، أي أن قيم البيانات الأكبر يتم تعيينها إلى الحد الأعلى
- 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: تنسيق كائن JSON للحصول على أفضل وضوح
Template wrappers
الوظيفة mapWrapper وchartWrapper لتمرير كافة معلمات القالب إلى كل من وظيفتي map و chart .
ملاحظة: في معاينة المحرر تنشىء إضافة Graph canvas element مع الرسومات المتجهة. ومع ذلك، عند حفظ الصفحة يتم إنشاء الرسومات النقطية بصيغة PNG بدلا من ذلك. {{#invoke:Graph2
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 = "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 = "id", -- key for map paths data
with = "highlights", -- name of highlight data source
withKey = "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.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"
-- for line charts, the thickness of the line (strokeWidth)
local linewidth = tonumber(frame.args.linewidth) or 2.5
-- 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 = "aggregate",
groupby = { "x" },
summarize = { y = "sum" }
}
}
}
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 = "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_y" }
else
yscale.domain = { data = "chart", field = "y" }
end
local colorScale =
{
name = "color",
type = "ordinal",
range = colors,
domain = { data = "chart", field = "series" }
}
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 = "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 = "x" },
y = { scale = "y", field = "y" }
},
-- chart update event handler
update = { },
-- chart hover event handler
hover = { }
}
}
if colorField == "stroke" then
marks.properties.enter["strokeWidth"] = { value = linewidth }
end
marks.properties.enter[colorField] = { scale = "color", field = "series" }
marks.properties.update[colorField] = { scale = "color", field = "series" }
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 = "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 ]]
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 = "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 = "layout_start" 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 = "series"
if alphaScale then marks.properties.update[colorField .. "Opacity"].field = "series" end
-- apply a grouping (facetting) transformation
marks =
{
type = "group",
marks = { marks },
from =
{
data = "chart",
transform =
{
{
type = "facet",
groupby = { "series" }
}
}
}
}
-- for stacked charts apply a stacking transformation
if stacked then
table.insert( marks.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "series" }, field = "y" } )
else
-- for bar charts the series are side-by-side grouped by x
if type == "rect" then
marks.from.transform[1].groupby = "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 =
{
version = 2,
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