Content deleted Content added
No edit summary |
Pietrasagh (talk | contribs) synh with Modul:Graph, other symbols shapes, symbols and line-widths serialization, see update notes, lowercase args names, new "nice" and "xGrid" / "yGrid" logic |
||
Line 1:
-- ATTENTION:
--
--
-- BUGS: 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
-- 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
-- TODO:
-- marks:
-- - line strokeDash + serialization,
-- - symStroke serialization
-- - symbolsNoFill serialization
-- - arbitrary SVG path symbol shape as symbolsShape argument
-- - annotations
-- - vertical / horizontal line at specific values
-- - 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-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
Line 20 ⟶ 42:
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 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)
Line 42 ⟶ 78:
end
end
return result, isInteger
end
Line 75 ⟶ 112:
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 or "linear"
-- 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)
-- show legend
local legend = frame.args.legend
local feature = frame.args.feature or "countries"
-- map center
Line 153 ⟶ 190:
}
end
-- get map url
local basemapUrl
Line 327 ⟶ 364:
local xscale =
{
range = "width",
zero = false, -- do not include zero value
___domain = { data = "chart", field = "x" }
}
if xScaleType then xscale.type = xScaleType else xscale.type = "linear" end
if xMin then xscale.domainMin = xMin end
if xMax then xscale.domainMax = xMax end
Line 343 ⟶ 379:
xscale.type = "ordinal"
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"
Line 350 ⟶ 387:
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
Line 359 ⟶ 396:
local yscale =
{
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
Line 366 ⟶ 402:
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 yMax then yscale.domainMax = yMax end
Line 416 ⟶ 453:
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
Line 474 ⟶ 551:
end
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
Line 493 ⟶ 570:
if colorField == "stroke" then
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
Line 533 ⟶ 618:
chartvis.properties.update[colorField].field = "series"
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
chartvis =
Line 663 ⟶ 755:
end
local function getSymbolMarks(chartvis, symSize, symShape, symStroke, noFill, alphaScale)
local symbolmarks
symbolmarks =
{
type = "symbol",
Line 673 ⟶ 767:
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
Line 687 ⟶ 802:
local xAxis, yAxis
if chartType ~= "pie" then
if xType == "integer"
-- if not xAxisFormat then xAxisFormat = "d" end
xAxis =
{
Line 694 ⟶ 810:
title = xTitle,
format = xAxisFormat,
grid = xGrid
}
if xAxisAngle then
Line 752 ⟶ 868:
end
if yType == "integer"
if not yAxisFormat then yAxisFormat = "d" end
yAxis =
{
Line 759 ⟶ 876:
title = yTitle,
format = yAxisFormat,
grid = yGrid
}
yAxis.properties =
Line 785 ⟶ 902:
}
}
end
Line 836 ⟶ 954:
-- 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 then linewidths = numericArray(linewidthsString) 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
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
local yGrid = frame.args.yGrid or
-- 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
-- pie chart radiuses
local innerRadius = tonumber(frame.args.innerRadius) or tonumber(frame.args.innerradius) or 0
local outerRadius = math.min(graphwidth, graphheight)
-- format JSON output
Line 882 ⟶ 1,018:
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
Line 932 ⟶ 1,068:
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
Line 944 ⟶ 1,086:
-- create chart markings
local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, lineScale, interpolate)
local marks = { chartvis }
Line 967 ⟶ 1,109:
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
local chartmarks = chartvis
if chartmarks.marks then chartmarks = chartmarks.marks[1] end
if type(showSymbols) == "string" then
showSymbols = numericArray(showSymbols)
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)
Line 982 ⟶ 1,228:
-- axes
local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xAxisAngle, xType, xGrid, yTitle, yAxisFormat, yType, yGrid, chartType)
-- legend
local legend
if legendTitle and tonumber(legendTitle) ~= 0 then legend = getLegend(legendTitle, chartType, outerRadius) end
-- construct final output object
local output =
Line 1,012 ⟶ 1,257:
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}},
|