<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3AGraph</id>
	<title>Module:Graph - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3AGraph"/>
	<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:Graph&amp;action=history"/>
	<updated>2026-04-06T02:53:09Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>https://linguifex.com/w/index.php?title=Module:Graph&amp;diff=152276&amp;oldid=prev</id>
		<title>Chrysophylax: loaned from mediawiki ;-)</title>
		<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:Graph&amp;diff=152276&amp;oldid=prev"/>
		<updated>2019-05-05T00:28:08Z</updated>

		<summary type="html">&lt;p&gt;loaned from mediawiki ;-)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- ATTENTION:  Please edit this code at https://de.wikipedia.org/wiki/Modul:Graph&lt;br /&gt;
--             This way all wiki languages can stay in sync. Thank you!&lt;br /&gt;
--&lt;br /&gt;
-- Version History:&lt;br /&gt;
--   2016-01-09 _PLEASE UPDATE when modifying anything_&lt;br /&gt;
--   2016-01-28 For maps, always use wikiraw:// protocol. https:// will be disabled soon.&lt;br /&gt;
--   2016-03-20 Allow omitted data for charts, labels for line charts with string (ordinal) scale at point location&lt;br /&gt;
--   2016-05-16 Added encodeTitleForPath() to help all path-based APIs graphs like pageviews&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local baseMapDirectory = &amp;quot;Module:Graph/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local function numericArray(csv)&lt;br /&gt;
	if not csv then return end&lt;br /&gt;
&lt;br /&gt;
	local list = mw.text.split(csv, &amp;quot;%s*,%s*&amp;quot;)&lt;br /&gt;
	local result = {}&lt;br /&gt;
	local isInteger = true&lt;br /&gt;
	for i = 1, #list do&lt;br /&gt;
		if list[i] == &amp;quot;&amp;quot; then result[i] = nil else&lt;br /&gt;
			result[i] = tonumber(list[i])&lt;br /&gt;
			if not result[i] then return end&lt;br /&gt;
			if isInteger then&lt;br /&gt;
				local int, frac = math.modf(result[i])&lt;br /&gt;
				isInteger = frac == 0.0&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return result, isInteger&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function stringArray(csv)&lt;br /&gt;
	if not csv then return end&lt;br /&gt;
&lt;br /&gt;
	return mw.text.split(csv, &amp;quot;%s*,%s*&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function isTable(t) return type(t) == &amp;quot;table&amp;quot; end&lt;br /&gt;
&lt;br /&gt;
local function copy(x)&lt;br /&gt;
	if type(x) == &amp;quot;table&amp;quot; then&lt;br /&gt;
		local result = {}&lt;br /&gt;
		for key, value in pairs(x) do result[key] = copy(value) end&lt;br /&gt;
		return result&lt;br /&gt;
	else&lt;br /&gt;
		return x&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.map(frame)&lt;br /&gt;
	-- map path data for geographic objects&lt;br /&gt;
	local basemap = frame.args.basemap or &amp;quot;WorldMap-iso2.json&amp;quot;&lt;br /&gt;
	-- scaling factor&lt;br /&gt;
	local scale = tonumber(frame.args.scale) or 100&lt;br /&gt;
	-- map projection, see https://github.com/mbostock/d3/wiki/Geo-Projections&lt;br /&gt;
	local projection = frame.args.projection or &amp;quot;equirectangular&amp;quot;&lt;br /&gt;
	-- defaultValue for geographic objects without data&lt;br /&gt;
	local defaultValue = frame.args.defaultValue&lt;br /&gt;
	local scaleType = frame.args.scaleType or &amp;quot;linear&amp;quot;&lt;br /&gt;
	-- minimaler Wertebereich (nur für numerische Daten)&lt;br /&gt;
	local domainMin = tonumber(frame.args.domainMin)&lt;br /&gt;
	-- maximaler Wertebereich (nur für numerische Daten)&lt;br /&gt;
	local domainMax = tonumber(frame.args.domainMax)&lt;br /&gt;
	-- Farbwerte der Farbskala (nur für numerische Daten)&lt;br /&gt;
	local colorScale = frame.args.colorScale or &amp;quot;category10&amp;quot;&lt;br /&gt;
	-- show legend&lt;br /&gt;
	local legend = frame.args.legend&lt;br /&gt;
	-- format JSON output&lt;br /&gt;
	local formatJson = frame.args.formatjson&lt;br /&gt;
&lt;br /&gt;
	-- map data are key-value pairs: keys are non-lowercase strings (ideally ISO codes) which need to match the &amp;quot;id&amp;quot; values of the map path data&lt;br /&gt;
	local values = {}&lt;br /&gt;
	local isNumbers = nil&lt;br /&gt;
	for name, value in pairs(frame.args) do&lt;br /&gt;
		if mw.ustring.find(name, &amp;quot;^[^%l]+$&amp;quot;) then&lt;br /&gt;
			if isNumbers == nil then isNumbers = tonumber(value) end&lt;br /&gt;
			local data = { id = name, v = value }&lt;br /&gt;
			if isNumbers then data.v = tonumber(data.v) end&lt;br /&gt;
			table.insert(values, data)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if not defaultValue then&lt;br /&gt;
		if isNumbers then defaultValue = 0 else defaultValue = &amp;quot;silver&amp;quot; end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- create highlight scale&lt;br /&gt;
	local scales&lt;br /&gt;
	if isNumbers then&lt;br /&gt;
		if colorScale == &amp;quot;category10&amp;quot; or colorScale == &amp;quot;category20&amp;quot; then else colorScale = stringArray(colorScale) end&lt;br /&gt;
		scales =&lt;br /&gt;
		{&lt;br /&gt;
			{&lt;br /&gt;
				name = &amp;quot;color&amp;quot;,&lt;br /&gt;
				type = scaleType,&lt;br /&gt;
				domain = { data = &amp;quot;highlights&amp;quot;, field = &amp;quot;v&amp;quot; },&lt;br /&gt;
				range = colorScale,&lt;br /&gt;
				nice = true&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
		if domainMin then scales[1].domainMin = domainMin end&lt;br /&gt;
		if domainMax then scales[1].domainMax = domainMax end&lt;br /&gt;
&lt;br /&gt;
		local exponent = string.match(scaleType, &amp;quot;pow%s+(%d+%.?%d+)&amp;quot;) -- check for exponent&lt;br /&gt;
		if exponent then&lt;br /&gt;
			scales[1].type = &amp;quot;pow&amp;quot;&lt;br /&gt;
			scales[1].exponent = exponent&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- create legend&lt;br /&gt;
	if legend then&lt;br /&gt;
		legend =&lt;br /&gt;
		{&lt;br /&gt;
			{&lt;br /&gt;
				fill = &amp;quot;color&amp;quot;,&lt;br /&gt;
				offset = 120,&lt;br /&gt;
				properties =&lt;br /&gt;
				{&lt;br /&gt;
					title = { fontSize = { value = 14 } },&lt;br /&gt;
					labels = { fontSize = { value = 12 } },&lt;br /&gt;
					legend =&lt;br /&gt;
					{&lt;br /&gt;
						stroke = { value = &amp;quot;silver&amp;quot; },&lt;br /&gt;
						strokeWidth = { value = 1.5 }&lt;br /&gt;
					}&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- get map url&lt;br /&gt;
	local basemapUrl&lt;br /&gt;
	if (string.sub(basemap, 1, 10) == &amp;quot;wikiraw://&amp;quot;) then&lt;br /&gt;
		basemapUrl = basemap&lt;br /&gt;
	else&lt;br /&gt;
		-- if not a (supported) url look for a colon as namespace separator. If none prepend default map directory name.&lt;br /&gt;
		if not string.find(basemap, &amp;quot;:&amp;quot;) then basemap = baseMapDirectory .. basemap end&lt;br /&gt;
		basemapUrl = &amp;quot;wikiraw:///&amp;quot; .. mw.uri.encode(mw.title.new(basemap).prefixedText, &amp;quot;PATH&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local output =&lt;br /&gt;
	{&lt;br /&gt;
		version = 2,&lt;br /&gt;
		width = 1,  -- generic value as output size depends solely on map size and scaling factor&lt;br /&gt;
		height = 1, -- ditto&lt;br /&gt;
		data =&lt;br /&gt;
		{&lt;br /&gt;
			{&lt;br /&gt;
				-- data source for the highlights&lt;br /&gt;
				name = &amp;quot;highlights&amp;quot;,&lt;br /&gt;
				values = values&lt;br /&gt;
			},&lt;br /&gt;
			{&lt;br /&gt;
				-- data source for map paths data&lt;br /&gt;
				name = &amp;quot;countries&amp;quot;,&lt;br /&gt;
				url = basemapUrl,&lt;br /&gt;
				format = { type = &amp;quot;topojson&amp;quot;, feature = &amp;quot;countries&amp;quot; },&lt;br /&gt;
				transform =&lt;br /&gt;
				{&lt;br /&gt;
					{&lt;br /&gt;
						-- geographic transformation (&amp;quot;geopath&amp;quot;) of map paths data&lt;br /&gt;
						type = &amp;quot;geopath&amp;quot;,&lt;br /&gt;
						value = &amp;quot;data&amp;quot;,			-- data source&lt;br /&gt;
						scale = scale,&lt;br /&gt;
						translate = { 0, 0 },&lt;br /&gt;
						projection = projection&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						-- join (&amp;quot;zip&amp;quot;) of mutiple data source: here map paths data and highlights&lt;br /&gt;
						type = &amp;quot;lookup&amp;quot;,&lt;br /&gt;
						keys = { &amp;quot;id&amp;quot; },      -- key for map paths data&lt;br /&gt;
						on = &amp;quot;highlights&amp;quot;,    -- name of highlight data source&lt;br /&gt;
						onKey = &amp;quot;id&amp;quot;,         -- key for highlight data source&lt;br /&gt;
						as = { &amp;quot;zipped&amp;quot; },    -- name of resulting table&lt;br /&gt;
						default = { v = defaultValue } -- default value for geographic objects that could not be joined&lt;br /&gt;
					}&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
		},&lt;br /&gt;
		marks =&lt;br /&gt;
		{&lt;br /&gt;
			-- output markings (map paths and highlights)&lt;br /&gt;
			{&lt;br /&gt;
				type = &amp;quot;path&amp;quot;,&lt;br /&gt;
				from = { data = &amp;quot;countries&amp;quot; },&lt;br /&gt;
				properties =&lt;br /&gt;
				{&lt;br /&gt;
					enter = { path = { field = &amp;quot;layout_path&amp;quot; } },&lt;br /&gt;
					update = { fill = { field = &amp;quot;zipped.v&amp;quot; } },&lt;br /&gt;
					hover = { fill = { value = &amp;quot;darkgrey&amp;quot; } }&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
		},&lt;br /&gt;
		legends = legend&lt;br /&gt;
	}&lt;br /&gt;
	if (scales) then&lt;br /&gt;
		output.scales = scales&lt;br /&gt;
		output.marks[1].properties.update.fill.scale = &amp;quot;color&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local flags&lt;br /&gt;
	if formatJson then flags = mw.text.JSON_PRETTY end&lt;br /&gt;
	return mw.text.jsonEncode(output, flags)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function deserializeXData(serializedX, xType, xMin, xMax)&lt;br /&gt;
	local x&lt;br /&gt;
&lt;br /&gt;
	if not xType or xType == &amp;quot;integer&amp;quot; or xType == &amp;quot;number&amp;quot; then&lt;br /&gt;
		local isInteger&lt;br /&gt;
		x, isInteger = numericArray(serializedX)&lt;br /&gt;
		if x then&lt;br /&gt;
			xMin = tonumber(xMin)&lt;br /&gt;
			xMax = tonumber(xMax)&lt;br /&gt;
			if not xType then&lt;br /&gt;
				if isInteger then xType = &amp;quot;integer&amp;quot; else xType = &amp;quot;number&amp;quot; end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			if xType then error(&amp;quot;Numbers expected for parameter &amp;#039;x&amp;#039;&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if not x then&lt;br /&gt;
		x = stringArray(serializedX)&lt;br /&gt;
		if not xType then xType = &amp;quot;string&amp;quot; end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return x, xType, xMin, xMax&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function deserializeYData(serializedYs, yType, yMin, yMax)&lt;br /&gt;
	local y = {}&lt;br /&gt;
	local areAllInteger = true&lt;br /&gt;
&lt;br /&gt;
	for yNum, value in pairs(serializedYs) do&lt;br /&gt;
		local yValues&lt;br /&gt;
		if not yType or yType == &amp;quot;integer&amp;quot; or yType == &amp;quot;number&amp;quot; then&lt;br /&gt;
			local isInteger&lt;br /&gt;
			yValues, isInteger = numericArray(value)&lt;br /&gt;
			if yValues then&lt;br /&gt;
				areAllInteger = areAllInteger and isInteger&lt;br /&gt;
			else&lt;br /&gt;
				if yType then&lt;br /&gt;
					error(&amp;quot;Numbers expected for parameter &amp;#039;&amp;quot; .. name .. &amp;quot;&amp;#039;&amp;quot;)&lt;br /&gt;
				else&lt;br /&gt;
					return deserializeYData(serializedYs, &amp;quot;string&amp;quot;, yMin, yMax)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if not yValues then yValues = stringArray(value) end&lt;br /&gt;
&lt;br /&gt;
		y[yNum] = yValues&lt;br /&gt;
	end&lt;br /&gt;
	if not yType then&lt;br /&gt;
		if areAllInteger then yType = &amp;quot;integer&amp;quot; else yType = &amp;quot;number&amp;quot; end&lt;br /&gt;
	end&lt;br /&gt;
	if yType == &amp;quot;integer&amp;quot; or yType == &amp;quot;number&amp;quot; then&lt;br /&gt;
		yMin = tonumber(yMin)&lt;br /&gt;
		yMax = tonumber(yMax)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return y, yType, yMin, yMax&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function convertXYToManySeries(x, y, xType, yType, seriesTitles)&lt;br /&gt;
	local data =&lt;br /&gt;
	{&lt;br /&gt;
		name = &amp;quot;chart&amp;quot;,&lt;br /&gt;
		format =&lt;br /&gt;
		{&lt;br /&gt;
			type = &amp;quot;json&amp;quot;,&lt;br /&gt;
			parse = { x = xType, y = yType }&lt;br /&gt;
		},&lt;br /&gt;
		values = {}&lt;br /&gt;
	}&lt;br /&gt;
	for i = 1, #y do&lt;br /&gt;
		local yLen = table.maxn(y[i])&lt;br /&gt;
		for j = 1, #x do&lt;br /&gt;
			if j &amp;lt;= yLen and y[i][j] then table.insert(data.values, { series = seriesTitles[i], x = x[j], y = y[i][j] }) end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function convertXYToSingleSeries(x, y, xType, yType, yNames)&lt;br /&gt;
	local data = { name = &amp;quot;chart&amp;quot;, format = { type = &amp;quot;json&amp;quot;, parse = { x = xType } }, values = {} }&lt;br /&gt;
&lt;br /&gt;
	for j = 1, #y do data.format.parse[yNames[j]] = yType end&lt;br /&gt;
&lt;br /&gt;
	for i = 1, #x do&lt;br /&gt;
		local item = { x = x[i] }&lt;br /&gt;
		for j = 1, #y do item[yNames[j]] = y[j][i] end&lt;br /&gt;
&lt;br /&gt;
		table.insert(data.values, item)&lt;br /&gt;
	end&lt;br /&gt;
	return data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getXScale(chartType, stacked, xMin, xMax, xType)&lt;br /&gt;
	if chartType == &amp;quot;pie&amp;quot; then return end&lt;br /&gt;
&lt;br /&gt;
	local xscale =&lt;br /&gt;
	{&lt;br /&gt;
		name = &amp;quot;x&amp;quot;,&lt;br /&gt;
		type = &amp;quot;linear&amp;quot;,&lt;br /&gt;
		range = &amp;quot;width&amp;quot;,&lt;br /&gt;
		zero = false, -- do not include zero value&lt;br /&gt;
		nice = true,  -- force round numbers for y scale&lt;br /&gt;
		domain = { data = &amp;quot;chart&amp;quot;, field = &amp;quot;x&amp;quot; }&lt;br /&gt;
	}&lt;br /&gt;
	if xMin then xscale.domainMin = xMin end&lt;br /&gt;
	if xMax then xscale.domainMax = xMax end&lt;br /&gt;
	if xMin or xMax then xscale.clamp = true end&lt;br /&gt;
	if chartType == &amp;quot;rect&amp;quot; then&lt;br /&gt;
		xscale.type = &amp;quot;ordinal&amp;quot;&lt;br /&gt;
		if not stacked then xscale.padding = 0.2 end -- pad each bar group&lt;br /&gt;
	else&lt;br /&gt;
		if xType == &amp;quot;date&amp;quot; then xscale.type = &amp;quot;time&amp;quot;&lt;br /&gt;
		elseif xType == &amp;quot;string&amp;quot; then&lt;br /&gt;
			xscale.type = &amp;quot;ordinal&amp;quot;&lt;br /&gt;
			xscale.points = true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return xscale&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getYScale(chartType, stacked, yMin, yMax, yType)&lt;br /&gt;
	if chartType == &amp;quot;pie&amp;quot; then return end&lt;br /&gt;
&lt;br /&gt;
	local yscale =&lt;br /&gt;
	{&lt;br /&gt;
		name = &amp;quot;y&amp;quot;,&lt;br /&gt;
		type = &amp;quot;linear&amp;quot;,&lt;br /&gt;
		range = &amp;quot;height&amp;quot;,&lt;br /&gt;
		-- area charts have the lower boundary of their filling at y=0 (see marks.properties.enter.y2), therefore these need to start at zero&lt;br /&gt;
		zero = chartType ~= &amp;quot;line&amp;quot;,&lt;br /&gt;
		nice = true&lt;br /&gt;
	}&lt;br /&gt;
	if yMin then yscale.domainMin = yMin end&lt;br /&gt;
	if yMax then yscale.domainMax = yMax end&lt;br /&gt;
	if yMin or yMax then yscale.clamp = true end&lt;br /&gt;
	if yType == &amp;quot;date&amp;quot; then yscale.type = &amp;quot;time&amp;quot;&lt;br /&gt;
	elseif yType == &amp;quot;string&amp;quot; then yscale.type = &amp;quot;ordinal&amp;quot; end&lt;br /&gt;
	if stacked then&lt;br /&gt;
		yscale.domain = { data = &amp;quot;stats&amp;quot;, field = &amp;quot;sum_y&amp;quot; }&lt;br /&gt;
	else&lt;br /&gt;
		yscale.domain = { data = &amp;quot;chart&amp;quot;, field = &amp;quot;y&amp;quot; }&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return yscale&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getColorScale(colors, chartType, xCount, yCount)&lt;br /&gt;
	if not colors then&lt;br /&gt;
		if (chartType == &amp;quot;pie&amp;quot; and xCount &amp;gt; 10) or yCount &amp;gt; 10 then colors = &amp;quot;category20&amp;quot; else colors = &amp;quot;category10&amp;quot; end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local colorScale =&lt;br /&gt;
	{&lt;br /&gt;
		name = &amp;quot;color&amp;quot;,&lt;br /&gt;
		type = &amp;quot;ordinal&amp;quot;,&lt;br /&gt;
		range = colors,&lt;br /&gt;
		domain = { data = &amp;quot;chart&amp;quot;, field = &amp;quot;series&amp;quot; }&lt;br /&gt;
	}&lt;br /&gt;
	if chartType == &amp;quot;pie&amp;quot; then colorScale.domain.field = &amp;quot;x&amp;quot; end&lt;br /&gt;
	return colorScale&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getAlphaColorScale(colors, y)&lt;br /&gt;
	local alphaScale&lt;br /&gt;
	-- if there is at least one color in the format &amp;quot;#aarrggbb&amp;quot;, create a transparency (alpha) scale&lt;br /&gt;
	if isTable(colors) then&lt;br /&gt;
		local alphas = {}&lt;br /&gt;
		local hasAlpha = false&lt;br /&gt;
		for i = 1, #colors do&lt;br /&gt;
			local a, rgb = string.match(colors[i], &amp;quot;#(%x%x)(%x%x%x%x%x%x)&amp;quot;)&lt;br /&gt;
			if a then&lt;br /&gt;
				hasAlpha = true&lt;br /&gt;
				alphas[i] = tostring(tonumber(a, 16) / 255.0)&lt;br /&gt;
				colors[i] = &amp;quot;#&amp;quot; .. rgb&lt;br /&gt;
			else&lt;br /&gt;
				alphas[i] = &amp;quot;1&amp;quot;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		for i = #colors + 1, #y do alphas[i] = &amp;quot;1&amp;quot; end&lt;br /&gt;
		if hasAlpha then alphaScale = { name = &amp;quot;transparency&amp;quot;, type = &amp;quot;ordinal&amp;quot;, range = alphas } end&lt;br /&gt;
	end&lt;br /&gt;
	return alphaScale&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getValueScale(fieldName, min, max, type)&lt;br /&gt;
	local valueScale =&lt;br /&gt;
	{&lt;br /&gt;
		name = fieldName,&lt;br /&gt;
		type = type or &amp;quot;linear&amp;quot;,&lt;br /&gt;
		domain = { data = &amp;quot;chart&amp;quot;, field = fieldName },&lt;br /&gt;
		range = { min, max }&lt;br /&gt;
	}&lt;br /&gt;
	return valueScale&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function addInteractionToChartVisualisation(plotMarks, colorField, dataField)&lt;br /&gt;
	-- initial setup&lt;br /&gt;
	if not plotMarks.properties.enter then plotMarks.properties.enter = {} end&lt;br /&gt;
	plotMarks.properties.enter[colorField] = { scale = &amp;quot;color&amp;quot;, field = dataField }&lt;br /&gt;
&lt;br /&gt;
	-- action when cursor is over plot mark: highlight&lt;br /&gt;
	if not plotMarks.properties.hover then plotMarks.properties.hover = {} end&lt;br /&gt;
	plotMarks.properties.hover[colorField] = { value = &amp;quot;red&amp;quot; }&lt;br /&gt;
&lt;br /&gt;
	-- action when cursor leaves plot mark: reset to initial setup&lt;br /&gt;
	if not plotMarks.properties.update then plotMarks.properties.update = {} end&lt;br /&gt;
	plotMarks.properties.update[colorField] = { scale = &amp;quot;color&amp;quot;, field = dataField }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale)&lt;br /&gt;
	local chartvis =&lt;br /&gt;
	{&lt;br /&gt;
		type = &amp;quot;arc&amp;quot;,&lt;br /&gt;
		from = { data = &amp;quot;chart&amp;quot;, transform = { { field = &amp;quot;y&amp;quot;, type = &amp;quot;pie&amp;quot; } } },&lt;br /&gt;
&lt;br /&gt;
		properties =&lt;br /&gt;
		{&lt;br /&gt;
			enter = {&lt;br /&gt;
				innerRadius = { value = innerRadius },&lt;br /&gt;
				outerRadius = { },&lt;br /&gt;
				startAngle = { field = &amp;quot;layout_start&amp;quot; },&lt;br /&gt;
				endAngle = { field = &amp;quot;layout_end&amp;quot; },&lt;br /&gt;
				stroke = { value = &amp;quot;white&amp;quot; },&lt;br /&gt;
				strokeWidth = { value = linewidth or 1 }&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if radiusScale then&lt;br /&gt;
		chartvis.properties.enter.outerRadius.scale = radiusScale.name&lt;br /&gt;
		chartvis.properties.enter.outerRadius.field = radiusScale.domain.field&lt;br /&gt;
	else&lt;br /&gt;
		chartvis.properties.enter.outerRadius.value = outerRadius&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	addInteractionToChartVisualisation(chartvis, &amp;quot;fill&amp;quot;, &amp;quot;x&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	return chartvis&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getChartVisualisation(chartType, stacked, colorField, yCount, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate)&lt;br /&gt;
	if chartType == &amp;quot;pie&amp;quot; then return getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale) end&lt;br /&gt;
&lt;br /&gt;
	local chartvis =&lt;br /&gt;
	{&lt;br /&gt;
		type = chartType,&lt;br /&gt;
		properties =&lt;br /&gt;
		{&lt;br /&gt;
			-- chart creation event handler&lt;br /&gt;
			enter =&lt;br /&gt;
			{&lt;br /&gt;
				x = { scale = &amp;quot;x&amp;quot;, field = &amp;quot;x&amp;quot; },&lt;br /&gt;
				y = { scale = &amp;quot;y&amp;quot;, field = &amp;quot;y&amp;quot; }&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	addInteractionToChartVisualisation(chartvis, colorField, &amp;quot;series&amp;quot;)&lt;br /&gt;
	if colorField == &amp;quot;stroke&amp;quot; then&lt;br /&gt;
		chartvis.properties.enter.strokeWidth = { value = linewidth or 2.5 }&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if interpolate then chartvis.properties.enter.interpolate = { value = interpolate } end&lt;br /&gt;
&lt;br /&gt;
	if alphaScale then chartvis.properties.update[colorField .. &amp;quot;Opacity&amp;quot;] = { scale = &amp;quot;transparency&amp;quot; } end&lt;br /&gt;
	-- for bars and area charts set the lower bound of their areas&lt;br /&gt;
	if chartType == &amp;quot;rect&amp;quot; or chartType == &amp;quot;area&amp;quot; then&lt;br /&gt;
		if stacked then&lt;br /&gt;
			-- for stacked charts this lower bound is the end of the last stacking element&lt;br /&gt;
			chartvis.properties.enter.y2 = { scale = &amp;quot;y&amp;quot;, field = &amp;quot;layout_end&amp;quot; }&lt;br /&gt;
		else&lt;br /&gt;
			--[[&lt;br /&gt;
			for non-stacking charts the lower bound is y=0&lt;br /&gt;
			TODO: &amp;quot;yscale.zero&amp;quot; is currently set to &amp;quot;true&amp;quot; for this case, but &amp;quot;false&amp;quot; for all other cases.&lt;br /&gt;
			For the similar behavior &amp;quot;y2&amp;quot; should actually be set to where y axis crosses the x axis,&lt;br /&gt;
			if there are only positive or negative values in the data ]]&lt;br /&gt;
			chartvis.properties.enter.y2 = { scale = &amp;quot;y&amp;quot;, value = 0 }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- for bar charts ...&lt;br /&gt;
	if chartType == &amp;quot;rect&amp;quot; then&lt;br /&gt;
		-- set 1 pixel width between the bars&lt;br /&gt;
		chartvis.properties.enter.width = { scale = &amp;quot;x&amp;quot;, band = true, offset = -1 }&lt;br /&gt;
		-- for multiple series the bar marking needs to use the &amp;quot;inner&amp;quot; series scale, whereas the &amp;quot;outer&amp;quot; x scale is used by the grouping&lt;br /&gt;
		if not stacked and yCount &amp;gt; 1 then&lt;br /&gt;
			chartvis.properties.enter.x.scale = &amp;quot;series&amp;quot;&lt;br /&gt;
			chartvis.properties.enter.x.field = &amp;quot;series&amp;quot;&lt;br /&gt;
			chartvis.properties.enter.width.scale = &amp;quot;series&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- stacked charts have their own (stacked) y values&lt;br /&gt;
	if stacked then chartvis.properties.enter.y.field = &amp;quot;layout_start&amp;quot; end&lt;br /&gt;
&lt;br /&gt;
	-- if there are multiple series group these together&lt;br /&gt;
	if yCount == 1 then&lt;br /&gt;
		chartvis.from = { data = &amp;quot;chart&amp;quot; }&lt;br /&gt;
	else&lt;br /&gt;
		-- if there are multiple series, connect colors to series&lt;br /&gt;
		chartvis.properties.update[colorField].field = &amp;quot;series&amp;quot;&lt;br /&gt;
		if alphaScale then chartvis.properties.update[colorField .. &amp;quot;Opacity&amp;quot;].field = &amp;quot;series&amp;quot; end&lt;br /&gt;
		-- apply a grouping (facetting) transformation&lt;br /&gt;
		chartvis =&lt;br /&gt;
		{&lt;br /&gt;
			type = &amp;quot;group&amp;quot;,&lt;br /&gt;
			marks = { chartvis },&lt;br /&gt;
			from =&lt;br /&gt;
			{&lt;br /&gt;
				data = &amp;quot;chart&amp;quot;,&lt;br /&gt;
				transform =&lt;br /&gt;
				{&lt;br /&gt;
					{&lt;br /&gt;
						type = &amp;quot;facet&amp;quot;,&lt;br /&gt;
						groupby = { &amp;quot;series&amp;quot; }&lt;br /&gt;
					}&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
		-- for stacked charts apply a stacking transformation&lt;br /&gt;
		if stacked then&lt;br /&gt;
			table.insert(chartvis.from.transform, 1, { type = &amp;quot;stack&amp;quot;, groupby = { &amp;quot;x&amp;quot; }, sortby = { &amp;quot;series&amp;quot; }, field = &amp;quot;y&amp;quot; } )&lt;br /&gt;
		else&lt;br /&gt;
			-- for bar charts the series are side-by-side grouped by x&lt;br /&gt;
			if chartType == &amp;quot;rect&amp;quot; then&lt;br /&gt;
				-- 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&lt;br /&gt;
				local groupScale =&lt;br /&gt;
				{&lt;br /&gt;
					name = &amp;quot;series&amp;quot;,&lt;br /&gt;
					type = &amp;quot;ordinal&amp;quot;,&lt;br /&gt;
					range = &amp;quot;width&amp;quot;,&lt;br /&gt;
					domain = { field = &amp;quot;series&amp;quot; }&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				chartvis.from.transform[1].groupby = &amp;quot;x&amp;quot;&lt;br /&gt;
				chartvis.scales = { groupScale }&lt;br /&gt;
				chartvis.properties = { enter = { x = { field = &amp;quot;key&amp;quot;, scale = &amp;quot;x&amp;quot; }, width = { scale = &amp;quot;x&amp;quot;, band = true } } }&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return chartvis&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getTextMarks(chartvis, chartType, outerRadius, scales, radiusScale, yType, showValues)&lt;br /&gt;
	local properties&lt;br /&gt;
	if chartType == &amp;quot;rect&amp;quot; then&lt;br /&gt;
		properties =&lt;br /&gt;
		{&lt;br /&gt;
			x = { scale = chartvis.properties.enter.x.scale, field = chartvis.properties.enter.x.field },&lt;br /&gt;
			y = { scale = chartvis.properties.enter.y.scale, field = chartvis.properties.enter.y.field, offset = -(tonumber(showValues.offset) or -4) },&lt;br /&gt;
			--dx = { scale = chartvis.properties.enter.x.scale, band = true, mult = 0.5 }, -- for horizontal text&lt;br /&gt;
			dy = { scale = chartvis.properties.enter.x.scale, band = true, mult = 0.5 }, -- for vertical text&lt;br /&gt;
			align = { },&lt;br /&gt;
			baseline = { value = &amp;quot;middle&amp;quot; },&lt;br /&gt;
			fill = { },&lt;br /&gt;
			angle = { value = -90 },&lt;br /&gt;
			fontSize = { value = tonumber(showValues.fontsize) or 11 }&lt;br /&gt;
		}&lt;br /&gt;
		if properties.y.offset &amp;gt;= 0 then&lt;br /&gt;
			properties.align.value = &amp;quot;right&amp;quot;&lt;br /&gt;
			properties.fill.value = showValues.fontcolor or &amp;quot;white&amp;quot;&lt;br /&gt;
		else&lt;br /&gt;
			properties.align.value = &amp;quot;left&amp;quot;&lt;br /&gt;
			properties.fill.value = showValues.fontcolor or &amp;quot;black&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	elseif chartType == &amp;quot;pie&amp;quot; then&lt;br /&gt;
		properties =&lt;br /&gt;
		{&lt;br /&gt;
			x = { group = &amp;quot;width&amp;quot;, mult = 0.5 },&lt;br /&gt;
			y = { group = &amp;quot;height&amp;quot;, mult = 0.5 },&lt;br /&gt;
			radius = { offset = tonumber(showValues.offset) or -4 },&lt;br /&gt;
			theta = { field = &amp;quot;layout_mid&amp;quot; },&lt;br /&gt;
			fill = { value = showValues.fontcolor or &amp;quot;black&amp;quot; },&lt;br /&gt;
			baseline = { },&lt;br /&gt;
			angle = { },&lt;br /&gt;
			fontSize = { value = tonumber(showValues.fontsize) or math.ceil(outerRadius / 10) }&lt;br /&gt;
		}&lt;br /&gt;
		if (showValues.angle or &amp;quot;midangle&amp;quot;) == &amp;quot;midangle&amp;quot; then&lt;br /&gt;
			properties.align = { value = &amp;quot;center&amp;quot; }&lt;br /&gt;
			properties.angle = { field = &amp;quot;layout_mid&amp;quot;, mult = 180.0 / math.pi }&lt;br /&gt;
&lt;br /&gt;
			if properties.radius.offset &amp;gt;= 0 then&lt;br /&gt;
				properties.baseline.value = &amp;quot;bottom&amp;quot;&lt;br /&gt;
			else&lt;br /&gt;
				if not showValues.fontcolor then properties.fill.value = &amp;quot;white&amp;quot; end&lt;br /&gt;
				properties.baseline.value = &amp;quot;top&amp;quot;&lt;br /&gt;
			end&lt;br /&gt;
		elseif tonumber(showValues.angle) then&lt;br /&gt;
			-- qunatize scale for aligning text left on right half-circle and right on left half-circle&lt;br /&gt;
			local alignScale = { name = &amp;quot;align&amp;quot;, type = &amp;quot;quantize&amp;quot;, domainMin = 0.0, domainMax = math.pi * 2, range = { &amp;quot;left&amp;quot;, &amp;quot;right&amp;quot; } }&lt;br /&gt;
			table.insert(scales, alignScale)&lt;br /&gt;
&lt;br /&gt;
			properties.align = { scale = alignScale.name, field = &amp;quot;layout_mid&amp;quot; }&lt;br /&gt;
			properties.angle = { value = tonumber(showValues.angle) }&lt;br /&gt;
			properties.baseline.value = &amp;quot;middle&amp;quot;&lt;br /&gt;
			if not tonumber(showValues.offset) then properties.radius.offset = 4 end&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		if radiusScale then&lt;br /&gt;
			properties.radius.scale = radiusScale.name&lt;br /&gt;
			properties.radius.field = radiusScale.domain.field&lt;br /&gt;
		else&lt;br /&gt;
			properties.radius.value = outerRadius&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if properties then&lt;br /&gt;
		if showValues.format then&lt;br /&gt;
			local template = &amp;quot;datum.y&amp;quot;&lt;br /&gt;
			if yType == &amp;quot;integer&amp;quot; or yType == &amp;quot;number&amp;quot; then template = template .. &amp;quot;|number:&amp;#039;&amp;quot; .. showValues.format .. &amp;quot;&amp;#039;&amp;quot;&lt;br /&gt;
			elseif yType == &amp;quot;date&amp;quot; then template = template .. &amp;quot;|time:&amp;quot; .. showValues.format .. &amp;quot;&amp;#039;&amp;quot;&lt;br /&gt;
			end&lt;br /&gt;
			properties.text = { template = &amp;quot;{{&amp;quot; .. template .. &amp;quot;}}&amp;quot; }&lt;br /&gt;
		else&lt;br /&gt;
			properties.text = { field = &amp;quot;y&amp;quot; }&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local textmarks =&lt;br /&gt;
		{&lt;br /&gt;
			type = &amp;quot;text&amp;quot;,&lt;br /&gt;
			properties =&lt;br /&gt;
			{&lt;br /&gt;
				enter = properties&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
		if chartvis.from then textmarks.from = copy(chartvis.from) end&lt;br /&gt;
&lt;br /&gt;
		return textmarks&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType)&lt;br /&gt;
	local xAxis, yAxis&lt;br /&gt;
	if chartType ~= &amp;quot;pie&amp;quot; then&lt;br /&gt;
		if xType == &amp;quot;integer&amp;quot; and not xAxisFormat then xAxisFormat = &amp;quot;d&amp;quot; end&lt;br /&gt;
		xAxis =&lt;br /&gt;
		{&lt;br /&gt;
			type = &amp;quot;x&amp;quot;,&lt;br /&gt;
			scale = &amp;quot;x&amp;quot;,&lt;br /&gt;
			title = xTitle,&lt;br /&gt;
			format = xAxisFormat&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		if yType == &amp;quot;integer&amp;quot; and not yAxisFormat then yAxisFormat = &amp;quot;d&amp;quot; end&lt;br /&gt;
		yAxis =&lt;br /&gt;
		{&lt;br /&gt;
			type = &amp;quot;y&amp;quot;,&lt;br /&gt;
			scale = &amp;quot;y&amp;quot;,&lt;br /&gt;
			title = yTitle,&lt;br /&gt;
			format = yAxisFormat&lt;br /&gt;
		}&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return xAxis, yAxis&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getLegend(legendTitle, chartType, outerRadius)&lt;br /&gt;
	local legend =&lt;br /&gt;
	{&lt;br /&gt;
		fill = &amp;quot;color&amp;quot;,&lt;br /&gt;
		stroke = &amp;quot;color&amp;quot;,&lt;br /&gt;
		title = legendTitle,&lt;br /&gt;
	}&lt;br /&gt;
	if chartType == &amp;quot;pie&amp;quot; then&lt;br /&gt;
		-- move legend from center position to top&lt;br /&gt;
		legend.properties = { legend = { y = { value = -outerRadius } } }&lt;br /&gt;
	end&lt;br /&gt;
	return legend&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.chart(frame)&lt;br /&gt;
	-- chart width and height&lt;br /&gt;
	local graphwidth = tonumber(frame.args.width) or 200&lt;br /&gt;
	local graphheight = tonumber(frame.args.height) or 200&lt;br /&gt;
	-- chart type&lt;br /&gt;
	local chartType = frame.args.type or &amp;quot;line&amp;quot;&lt;br /&gt;
	-- 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&lt;br /&gt;
	local interpolate = frame.args.interpolate&lt;br /&gt;
	-- mark colors (if no colors are given, the default 10 color palette is used)&lt;br /&gt;
	local colors = stringArray(frame.args.colors)&lt;br /&gt;
	-- for line charts, the thickness of the line; for pie charts the gap between each slice&lt;br /&gt;
	local linewidth = tonumber(frame.args.linewidth)&lt;br /&gt;
	-- x and y axis caption&lt;br /&gt;
	local xTitle = frame.args.xAxisTitle&lt;br /&gt;
	local yTitle = frame.args.yAxisTitle&lt;br /&gt;
	-- x and y value types&lt;br /&gt;
	local xType = frame.args.xType&lt;br /&gt;
	local yType = frame.args.yType&lt;br /&gt;
	-- override x and y axis minimum and maximum&lt;br /&gt;
	local xMin = frame.args.xAxisMin&lt;br /&gt;
	local xMax = frame.args.xAxisMax&lt;br /&gt;
	local yMin = frame.args.yAxisMin&lt;br /&gt;
	local yMax = frame.args.yAxisMax&lt;br /&gt;
	-- override x and y axis label formatting&lt;br /&gt;
	local xAxisFormat = frame.args.xAxisFormat&lt;br /&gt;
	local yAxisFormat = frame.args.yAxisFormat&lt;br /&gt;
	-- show legend with given title&lt;br /&gt;
	local legendTitle = frame.args.legend&lt;br /&gt;
	-- show values as text&lt;br /&gt;
	local showValues = frame.args.showValues&lt;br /&gt;
	-- pie chart radiuses&lt;br /&gt;
	local innerRadius = tonumber(frame.args.innerRadius) or 0&lt;br /&gt;
	local outerRadius = math.min(graphwidth, graphheight)&lt;br /&gt;
	-- format JSON output&lt;br /&gt;
	local formatJson = frame.args.formatjson&lt;br /&gt;
&lt;br /&gt;
	-- get x values&lt;br /&gt;
	local x&lt;br /&gt;
	x, xType, xMin, xMax = deserializeXData(frame.args.x, xType, xMin, xMax)&lt;br /&gt;
&lt;br /&gt;
	-- get y values (series)&lt;br /&gt;
	local yValues = {}&lt;br /&gt;
	local seriesTitles = {}&lt;br /&gt;
	for name, value in pairs(frame.args) do&lt;br /&gt;
		local yNum&lt;br /&gt;
		if name == &amp;quot;y&amp;quot; then yNum = 1 else yNum = tonumber(string.match(name, &amp;quot;^y(%d+)$&amp;quot;)) end&lt;br /&gt;
		if yNum then&lt;br /&gt;
			yValues[yNum] = value&lt;br /&gt;
			-- name the series: default is &amp;quot;y&amp;lt;number&amp;gt;&amp;quot;. Can be overwritten using the &amp;quot;y&amp;lt;number&amp;gt;Title&amp;quot; parameters.&lt;br /&gt;
			seriesTitles[yNum] = frame.args[&amp;quot;y&amp;quot; .. yNum .. &amp;quot;Title&amp;quot;] or name&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local y&lt;br /&gt;
	y, yType, yMin, yMax = deserializeYData(yValues, yType, yMin, yMax)&lt;br /&gt;
&lt;br /&gt;
	-- create data tuples, consisting of series index, x value, y value&lt;br /&gt;
	local data&lt;br /&gt;
	if chartType == &amp;quot;pie&amp;quot; then&lt;br /&gt;
		-- for pie charts the second second series is merged into the first series as radius values&lt;br /&gt;
		data = convertXYToSingleSeries(x, y, xType, yType, { &amp;quot;y&amp;quot;, &amp;quot;r&amp;quot; })&lt;br /&gt;
	else&lt;br /&gt;
		data = convertXYToManySeries(x, y, xType, yType, seriesTitles)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- configure stacked charts&lt;br /&gt;
	local stacked = false&lt;br /&gt;
	local stats&lt;br /&gt;
	if string.sub(chartType, 1, 7) == &amp;quot;stacked&amp;quot; then&lt;br /&gt;
		chartType = string.sub(chartType, 8)&lt;br /&gt;
		if #y &amp;gt; 1 then -- ignore stacked charts if there is only one series&lt;br /&gt;
		stacked = true&lt;br /&gt;
		-- aggregate data by cumulative y values&lt;br /&gt;
		stats =&lt;br /&gt;
		{&lt;br /&gt;
			name = &amp;quot;stats&amp;quot;, source = &amp;quot;chart&amp;quot;, transform =&lt;br /&gt;
		{&lt;br /&gt;
			{&lt;br /&gt;
				type = &amp;quot;aggregate&amp;quot;,&lt;br /&gt;
				groupby = { &amp;quot;x&amp;quot; },&lt;br /&gt;
				summarize = { y = &amp;quot;sum&amp;quot; }&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
		}&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- create scales&lt;br /&gt;
	local scales = {}&lt;br /&gt;
&lt;br /&gt;
	local xscale = getXScale(chartType, stacked, xMin, xMax, xType)&lt;br /&gt;
	table.insert(scales, xscale)&lt;br /&gt;
	local yscale = getYScale(chartType, stacked, yMin, yMax, yType)&lt;br /&gt;
	table.insert(scales, yscale)&lt;br /&gt;
&lt;br /&gt;
	local colorScale = getColorScale(colors, chartType, #x, #y)&lt;br /&gt;
	table.insert(scales, colorScale)&lt;br /&gt;
&lt;br /&gt;
	local alphaScale = getAlphaColorScale(colors, y)&lt;br /&gt;
	table.insert(scales, alphaScale)&lt;br /&gt;
&lt;br /&gt;
	local radiusScale&lt;br /&gt;
	if chartType == &amp;quot;pie&amp;quot; and #y &amp;gt; 1 then&lt;br /&gt;
		radiusScale = getValueScale(&amp;quot;r&amp;quot;, 0, outerRadius)&lt;br /&gt;
		table.insert(scales, radiusScale)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- decide if lines (strokes) or areas (fills) should be drawn&lt;br /&gt;
	local colorField&lt;br /&gt;
	if chartType == &amp;quot;line&amp;quot; then colorField = &amp;quot;stroke&amp;quot; else colorField = &amp;quot;fill&amp;quot; end&lt;br /&gt;
&lt;br /&gt;
	-- create chart markings&lt;br /&gt;
	local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate)&lt;br /&gt;
&lt;br /&gt;
	-- text marks&lt;br /&gt;
	local textmarks&lt;br /&gt;
	if showValues then&lt;br /&gt;
		if type(showValues) == &amp;quot;string&amp;quot; then -- deserialize as table&lt;br /&gt;
		local keyValues = mw.text.split(showValues, &amp;quot;%s*,%s*&amp;quot;)&lt;br /&gt;
		showValues = {}&lt;br /&gt;
		for _, kv in ipairs(keyValues) do&lt;br /&gt;
			local key, value = mw.ustring.match(kv, &amp;quot;^%s*(.-)%s*:%s*(.-)%s*$&amp;quot;)&lt;br /&gt;
			if key then showValues[key] = value end&lt;br /&gt;
		end&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local chartmarks = chartvis&lt;br /&gt;
		if chartmarks.marks then chartmarks = chartmarks.marks[1] end&lt;br /&gt;
		textmarks = getTextMarks(chartmarks, chartType, outerRadius, scales, radiusScale, yType, showValues)&lt;br /&gt;
		if chartmarks ~= chartvis then&lt;br /&gt;
			table.insert(chartvis.marks, textmarks)&lt;br /&gt;
			textmarks = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- axes&lt;br /&gt;
	local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType)&lt;br /&gt;
&lt;br /&gt;
	-- legend&lt;br /&gt;
	local legend&lt;br /&gt;
	if legendTitle then legend = getLegend(legendTitle, chartType, outerRadius) end&lt;br /&gt;
&lt;br /&gt;
	-- construct final output object&lt;br /&gt;
	local output =&lt;br /&gt;
	{&lt;br /&gt;
		version = 2,&lt;br /&gt;
		width = graphwidth,&lt;br /&gt;
		height = graphheight,&lt;br /&gt;
		data = { data, stats },&lt;br /&gt;
		scales = scales,&lt;br /&gt;
		axes = { xAxis, yAxis },&lt;br /&gt;
		marks = { chartvis, textmarks },&lt;br /&gt;
		legends = { legend }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	local flags&lt;br /&gt;
	if formatJson then flags = mw.text.JSON_PRETTY end&lt;br /&gt;
	return mw.text.jsonEncode(output, flags)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.mapWrapper(frame)&lt;br /&gt;
	return p.map(frame:getParent())&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.chartWrapper(frame)&lt;br /&gt;
	return p.chart(frame:getParent())&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Given an HTML-encoded title as first argument, e.g. one produced with {{ARTICLEPAGENAME}},&lt;br /&gt;
-- convert it into a properly URL path-encoded string&lt;br /&gt;
-- This function is critical for any graph that uses path-based APIs, e.g. PageViews graph&lt;br /&gt;
function p.encodeTitleForPath(frame)&lt;br /&gt;
	return mw.uri.encode(mw.text.decode(mw.text.trim(frame.args[1])), &amp;#039;PATH&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Chrysophylax</name></author>
	</entry>
</feed>