יחידה:Graph – הבדלי גרסאות

מויקיפדיה: https://he.wikipedia.org/wiki/%D7%99%D7%97%D7%99%D7%93%D7%94:Graph
 
מ. רובין (שיחה | תרומות)
עדכון מוויקיפדיה
 
שורה 1: שורה 1:
-- ATTENTION: Please edit this code at https://de.wikipedia.org/wiki/Modul:Graph
-- ATTENTION: Please edit this code at https://de.wikipedia.org/wiki/Modul:Graph
--             This way all wiki languages can stay in sync. Thank you!
--         This way all wiki languages can stay in sync. Thank you!
--
--
-- Version History:
-- BUGS: X-Axis label format bug? (xAxisFormat =) https://en.wikipedia.org/wiki/Template_talk:Graph:Chart#X-Axis_label_format_bug?_(xAxisFormat_=)
--   2016-01-09 _PLEASE UPDATE when modifying anything_
-- 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
-- clamp - "clamp" used to avoid marks outside marks area, "clip" should be use instead but not working in Graph extension, see https://phabricator.wikimedia.org/T251709
-- Reordering even strings like integers - see https://en.wikipedia.org/wiki/Template_talk:Graph:Chart#Reordering_even_strings_like_integers
-- TODO: 
-- marks:
-- - line strokeDash + serialization,
-- - symStroke serialization
-- - symbolsNoFill serialization
-- - arbitrary SVG path symbol shape as symbolsShape argument
-- - annotations
-- - vertical / horizontal line at specific values [DONE] 2020-09-01
-- - 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_):
--  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.
--  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


local p = {}
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
debuglog = "Debug " .. "\n\n"


local baseMapDirectory = "Module:Graph/"
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)
local function numericArray(csv)
if not csv then return end
if not csv then return end
local list = mw.text.split(csv, "%s*,%s*")
local list = mw.text.split(csv, "%s*,%s*")
local result = {}
local result = {}
local isInteger = true
local isInteger = true
for i = 1, #list do
for i = 1, #list do
if list[i] == "" then result[i] = nil else
if list[i] == "" then
result[i] = nil
else
result[i] = tonumber(list[i])
result[i] = tonumber(list[i])
if not result[i] then return end
if not result[i] then return end
שורה 28: שורה 79:
end
end
end
end
return result, isInteger
 
    return result, isInteger
end
end


local function stringArray(csv)
local function stringArray(text)
if not csv then return end
if not text then return end


return mw.text.split(csv, "%s*,%s*")
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
end


שורה 51: שורה 107:
function p.map(frame)
function p.map(frame)
-- map path data for geographic objects
-- map path data for geographic objects
local basemap = frame.args.basemap or "WorldMap-iso2.json"
local basemap = frame.args.basemap or "WorldMap-iso2.json" -- WorldMap name and/or location may vary from wiki to wiki
-- scaling factor
-- scaling factor
local scale = tonumber(frame.args.scale) or 100
local scale = tonumber(frame.args.scale) or 100
שורה 57: שורה 113:
local projection = frame.args.projection or "equirectangular"
local projection = frame.args.projection or "equirectangular"
-- defaultValue for geographic objects without data
-- defaultValue for geographic objects without data
local defaultValue = frame.args.defaultValue
local defaultValue = frame.args.defaultValue or frame.args.defaultvalue
local scaleType = frame.args.scaleType or "linear"
local scaleType = frame.args.scaleType or frame.args.scaletype or "linear"
-- minimaler Wertebereich (nur für numerische Daten)
-- minimaler Wertebereich (nur für numerische Daten)
local domainMin = tonumber(frame.args.domainMin)
local domainMin = tonumber(frame.args.domainMin or frame.args.domainmin)
-- maximaler Wertebereich (nur für numerische Daten)
-- maximaler Wertebereich (nur für numerische Daten)
local domainMax = tonumber(frame.args.domainMax)
local domainMax = tonumber(frame.args.domainMax or frame.args.domainmax)
-- Farbwerte der Farbskala (nur für numerische Daten)
-- Farbwerte der Farbskala (nur für numerische Daten)
local colorScale = frame.args.colorScale or "category10"
local colorScale = frame.args.colorScale or frame.args.colorscale or "category10"
-- show legend
-- show legend
local legend = frame.args.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
-- format JSON output
local formatJson = frame.args.formatjson
local formatJson = frame.args.formatjson
שורה 74: שורה 134:
local isNumbers = nil
local isNumbers = nil
for name, value in pairs(frame.args) do
for name, value in pairs(frame.args) do
if mw.ustring.find(name, "^[^%l]+$") then
if mw.ustring.find(name, "^[^%l]+$") and value and value ~= "" then
if isNumbers == nil then isNumbers = tonumber(value) end
if isNumbers == nil then isNumbers = tonumber(value) end
local data = { id = name, v = value }
local data = { id = name, v = value }
שורה 88: שורה 148:
local scales
local scales
if isNumbers then
if isNumbers then
if colorScale then colorScale = string.lower(colorScale) end
if colorScale == "category10" or colorScale == "category20" then else colorScale = stringArray(colorScale) end
if colorScale == "category10" or colorScale == "category20" then else colorScale = stringArray(colorScale) end
scales =
scales =
שורה 96: שורה 157:
domain = { data = "highlights", field = "v" },
domain = { data = "highlights", field = "v" },
range = colorScale,
range = colorScale,
nice = true
nice = true,
zero = false
}
}
}
}
שורה 129: שורה 191:
}
}
end
end
 
-- get map url
-- get map url
local basemapUrl
local basemapUrl
שורה 154: שורה 216:
{
{
-- data source for map paths data
-- data source for map paths data
name = "countries",
name = feature,
url = basemapUrl,
url = basemapUrl,
format = { type = "topojson", feature = "countries" },
format = { type = "topojson", feature = feature },
transform =
transform =
{
{
שורה 164: שורה 226:
value = "data", -- data source
value = "data", -- data source
scale = scale,
scale = scale,
translate = { 0, 0 },
                        translate = { 0, 0 },
                        center = center,
projection = projection
projection = projection
},
},
שורה 184: שורה 247:
{
{
type = "path",
type = "path",
from = { data = "countries" },
from = { data = feature },
properties =
properties =
{
{
שורה 225: שורה 288:
if not xType then xType = "string" end
if not xType then xType = "string" end
end
end
return x, xType, xMin, xMax
return x, xType, xMin, xMax
end
end
שורה 297: שורה 359:
end
end


local function getXScale(chartType, stacked, xMin, xMax, xType)
local function getXScale(chartType, stacked, xMin, xMax, xType, xScaleType)
if chartType == "pie" then return end
if chartType == "pie" then return end


שורה 303: שורה 365:
{
{
name = "x",
name = "x",
type = "linear",
range = "width",
range = "width",
zero = false, -- do not include zero value
zero = false, -- do not include zero value
nice = true,  -- force round numbers for y scale
domain = { data = "chart", field = "x" }
domain = { data = "chart", field = "x" }
}
}
if xScaleType then xscale.type = xScaleType else xscale.type = "linear" end
if xMin then xscale.domainMin = xMin end
if xMin then xscale.domainMin = xMin end
if xMax then xscale.domainMax = xMax end
if xMax then xscale.domainMax = xMax end
if xMin or xMax then xscale.clamp = true end
if xMin or xMax then
xscale.clamp = true
xscale.nice = false
end
if chartType == "rect" then
if chartType == "rect" then
xscale.type = "ordinal"
xscale.type = "ordinal"
if not stacked then xscale.padding = 0.2 end -- pad each bar group
if not stacked then xscale.padding = 0.2 end -- pad each bar group
else
else  
if xType == "date" then xscale.type = "time"
if xType == "date" then  
xscale.type = "time"
elseif xType == "string" then
elseif xType == "string" then
xscale.type = "ordinal"
xscale.type = "ordinal"
שורה 322: שורה 387:
end
end
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
return xscale
end
end


local function getYScale(chartType, stacked, yMin, yMax, yType)
local function getYScale(chartType, stacked, yMin, yMax, yType, yScaleType)
if chartType == "pie" then return end
if chartType == "pie" then return end


שורה 332: שורה 397:
{
{
name = "y",
name = "y",
type = "linear",
range = "height",
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
-- 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 = chartType ~= "line",
zero = chartType ~= "line",
nice = true
nice = yScaleType ~= "log" -- force round numbers for y scale, but log scale outputs a wrong "nice" scale
}
}
if yScaleType then yscale.type = yScaleType else yscale.type = "linear" end
if yMin then yscale.domainMin = yMin end
if yMin then yscale.domainMin = yMin end
if yMax then yscale.domainMax = yMax end
if yMax then yscale.domainMax = yMax end
שורה 388: שורה 453:
end
end
return alphaScale
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
end


שורה 446: שורה 551:
end
end


local function getChartVisualisation(chartType, stacked, colorField, yCount, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate)
local function getChartVisualisation(chartType, stacked, colorField, yCount, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, lineScale, interpolate)
if chartType == "pie" then return getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale) end
if chartType == "pie" then return getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale) end


שורה 465: שורה 570:
if colorField == "stroke" then
if colorField == "stroke" then
chartvis.properties.enter.strokeWidth = { value = linewidth or 2.5 }
chartvis.properties.enter.strokeWidth = { value = linewidth or 2.5 }
if type(lineScale) =="table"  then
chartvis.properties.enter.strokeWidth.value = nil
chartvis.properties.enter.strokeWidth =
{
scale = "line",
field= "series"
}
end
end
end


שורה 505: שורה 618:
chartvis.properties.update[colorField].field = "series"
chartvis.properties.update[colorField].field = "series"
if alphaScale then chartvis.properties.update[colorField .. "Opacity"].field = "series" end
if alphaScale then chartvis.properties.update[colorField .. "Opacity"].field = "series" end
    -- if there are multiple series, connect linewidths to series
if chartype == "line" then
chartvis.properties.update["strokeWidth"].field = "series"
end
-- apply a grouping (facetting) transformation
-- apply a grouping (facetting) transformation
chartvis =
chartvis =
שורה 524: שורה 644:
-- for stacked charts apply a stacking transformation
-- for stacked charts apply a stacking transformation
if stacked then
if stacked then
table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "series" }, field = "y" } )
table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "-_id" }, field = "y" } )
else
else
-- for bar charts the series are side-by-side grouped by x
-- for bar charts the series are side-by-side grouped by x
שורה 567: שורה 687:
else
else
properties.align.value = "left"
properties.align.value = "left"
properties.fill.value = showValues.fontcolor or "black"
properties.fill.value = showValues.fontcolor or persistentGrey
end
end
elseif chartType == "pie" then
elseif chartType == "pie" then
שורה 576: שורה 696:
radius = { offset = tonumber(showValues.offset) or -4 },
radius = { offset = tonumber(showValues.offset) or -4 },
theta = { field = "layout_mid" },
theta = { field = "layout_mid" },
fill = { value = showValues.fontcolor or "black" },
fill = { value = showValues.fontcolor or persistentGrey },
baseline = { },
baseline = { },
angle = { },
angle = { },
שורה 635: שורה 755:
end
end


local function getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType)
local function getSymbolMarks(chartvis, symSize, symShape, symStroke, noFill, alphaScale)
 
local symbolmarks
symbolmarks =
{
type = "symbol",
properties =
{
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.properties.enter.shape = { value = symShape }
end
if type(symShape) == "table" then
symbolmarks.properties.enter.shape = { scale = "symShape", field = "series" }
end
if type(symSize) == "number" then
symbolmarks.properties.enter.size = { value = symSize }
end
if type(symSize) == "table" then
symbolmarks.properties.enter.size = { scale = "symSize", field = "series" }
end
if noFill then
symbolmarks.properties.enter.fill = nil
end
if alphaScale then
symbolmarks.properties.enter.fillOpacity =
{ scale = "transparency", field = "series" }
symbolmarks.properties.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, vannoLabels
vannolines =
{
type = "rule",
from = { data = "v_anno" },
properties =
{
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" },
properties =
{
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" },
properties =
{
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" },
properties =
{
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)
local xAxis, yAxis
local xAxis, yAxis
if chartType ~= "pie" then
if chartType ~= "pie" then
שורה 644: שורה 888:
scale = "x",
scale = "x",
title = xTitle,
title = xTitle,
format = xAxisFormat
format = xAxisFormat,
grid = xGrid
}
}
if xAxisAngle then
local xAxisAlign
if xAxisAngle < 0 then xAxisAlign = "right" else xAxisAlign = "left" end
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
if yType == "integer" and not yAxisFormat then yAxisFormat = "d" end
שורה 653: שורה 953:
scale = "y",
scale = "y",
title = yTitle,
title = yTitle,
format = yAxisFormat
format = yAxisFormat,
grid = yGrid
}
}
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
end


שורה 666: שורה 992:
stroke = "color",
stroke = "color",
title = legendTitle,
title = legendTitle,
}
legend.properties = {
title = {
fill = { value = persistentGrey },
},
labels = {
fill = { value = persistentGrey },
},
}
}
if chartType == "pie" then
if chartType == "pie" then
-- move legend from center position to top
legend.properties = {
legend.properties = { legend = { y = { value = -outerRadius } } }
-- move legend from center position to top
legend = {
y = { value = -outerRadius },
},
title = {
fill = { value = persistentGrey }
},
labels = {
fill = { value = persistentGrey },
},
}
end
end
return legend
return legend
שורה 683: שורה 1,027:
local interpolate = frame.args.interpolate
local interpolate = frame.args.interpolate
-- mark colors (if no colors are given, the default 10 color palette is used)
-- mark colors (if no colors are given, the default 10 color palette is used)
local colors = stringArray(frame.args.colors)
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
-- for line charts, the thickness of the line; for pie charts the gap between each slice
local linewidth = tonumber(frame.args.linewidth)
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
-- x and y axis caption
local xTitle = frame.args.xAxisTitle
local xTitle = frame.args.xAxisTitle or frame.args.xaxistitle
local yTitle = frame.args.yAxisTitle
local yTitle = frame.args.yAxisTitle or frame.args.yaxistitle
-- x and y value types
-- x and y value types
local xType = frame.args.xType
local xType = frame.args.xType or frame.args.xtype
local yType = frame.args.yType
local yType = frame.args.yType or frame.args.ytype
-- override x and y axis minimum and maximum
-- override x and y axis minimum and maximum
local xMin = frame.args.xAxisMin
local xMin = frame.args.xAxisMin or frame.args.xaxismin
local xMax = frame.args.xAxisMax
local xMax = frame.args.xAxisMax or frame.args.xaxismax
local yMin = frame.args.yAxisMin
local yMin = frame.args.yAxisMin or frame.args.yaxismin
local yMax = frame.args.yAxisMax
local yMax = frame.args.yAxisMax or frame.args.yaxismax
-- override x and y axis label formatting
-- override x and y axis label formatting
local xAxisFormat = frame.args.xAxisFormat
local xAxisFormat = frame.args.xAxisFormat or frame.args.xaxisformat
local yAxisFormat = frame.args.yAxisFormat
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
-- 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
-- show legend with given title
local legendTitle = frame.args.legend
local legendTitle = frame.args.legend
-- show values as text
-- show values as text
local showValues = frame.args.showValues
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" or xType == "integer" 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" or yType == "integer" then
h_annoLine = numericArray(h_annoLineString)
 
else
h_annoLine = stringArray(h_annoLineString)
 
end
h_annoLabel = stringArray(h_annoLabelString)
end
 
 
 
 
 
-- pie chart radiuses
-- pie chart radiuses
local innerRadius = tonumber(frame.args.innerRadius) or 0
local innerRadius = tonumber(frame.args.innerRadius) or tonumber(frame.args.innerradius) or 0
local outerRadius = math.min(graphwidth, graphheight)
local outerRadius = math.min(graphwidth, graphheight)
-- format JSON output
-- format JSON output
שורה 723: שורה 1,133:
yValues[yNum] = value
yValues[yNum] = value
-- name the series: default is "y<number>". Can be overwritten using the "y<number>Title" parameters.
-- 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
seriesTitles[yNum] = frame.args["y" .. yNum .. "Title"] or frame.args["y" .. yNum .. "title"] or name
end
end
end
end
שורה 759: שורה 1,169:
end
end
end
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
-- create scales
local scales = {}
local scales = {}


local xscale = getXScale(chartType, stacked, xMin, xMax, xType)
local xscale = getXScale(chartType, stacked, xMin, xMax, xType, xScaleType)
table.insert(scales, xscale)
table.insert(scales, xscale)
local yscale = getYScale(chartType, stacked, yMin, yMax, yType)
local yscale = getYScale(chartType, stacked, yMin, yMax, yType, yScaleType)
table.insert(scales, yscale)
table.insert(scales, yscale)


שורה 773: שורה 1,202:
local alphaScale = getAlphaColorScale(colors, y)
local alphaScale = getAlphaColorScale(colors, y)
table.insert(scales, alphaScale)
table.insert(scales, alphaScale)
local lineScale
if (linewidths) and (chartType == "line") then
lineScale = getLineScale(linewidths, chartType)
table.insert(scales, lineScale)
end


local radiusScale
local radiusScale
שורה 783: שורה 1,218:
local colorField
local colorField
if chartType == "line" then colorField = "stroke" else colorField = "fill" end
if chartType == "line" then colorField = "stroke" else colorField = "fill" end


-- create chart markings
-- create chart markings
local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate)
local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, lineScale, interpolate)
 
local marks = { chartvis }
-- text marks
-- text marks
local textmarks
if showValues then
if showValues then
if type(showValues) == "string" then -- deserialize as table
if type(showValues) == "string" then -- deserialize as table
local keyValues = mw.text.split(showValues, "%s*,%s*")
local keyValues = mw.text.split(showValues, "%s*,%s*")
showValues = {}
showValues = {}
for _, kv in ipairs(keyValues) do
for _, kv in ipairs(keyValues) do
local key, value = mw.ustring.match(kv, "^%s*(.-)%s*:%s*(.-)%s*$")
local key, value = mw.ustring.match(kv, "^%s*(.-)%s*:%s*(.-)%s*$")
if key then showValues[key] = value end
if key then showValues[key] = value end
end
end
end
end


local chartmarks = chartvis
local chartmarks = chartvis
if chartmarks.marks then chartmarks = chartmarks.marks[1] end
if chartmarks.marks then chartmarks = chartmarks.marks[1] end
textmarks = getTextMarks(chartmarks, chartType, outerRadius, scales, radiusScale, yType, showValues)
local textmarks = getTextMarks(chartmarks, chartType, outerRadius, scales, radiusScale, yType, showValues)
if chartmarks ~= chartvis then
if chartmarks ~= chartvis then
table.insert(chartvis.marks, textmarks)
table.insert(chartvis.marks, textmarks)
textmarks = nil
else
table.insert(marks, textmarks)
end
end
end
end
    -- grids
    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
   
-- 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 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, persistentGrey, 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
-- axes
local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType)
local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType)
 
-- legend
-- legend
local legend
local legend
if legendTitle then legend = getLegend(legendTitle, chartType, outerRadius) end
if legendTitle and tonumber(legendTitle) ~= 0 then legend = getLegend(legendTitle, chartType, outerRadius) end
 
-- construct final output object
-- construct final output object
local output =
local output =
שורה 821: שורה 1,387:
width = graphwidth,
width = graphwidth,
height = graphheight,
height = graphheight,
data = { data, stats },
data = { data },
scales = scales,
scales = scales,
axes = { xAxis, yAxis },
axes = { xAxis, yAxis },
marks = { chartvis, textmarks },
marks = marks,
legends = { legend }
legends = { legend }
}
}
 
if vannoData then table.insert(output.data, vannoData) end
if hannoData then table.insert(output.data, hannoData) end
if stats then table.insert(output.data, stats) end
local flags
local flags
if formatJson then flags = mw.text.JSON_PRETTY end
if formatJson then flags = mw.text.JSON_PRETTY end
שורה 840: שורה 1,409:
return p.chart(frame:getParent())
return p.chart(frame:getParent())
end
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}},
-- Given an HTML-encoded title as first argument, e.g. one produced with {{ARTICLEPAGENAME}},