Editing Module:Sandbox/Poshpotato/Graph

From Fallen London Wiki

Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.

The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then save the changes below to finish undoing the edit.

Latest revision Your text
Line 1: Line 1:
  
-- ATTENTION: Please edit this code at https://www.mediawiki.org/w/index.php?title=Module:Graph
 
--          This way all wiki languages can stay in sync. Thank you!
 
-- Changes history and TODO's moved to end of script
 
 
local p = {}
 
 
--add debug text to this string with eg. debuglog = debuglog .. "" .. "\n\n"  .. "- " .. debug.traceback() .. "result type: ".. type(result) ..  " result: \n\n" .. mw.dumpObject(result)
 
--invoke chartDebuger() to get graph JSON and this string
 
local debuglog = "Debug " .. "\n\n"
 
 
local baseMapDirectory = "Module:Graph/"
 
local persistentGrey = "#54595d"
 
 
local shapes = {}
 
shapes = {
 
circle = "circle", x= "M-.5,-.5L.5,.5M.5,-.5L-.5,.5" , square = "square",
 
cross = "cross", diamond = "diamond", triangle_up = "triangle-up",
 
triangle_down = "triangle-down", triangle_right = "triangle-right",
 
triangle_left = "triangle-left",
 
banana = "m -0.5281,0.2880 0.0020,0.0192 m 0,0 c 0.1253,0.0543 0.2118,0.0679 0.3268,0.0252 0.1569,-0.0582 0.3663,-0.1636 0.4607,-0.3407 0.0824,-0.1547 0.1202,-0.2850 0.0838,-0.4794 l 0.0111,-0.1498 -0.0457,-0.0015 c -0.0024,0.3045 -0.1205,0.5674 -0.3357,0.7414 -0.1409,0.1139 -0.3227,0.1693 -0.5031,0.1856 m 0,0 c 0.1804,-0.0163 0.3622,-0.0717 0.5031,-0.1856 0.2152,-0.1739 0.3329,-0.4291 0.3357,-0.7414 l -0.0422,0.0079 c 0,0 -0.0099,0.1111 -0.0227,0.1644 -0.0537,0.1937 -0.1918,0.3355 -0.3349,0.4481 -0.1393,0.1089 -0.2717,0.2072 -0.4326,0.2806 l -0.0062,0.0260"
 
  }
 
 
 
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
 
 
local function deserializeXData(serializedX, xType, xMin, xMax)
 
local x
 
 
if not xType 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 = "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 == "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 = "number" end
 
end
 
if 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 = {},
 
transform = {}
 
}
 
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], index = 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 = {},
 
transform = {} }
 
 
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, xScaleType)
 
if chartType == "pie" then return end
 
 
local xscale =
 
{
 
name = "x",
 
range = "width",
 
zero = false, -- do not include zero value
 
nice = true,
 
domain = { data = "chart", field = "x" }
 
}
 
if xScaleType then xscale.type = xScaleType else xscale.type = "linear" end
 
if xMin then xscale.domainMin = tonumber(xMin) end
 
if xMax then xscale.domainMax = tonumber(xMax) end
 
if xMin or xMax then
 
xscale.clamp = true
 
xscale.nice = false
 
end
 
if chartType == "rect" then
 
xscale.type = "band"
 
xscale.zero = nil
 
xscale.nice = nil
 
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
 
if xType and xType ~= "date" and xScaleType ~= "log" then xscale.nice = true end -- force round numbers for x scale, but "log" and "date" scale outputs a wrong "nice" scale
 
return xscale
 
end
 
 
local function getYScale(chartType, stacked, yMin, yMax, yType, yScaleType)
 
if chartType == "pie" then return end
 
 
local yscale =
 
{
 
name = "y",
 
range = "height",
 
-- area charts have the lower boundary of their filling at y=0 (see marks.encode.enter.y2), therefore these need to start at zero
 
zero = chartType ~= "line",
 
nice = true,
 
 
}
 
if yScaleType then yscale.type = yScaleType else yscale.type = "linear" end
 
if yMin then yscale.domainMin = tonumber(yMin) end
 
if yMax then yscale.domainMax = tonumber(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 = "chart", field = "y1" }
 
else
 
yscale.domain = { data = "chart", field = "y" }
 
end
 
 
return yscale
 
end
 
 
local function getColorScale(colors, chartType, xCount, yCount)
 
if not colors then
 
colors = {scheme = "category10"}
 
if (chartType == "pie" and xCount > 10) or yCount > 10 then colors = {scheme = "category20"} else colors = {scheme = "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 getLineScale(linewidths, chartType)
 
local lineScale = {}
 
 
lineScale =
 
    {
 
        name = "line",
 
        type = "ordinal",
 
        range = linewidths,
 
        domain = { data = "chart", field = "series" }
 
    }
 
 
return lineScale
 
end
 
 
local function getSymSizeScale(symSize)
 
local SymSizeScale = {}
 
SymSizeScale =
 
      {
 
        name = "symSize",
 
        type = "ordinal",
 
        range = symSize,
 
        domain = { data = "chart", field = "series" }
 
        }
 
 
return SymSizeScale
 
end
 
 
local function getSymShapeScale(symShape)
 
local SymShapeScale = {}
 
SymShapeScale =
 
      {
 
        name = "symShape",
 
        type = "ordinal",
 
        range = symShape,
 
        domain = { data = "chart", field = "series" }
 
        }
 
 
return SymShapeScale
 
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.encode.enter then plotMarks.encode.enter = {} end
 
plotMarks.encode.enter[colorField] = { scale = "color", field = dataField }
 
 
-- action when cursor is over plot mark: highlight
 
if not plotMarks.encode.hover then plotMarks.encode.hover = {} end
 
plotMarks.encode.hover[colorField] = { value = "red" }
 
 
-- action when cursor leaves plot mark: reset to initial setup
 
if not plotMarks.encode.update then plotMarks.encode.update = {} end
 
plotMarks.encode.update[colorField] = { scale = "color", field = dataField }
 
end
 
 
local function getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale, graphwidth, graphheight)
 
local chartvis =
 
{
 
type = "arc",
 
from = { data = "chart"} ,
 
 
encode =
 
{ enter = {
 
          x = { value = graphwidth / 2},
 
          y = { value = graphheight / 2}
 
        },
 
update = {
 
innerRadius = { value = innerRadius },
 
outerRadius = { value = outerRadius },
 
startAngle = { field = "startAngle" },
 
endAngle = { field = "endAngle" },
 
stroke = { value = "white" },
 
strokeWidth = { value = linewidth or 1 }
 
}
 
}
 
}
 
 
if radiusScale then
 
chartvis.encode.update.outerRadius.scale = radiusScale.name
 
chartvis.encode.update.outerRadius.field = radiusScale.domain.field
 
else
 
chartvis.encode.update.outerRadius.value = outerRadius
 
end
 
 
addInteractionToChartVisualisation(chartvis, "fill", "x")
 
 
return chartvis
 
end
 
 
local function getChartVisualisation(chartType, stacked, colorField, yCount, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, lineScale, interpolate, graphwidth, graphheight)
 
if chartType == "pie" then return getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale, graphwidth, graphheight) end
 
 
local chartvis =
 
{
 
type = chartType,
 
encode =
 
{
 
-- chart creation event handler
 
enter =
 
{
 
x = { scale = "x", field = "x" },
 
y = { scale = "y", field = "y" }
 
}
 
}
 
}
 
addInteractionToChartVisualisation(chartvis, colorField, "series")
 
if colorField == "stroke" then
 
chartvis.encode.enter.strokeWidth = { value = linewidth or 2.5 }
 
if type(lineScale) =="table"  then
 
chartvis.encode.enter.strokeWidth.value = nil
 
chartvis.encode.enter.strokeWidth =
 
{
 
scale = "line",
 
field= "series"
 
}
 
end
 
end
 
 
if interpolate then chartvis.encode.enter.interpolate = { value = interpolate } end
 
 
if alphaScale then chartvis.encode.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.encode.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.encode.enter.y2 = { scale = "y", value = 0 }
 
end
 
end
 
-- for bar charts ...
 
if chartType == "rect" then
 
-- set 1 pixel width between the bars
 
chartvis.encode.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.encode.enter.x.scale = "x"
 
chartvis.encode.enter.x.field = "x"
 
chartvis.encode.enter.width.scale = "series"
 
end
 
end
 
-- stacked charts have their own (stacked) y values
 
if stacked then chartvis.encode.enter.y.field = "layout_start" end
 
 
-- if there are multiple series group these together
 
if yCount == 1 then
 
chartvis.from = { data = "chart" }
 
else
 
chartvis.from = { data = "facet" }
 
-- if there are multiple series, connect colors to series
 
chartvis.encode.enter[colorField].field = "series"
 
if alphaScale then chartvis.encode.update[colorField .. "Opacity"].field = "series" end
 
 
    ---- TODO check? if there are multiple series, connect linewidths to series
 
-- if chartType == "line" then
 
-- chartvis.encode.update.strokeWidth.field = "series"
 
--end
 
-- apply a grouping (facetting) transformation
 
chartvis =
 
{
 
type = "group",
 
from =
 
{ facet=
 
{
 
data = "chart",
 
name = "facet",
 
groupby = "series"
 
}
 
},
 
marks = chartvis
 
}
 
 
-- for stacked charts apply a stacking transformation
 
if stacked then -- TODO must check for non bar
 
--table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "-_id" }, field = "y" } )
 
-- transform goes to data in vega 5
 
chartvis.marks.encode.enter.y = {
 
scale = "y"; field = "y1"
 
}
 
chartvis.marks.encode.enter.y2 = {
 
scale = "y"; field = "y0"
 
}
 
chartvis.marks = {chartvis.marks}
 
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
 
chartvis.from.facet.groupby = "x"
 
chartvis.signals = {{name = "width", update = "bandwidth('x')"}} -- calculation or width for each group od bars
 
chartvis.scales = {{
 
name = "facet_index", type = "band", range = "width",
 
domain = { data = "facet", field = "series" }}}
 
chartvis.encode = {enter = {x = { scale =  "x", field = "x"}}}
 
chartvis.marks.encode.enter.x = { field = "series", scale = "facet_index" }
 
chartvis.marks.encode.enter.width = { scale = "facet_index", band = true }
 
chartvis.marks = {chartvis.marks}
 
else chartvis.marks = {chartvis.marks} end
 
end
 
end
 
 
return chartvis
 
end
 
 
local function getTextMarks(chartvis, chartType, outerRadius, scales, radiusScale, yType, showValues, graphwidth, graphheight)
 
local encode
 
if chartType == "rect" then
 
encode =
 
{
 
x = { scale = chartvis.encode.enter.x.scale, field = chartvis.encode.enter.x.field },
 
y = { scale = chartvis.encode.enter.y.scale, field = chartvis.encode.enter.y.field, offset = -(tonumber(showValues.offset) or -4) },
 
--dx = { scale = chartvis.encode.enter.x.scale, band = true, mult = 0.5 }, -- for horizontal text
 
dy = { scale = chartvis.encode.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 encode.y.offset >= 0 then
 
encode.align.value = "right"
 
encode.fill.value = showValues.fontcolor or "white"
 
else
 
encode.align.value = "left"
 
encode.fill.value = showValues.fontcolor or persistentGrey
 
end
 
elseif chartType == "pie" then
 
encode =
 
{
 
        x = { value = graphwidth / 2},
 
        y = { value = graphheight / 2},
 
radius = { offset = tonumber(showValues.offset) or -15 },
 
theta =  {signal = "(datum.startAngle + datum.endAngle)/2"},
 
fill = { value = showValues.fontcolor or persistentGrey },
 
baseline = { },
 
angle = { },
 
fontSize = { value = tonumber(showValues.fontsize) or math.ceil(outerRadius / 10) }
 
}
 
if (showValues.angle or "midangle") == "midangle" then -- TODO check always true ?
 
encode.align = { value = "center" }
 
encode.angle = { field = "layout_mid", mult = 180.0 / math.pi }
 
 
if encode.radius.offset >= 0 then
 
encode.baseline.value = "bottom"
 
else
 
if not showValues.fontcolor then encode.fill.value = "white" end
 
encode.baseline.value = "middle"
 
end
 
elseif tonumber(showValues.angle) then -- Todo check
 
-- 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)
 
 
encode.align = { scale = alignScale.name, field = "layout_mid" }
 
encode.angle = { value = tonumber(showValues.angle) }
 
encode.baseline.value = "middle"
 
if not tonumber(showValues.offset) then encode.radius.offset = 4 end
 
end
 
 
if radiusScale then
 
encode.radius.scale = radiusScale.name
 
encode.radius.field = radiusScale.domain.field
 
else
 
encode.radius.value = outerRadius
 
end
 
end
 
 
if encode then
 
if showValues.format then
 
local template = "datum.y"
 
if yType == "number" then template = template .. "|number:'" .. showValues.format .. "'"
 
elseif yType == "date" then template = template .. "|time:" .. showValues.format .. "'"
 
end
 
encode.text = { template = "{{" .. template .. "}}" }
 
else
 
encode.text = { field = "y" }
 
end
 
 
local textmarks =
 
{
 
type = "text",
 
encode =
 
{
 
enter = encode
 
}
 
}
 
if chartvis.from then textmarks.from = copy(chartvis.from) end
 
 
return textmarks
 
end
 
end
 
 
local function getSymbolMarks(chartvis, symSize, symShape, symStroke, noFill, alphaScale)
 
 
local symbolmarks
 
symbolmarks =
 
{
 
type = "symbol",
 
encode =
 
{
 
enter =
 
{
 
x = { scale = "x", field = "x" },
 
y = { scale = "y", field = "y" },
 
strokeWidth = { value = symStroke },
 
stroke = { scale = "color", field = "series" },
 
fill = { scale = "color", field = "series" },
 
}
 
}
 
}
 
if type(symShape) == "string" then
 
symbolmarks.encode.enter.shape = { value = symShape }
 
end
 
if type(symShape) == "table" then
 
symbolmarks.encode.enter.shape = { scale = "symShape", field = "series" }
 
end
 
if type(symSize) == "number" then
 
symbolmarks.encode.enter.size = { value = symSize }
 
end
 
if type(symSize) == "table" then
 
symbolmarks.encode.enter.size = { scale = "symSize", field = "series" }
 
end
 
if noFill then
 
symbolmarks.encode.enter.fill = nil
 
end
 
if alphaScale then
 
symbolmarks.encode.enter.fillOpacity =
 
{ scale = "transparency", field = "series" }
 
symbolmarks.encode.enter.strokeOpacity =
 
{ scale = "transparency", field = "series" }
 
end
 
if chartvis.from then symbolmarks.from = copy(chartvis.from) end
 
   
 
return symbolmarks
 
end
 
 
local function getAnnoMarks(chartvis, stroke, fill, opacity)
 
 
local vannolines, hannolines, vannolabels, hannolabels
 
vannolines =
 
{
 
type = "rule",
 
from = { data = "v_anno" },
 
encode =
 
{
 
update =
 
{
 
x = { scale = "x", field = "x" },
 
y = { value = 0 },
 
y2 = {  field = { group = "height" } },
 
strokeWidth = { value = stroke },
 
stroke = { value = persistentGrey },
 
opacity = { value = opacity }
 
}
 
}
 
}
 
 
vannolabels =
 
{
 
type = "text",
 
from = { data = "v_anno" },
 
encode =
 
{
 
update =
 
{
 
x = { scale = "x", field = "x", offset = 3 },
 
y = {  field = { group = "height" }, offset = -3 },
 
text = { field = "label" },
 
baseline = { value = "top" },
 
        angle = { value = -90 },
 
        fill = { value = persistentGrey },
 
        opacity = { value = opacity }
 
}
 
}
 
}
 
 
hannolines =
 
{
 
type = "rule",
 
from = { data = "h_anno" },
 
encode =
 
{
 
update =
 
{
 
y = { scale = "y", field = "y" },
 
x = { value = 0 },
 
x2 = {  field = { group = "width" } },
 
strokeWidth = { value = stroke },
 
stroke = { value = persistentGrey },
 
opacity = { value = opacity }
 
}
 
}
 
}
 
 
hannolabels =
 
{
 
type = "text",
 
from = { data = "h_anno" },
 
encode =
 
{
 
update =
 
{
 
y = { scale = "y", field = "y", offset = 3 },
 
x = {  value = 0 , offset = 3 },
 
text = { field = "label" },
 
baseline = { value = "top" },
 
        angle = { value = 0 },
 
        fill = { value = persistentGrey },
 
        opacity = { value = opacity }
 
}
 
}
 
}
 
 
return vannolines, vannolabels, hannolines, hannolabels
 
end
 
 
local function getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType, graphheight)
 
local xAxis, yAxis
 
if chartType ~= "pie" then
 
if xType == "number" and not xAxisFormat then xAxisFormat = "d" end
 
xAxis =
 
{
 
scale = "x",
 
title = xTitle,
 
format = xAxisFormat,
 
grid = xGrid,
 
-- hard coding required orient values
 
orient = "bottom"
 
 
}
 
if xAxisFormat == "d" then xAxis.tickMinStep = 1 end
 
if xAxisAngle then
 
local xAxisAlign
 
if xAxisAngle < 0 then xAxisAlign = "right" else xAxisAlign = "left" end
 
xAxis.encode =
 
{
 
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.encode =
 
{
 
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 == "number" and not yAxisFormat then yAxisFormat = "d" end
 
yAxis =
 
{
 
scale = "y",
 
title = yTitle,
 
format = yAxisFormat,
 
grid = yGrid,
 
-- hard coding required orient values
 
orient = "left"
 
}
 
if yAxisFormat == "d" then
 
yAxis.tickMinStep = 1
 
if graphheight < 151 then yAxis.tickCount = math.max(math.floor(graphheight/12), 2) end
 
end
 
 
yAxis.encode =
 
{
 
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.titleColor = persistentGrey
 
legend.labelColor = persistentGrey
 
 
if chartType == "pie" then
 
 
legend.orient = "top-right"
 
legend.titleColor = persistentGrey
 
legend.labelColor = persistentGrey
 
 
end
 
return legend
 
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 60
 
-- 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 or frame.args.defaultvalue
 
local scaleType = frame.args.scaleType or frame.args.scaletype
 
-- minimaler Wertebereich (nur für numerische Daten)
 
local domainMin = tonumber(frame.args.domainMin or frame.args.domainmin)
 
-- maximaler Wertebereich (nur für numerische Daten)
 
local domainMax = tonumber(frame.args.domainMax or frame.args.domainmax)
 
-- Farbwerte der Farbskala (nur für numerische Daten)
 
local colorScale = frame.args.colorScale or frame.args.colorscale or "category10"
 
-- show legend
 
local legend = frame.args.legend
 
-- the map feature to display
 
    local feature = frame.args.feature or "countries"
 
    -- map center
 
    local center = numericArray(frame.args.center)
 
-- 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
 
colorScale = {scheme = colorScale}
 
else colorScale = stringArray(colorScale) end
 
scales =
 
{
 
{
 
name = "color",
 
type = scaleType or "linear",
 
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
 
else
 
if colorScale == "category10" or colorScale == "category20" then colorScale = {scheme = colorScale}
 
else colorScale = stringArray(colorScale) end
 
scales =
 
{
 
{
 
name = "color",
 
type = scaleType or "ordinal",
 
domain = { data = "highlights", field = "v" },
 
range = colorScale,
 
 
}
 
}
 
end
 
 
 
 
 
-- create legend
 
if legend then
 
legend =
 
{
 
fill = "color",
 
stroke = "color",
 
title = legendTitle,
 
}
 
legend.titleColor = persistentGrey
 
legend.labelColor = persistentGrey
 
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 =
 
{
 
schema = "https://vega.github.io/schema/vega/v5.json",
 
width = 190, 
 
height = 60, -- fit nicely world map with scale 60
 
projections =
 
{
 
{ name = "projection",
 
value = "data", -- data source
 
scale = scale,
 
                        translate = { 0, 0 },
 
                        center = center,
 
type = projection
 
},
 
},
 
data =
 
{
 
{
 
-- data source for the highlights
 
name = "highlights",
 
values = values
 
},
 
{
 
-- data source for map paths data
 
name = feature,
 
url = basemapUrl,
 
format = { type = "topojson", feature = feature },
 
 
transform =
 
{
 
{
 
-- join ("zip") of mutiple data source: here map paths data and highlights
 
type = "lookup",
 
fields = { "id" },      -- key for map paths data
 
from = "highlights",    -- name of highlight data source
 
key = "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)
 
{ transform = {{type = "geoshape", projection = "projection"}},
 
type = "shape",
 
from = { data = feature },
 
encode =
 
{
 
enter = {stroke = { value = persistentGrey } },
 
update = { fill = { field = "zipped.v" } },
 
hover = { fill = { value = persistentGrey } }
 
}
 
}
 
},
 
legends = { legend}
 
}
 
if (scales) then
 
output.scales = scales
 
output.marks[1].encode.update.fill.scale = "color"
 
end
 
 
local flags
 
if formatJson then flags = mw.text.JSON_PRETTY end
 
JSONtemp = mw.text.jsonEncode(output, flags)
 
-- $ is not allowed in variable name so it need to be added in JSON string
 
JSON = string.gsub(JSONtemp, '\"schema\":\"https://vega.github.io/schema/vega/v5.json\"','\"$schema\":\"https://vega.github.io/schema/vega/v5.json\"',1)
 
return JSON
 
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)
 
local linewidthsString = frame.args.linewidths
 
local linewidths
 
if linewidthsString and linewidthsString ~= "" then linewidths = numericArray(linewidthsString) or false end
 
-- x and y axis caption
 
local xTitle = frame.args.xAxisTitle  or frame.args.xaxistitle
 
local yTitle = frame.args.yAxisTitle  or frame.args.yaxistitle
 
-- x and y value types
 
local xType = frame.args.xType or frame.args.xtype
 
local yType = frame.args.yType or frame.args.ytype
 
-- override x and y axis minimum and maximum
 
local xMin = frame.args.xAxisMin or frame.args.xaxismin
 
local xMax = frame.args.xAxisMax or frame.args.xaxismax
 
local yMin = frame.args.yAxisMin or frame.args.yaxismin
 
local yMax = frame.args.yAxisMax or frame.args.yaxismax
 
-- override x and y axis label formatting
 
local xAxisFormat = frame.args.xAxisFormat or frame.args.xaxisformat
 
local yAxisFormat = frame.args.yAxisFormat or frame.args.yaxisformat
 
local xAxisAngle = tonumber(frame.args.xAxisAngle) or tonumber(frame.args.xaxisangle)
 
-- x and y scale types
 
local xScaleType = frame.args.xScaleType or frame.args.xscaletype
 
local yScaleType = frame.args.yScaleType or frame.args.yscaletype
 
-- log scale require minimum > 0, for now it's no possible to plot negative values on log - TODO see: https://www.mathworks.com/matlabcentral/answers/1792-log-scale-graphic-with-negative-value
 
-- if xScaleType == "log" then
 
-- if (not xMin or tonumber(xMin) <= 0) then xMin = 0.1 end
 
-- if not xType then xType = "number" end
 
-- end
 
-- if yScaleType == "log" then
 
-- if (not yMin or tonumber(yMin) <= 0) then yMin = 0.1 end
 
-- if not yType then yType = "number" end
 
-- end
 
 
-- show grid
 
local xGrid = frame.args.xGrid or frame.args.xgrid or false
 
local yGrid = frame.args.yGrid or frame.args.ygrid or false
 
-- grids fail-safe
 
if xGrid then
 
if xGrid == "0" then xGrid = false
 
elseif xGrid == 0 then xGrid = false
 
elseif xGrid == "false" then xGrid = false
 
elseif xGrid == "n" then xGrid = false
 
else xGrid = true
 
end
 
end
 
if yGrid then
 
if yGrid == "0" then yGrid = false
 
elseif yGrid == 0 then yGrid = false
 
elseif yGrid == "false" then yGrid = false
 
elseif yGrid == "n" then yGrid = false
 
else yGrid = true
 
end
 
end
 
-- for line chart, show a symbol at each data point
 
local showSymbols = frame.args.showSymbols or frame.args.showsymbols
 
local symbolsShape = frame.args.symbolsShape or frame.args.symbolsshape
 
local symbolsNoFill = frame.args.symbolsNoFill or frame.args.symbolsnofill
 
local symbolsStroke = tonumber(frame.args.symbolsStroke or frame.args.symbolsstroke)
 
-- show legend with given title
 
local legendTitle = frame.args.legend
 
-- show values as text
 
local showValues = frame.args.showValues or frame.args.showvalues
 
-- show v- and h-line annotations
 
local v_annoLineString = frame.args.vAnnotatonsLine or frame.args.vannotatonsline
 
local h_annoLineString = frame.args.hAnnotatonsLine or frame.args.hannotatonsline
 
local v_annoLabelString = frame.args.vAnnotatonsLabel or frame.args.vannotatonslabel
 
local h_annoLabelString = frame.args.hAnnotatonsLabel or frame.args.hannotatonslabel
 
-- decode annotations cvs
 
local v_annoLine, v_annoLabel, h_annoLine, h_annoLabel
 
if v_annoLineString and v_annoLineString ~= "" then
 
if xType == "number" then v_annoLine = numericArray(v_annoLineString)
 
else v_annoLine = stringArray(v_annoLineString) end
 
v_annoLabel = stringArray(v_annoLabelString)
 
end
 
if h_annoLineString and h_annoLineString ~= "" then
 
if yType == "number" then h_annoLine = numericArray(h_annoLineString)
 
else h_annoLine = stringArray(h_annoLineString) end
 
h_annoLabel = stringArray(h_annoLabelString)
 
end
 
-- pie chart radiuses
 
local innerRadius = tonumber(frame.args.innerRadius) or tonumber(frame.args.innerradius) or 0
 
local outerRadius = math.min(graphwidth, graphheight) / 2;
 
-- 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 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, transform
 
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 transform in data for stacked charts and pie
 
local stacked = false
 
 
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
 
transform =
 
{{
 
type = "stack",
 
    groupby = {"x"},
 
          sort = { field = "index"},
 
          field = "y"
 
      }}
 
else transform = {} end
 
end
 
if chartType == "pie" then
 
transform = { { field = "y", type = "pie" } }
 
end
 
 
-- add annotations to data
 
local vannoData, hannoData
 
 
if v_annoLine then
 
vannoData = { name = "v_anno", format = { type = "json", parse = { x = xType } }, values = {} }
 
for i = 1, #v_annoLine do
 
local item = { x = v_annoLine[i], label = v_annoLabel[i] }
 
table.insert(vannoData.values, item)
 
end
 
end
 
if h_annoLine then
 
hannoData = { name = "h_anno", format = { type = "json", parse = { y = yType } }, values = {} }
 
for i = 1, #h_annoLine do
 
local item = { y = h_annoLine[i], label = h_annoLabel[i] }
 
table.insert(hannoData.values, item)
 
end
 
end
 
 
 
-- create scales
 
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)
 
 
local colorScale = getColorScale(colors, chartType, #x, #y)
 
table.insert(scales, colorScale)
 
 
local alphaScale = getAlphaColorScale(colors, y)
 
table.insert(scales, alphaScale)
 
 
local lineScale
 
if (linewidths) and (chartType == "line") then
 
lineScale = getLineScale(linewidths, chartType)
 
table.insert(scales, lineScale)
 
end
 
 
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, lineScale, interpolate, graphwidth, graphheight)
 
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, graphwidth, graphheight)
 
if chartmarks ~= chartvis then
 
table.insert(chartvis.marks, textmarks)
 
else
 
table.insert(marks, textmarks)
 
end
 
end
 
 
 
   
 
-- symbol marks
 
if showSymbols and chartType ~= "rect" then
 
local chartmarks = chartvis
 
if chartmarks.marks then chartmarks = chartmarks.marks[1] end
 
 
if type(showSymbols) == "string" then
 
if showSymbols == "" then showSymbols = true
 
else showSymbols = numericArray(showSymbols)
 
end
 
else
 
showSymbols = tonumber(showSymbols)
 
end
 
 
-- custom symbol size
 
local symSize
 
if type(showSymbols) == "number" then
 
symSize = tonumber(showSymbols*showSymbols*8.5)
 
elseif type(showSymbols) == "table" then
 
symSize = {}
 
for k, v in pairs(showSymbols) do
 
                symSize[k]=v*v*8.5 -- "size" acc to Vega syntax is area of symbol
 
            end
 
        else
 
symSize = 50
 
end
 
-- symSizeScale
 
local symSizeScale = {}
 
if type(symSize) == "table" then
 
symSizeScale = getSymSizeScale(symSize)
 
table.insert(scales, symSizeScale)
 
end
 
 
    -- custom shape
 
    if  stringArray(symbolsShape) and #stringArray(symbolsShape) > 1 then symbolsShape = stringArray(symbolsShape) end
 
   
 
    local symShape --= " "
 
 
if type(symbolsShape) == "string" and shapes[symbolsShape] then
 
symShape = shapes[symbolsShape]
 
elseif type(symbolsShape) == "table" then
 
symShape = {}
 
for k, v in pairs(symbolsShape) do
 
                if symbolsShape[k] and shapes[symbolsShape[k]] then
 
                symShape[k]=shapes[symbolsShape[k]]
 
                else
 
                symShape[k] = "circle"
 
                end
 
            end
 
      else
 
symShape = "circle"
 
end
 
-- symShapeScale
 
local symShapeScale = {}
 
if type(symShape) == "table" then
 
symShapeScale = getSymShapeScale(symShape)
 
table.insert(scales, symShapeScale)
 
end
 
 
-- custom stroke
 
local symStroke
 
if (type(symbolsStroke) == "number") then
 
symStroke = tonumber(symbolsStroke)
 
-- TODO symStroke serialization
 
-- elseif type(symbolsStroke) == "table" then
 
-- symStroke = {}
 
-- for k, v in pairs(symbolsStroke) do
 
--                symStroke[k]=symbolsStroke[k]
 
--                --always draw x with stroke
 
-- if symbolsShape[k] == "x" then symStroke[k] = 2.5 end
 
--always draw x with stroke
 
-- if symbolsNoFill[k] then symStroke[k] = 2.5 end
 
--            end
 
else
 
symStroke = 0
 
--always draw x with stroke
 
if symbolsShape == "x" then symStroke = 2.5 end
 
--always draw x with stroke
 
if symbolsNoFill then symStroke = 2.5 end
 
end
 
 
 
-- TODO -- symStrokeScale
 
-- local symStrokeScale = {}
 
-- if type(symStroke) == "table" then
 
-- symStrokeScale = getSymStrokeScale(symStroke)
 
-- table.insert(scales, symStrokeScale)
 
-- end
 
 
local symbolmarks = getSymbolMarks(chartmarks, symSize, symShape, symStroke, symbolsNoFill, alphaScale)
 
if chartmarks ~= chartvis then
 
table.insert(chartvis.marks, symbolmarks)
 
else
 
table.insert(marks, symbolmarks)
 
end
 
end
 
 
local vannolines, vannolabels, hannolines, hannolabels = getAnnoMarks(chartmarks, 2.5, persistentGrey, 0.75)
 
if vannoData then
 
table.insert(marks, vannolines)
 
table.insert(marks, vannolabels)
 
end
 
if hannoData then
 
table.insert(marks, hannolines)
 
table.insert(marks, hannolabels)
 
end
 
 
 
 
-- axes
 
local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType, graphheight)
 
 
-- legend
 
local legend
 
if legendTitle and tonumber(legendTitle) ~= 0 then legend = getLegend(legendTitle, chartType, outerRadius) end
 
if legend and chartType == "pie" and outerRadius < graphwidth/2+100 then graphwidth = graphwidth + 100 end
 
-- construct final output object
 
local output =
 
{
 
schema = "https://vega.github.io/schema/vega/v5.json",
 
width = graphwidth,
 
height = graphheight,
 
data = { data },
 
scales = scales,
 
axes = { xAxis, yAxis},
 
marks = marks,
 
legends = { legend }
 
}
 
if vannoData then table.insert(output.data, vannoData) end
 
if hannoData then table.insert(output.data, hannoData) end
 
if transform then data.transform = transform end -- table.insert(output.data.transform, transform) end
 
 
local flags
 
if formatJson then flags = mw.text.JSON_PRETTY end
 
JSONtemp = mw.text.jsonEncode(output, flags)
 
-- $ is not allowed in variable name so it need to be added in JSON string
 
JSON = string.gsub(JSONtemp, '\"schema\":\"https://vega.github.io/schema/vega/v5.json\"','\"$schema\":\"https://vega.github.io/schema/vega/v5.json\"',1)
 
return JSON
 
end
 
 
function p.mapWrapper(frame)
 
return p.map(frame:getParent())
 
end
 
 
function p.chartWrapper(frame)
 
return p.chart(frame:getParent())
 
end
 
 
function p.chartDebuger(frame)
 
return  "\n\nchart JSON\n ".. p.chart(frame) .. " \n\n" .. debuglog
 
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
 
 
-- BUGS: [check if still exist in Vega 5]
 
-- X-Axis label format bug? (xAxisFormat =) https://en.wikipedia.org/wiki/Template_talk:Graph:Chart#X-Axis_label_format_bug?_(xAxisFormat_=)
 
-- linewidths - doesnt work for two values (eg 0, 1) but work if added third value of both are zeros? Same for marksStroke - probably bug in Graph extension
 
-- Reordering even strings like integers - see https://en.wikipedia.org/wiki/Template_talk:Graph:Chart#Reordering_even_strings_like_integers
 
-- TODO:
 
-- - bugs from Vega 2 - check if still exist in Vega 5
 
-- - marks:
 
-- - line strokeDash + serialization,
 
-- - symStroke serialization
 
-- - symbolsNoFill serialization
 
-- - arbitrary SVG path symbol shape as symbolsShape argument
 
-- - annotations
 
-- - rectangle shape for x,y data range
 
-- - graph type serialization (deep rebuild reqired)
 
--     - second axis (deep rebuild required - assignment of series to one of two axies)
 
 
-- Version History (_PLEASE UPDATE when modifying anything_):
 
--  2023-09-10 Update to Vega 5 (except maps)
 
--  2020-09-01 Vertical and horizontal line annotations
 
--  2020-08-08 New logic for "nice" for x axis (problem with scale when xType = "date") and grid
 
--  2020-06-21 Serializes symbol size
 
--              transparent symbosls (from line colour) - buggy (incorrect opacity on overlap with line)
 
--              Linewidth serialized with "linewidths"
 
--              Variable symbol size and shape of symbols on line charts, default showSymbols = 2, default symbolsShape = circle, symbolsStroke = 0
 
--              p.chartDebuger(frame) for easy debug and JSON output
 
--  2020-06-07 Allow lowercase variables for use with [[Template:Wikidata list]]
 
--  2020-05-27 Map: allow specification which feature to display and changing the map center
 
--  2020-04-08 Change default showValues.fontcolor from black to persistentGrey
 
--  2020-04-06 Logarithmic scale outputs wrong axis labels when "nice"=true
 
--  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-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.
 

Please note that all contributions to Fallen London Wiki are considered to be released under the Creative Commons Attribution-ShareAlike (see Fallen London Wiki:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!

To edit this page, please answer the question that appears below (more info):

Cancel Editing help (opens in new window)
Preview page with this template

Template used on this page: