Модуль:Graph: відмінності між версіями

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
[перевірена версія][перевірена версія]
Вилучено вміст Додано вміст
Створена сторінка: -- УВАГА: Будь ласка, редагуйте цей код на https://de.wikipedia.org/wiki/Modul:Graph -- Таким чином код в усіх...
 
Немає опису редагування
Рядок 169: Рядок 169:
on = "highlights", -- ім’я джерела даних про виділення
on = "highlights", -- ім’я джерела даних про виділення
onKey = "id", -- ключ для джерела даних про виділення
onKey = "id", -- ключ для джерела даних про виділення
as = { "zipped" }, -- ім’я результівної таблиці
as = { "zipped" }, -- ім’я вихідної таблиці
default = { v = defaultValue } -- значення за замовчанням для географічних об’єктів, які не вдалося поєднати
default = { v = defaultValue } -- значення за замовчанням для географічних об’єктів, які не вдалося поєднати
}
}
Рядок 672: Рядок 672:
-- тип діаграми
-- тип діаграми
local chartType = frame.args.type or "line"
local chartType = frame.args.type or "line"
-- режим інтерполяції для лінійних і площинних діаграм: 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
-- режим перекриття кольорів для лінійних і площинних діаграм: 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
local interpolate = frame.args.interpolate
-- позначити кольори (якщо кольори не задані, використовується 10-кольорова палітра за замовчанням)
-- позначити кольори (якщо кольори не задані, використовується 10-кольорова палітра за замовчанням)
Рядок 724: Рядок 724:
local data
local data
if chartType == "pie" then
if chartType == "pie" then
-- для секторних діаграм the second second series is merged into the first series as radius values
-- для секторних діаграм: друга серія розміщується всередині першої серії за значеннями радіусів
data = convertXYToSingleSeries(x, y, xType, yType, { "y", "r" })
data = convertXYToSingleSeries(x, y, xType, yType, { "y", "r" })
else
else
Рядок 772: Рядок 772:
end
end


-- вирішити, що саме необхідно малювати: лінії (контури) чи площі (заливки)
-- вирішити, що саме необхідно вивести: лінії (контури) чи площини (заливки)
local colorField
local colorField
if chartType == "line" then colorField = "stroke" else colorField = "fill" end
if chartType == "line" then colorField = "stroke" else colorField = "fill" end
Рядок 782: Рядок 782:
local textmarks
local textmarks
if showValues then
if showValues then
if type(showValues) == "string" then -- deserialize as table
if type(showValues) == "string" then -- десеріалізуати як таблицю
local keyValues = mw.text.split(showValues, "%s*,%s*")
local keyValues = mw.text.split(showValues, "%s*,%s*")
showValues = {}
showValues = {}

Версія за 02:27, 15 березня 2016

{{i}} Документація модуля[перегляд] [редагувати] [історія] [очистити кеш]

Обслуговує шаблон для створення діаграм {{graph:Chart}}.

-- УВАГА: Будь ласка, редагуйте цей код на https://de.wikipedia.org/wiki/Modul:Graph
-- Таким чином код в усіх мовних розділах буде ідентичним. Дякуємо!
--
-- Історія версій:
--   2016-01-09 _БУДЬ ЛАСКА, ПОНОВІТЬ ДАТУ при внесенні будь-яких змін_
--   2016-01-28 Для карт завжди використовуйте протокол wikiraw://. Протокол https:// невдовзі буде деактивований.

local p = {}

local baseMapDirectory = "Module:Graph/"

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
		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
	return result, isInteger
end

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

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

function p.map(frame)
	-- дані контурів карт для географічних об’єктів
	local basemap = frame.args.basemap or "WorldMap-iso2.json"
	-- масштабний коефіцієнт
	local scale = tonumber(frame.args.scale) or 100
	-- проекція карти, див. https://github.com/mbostock/d3/wiki/Geo-Projections
	local projection = frame.args.projection or "equirectangular"
	-- значення за замовчанням для географічних об’єктів без даних
	local defaultValue = frame.args.defaultValue
	local scaleType = frame.args.scaleType or "linear"
	-- мінімальний діапазон значень (тільки для цифрових даних)
	local domainMin = tonumber(frame.args.domainMin)
	-- максимальний діапазон значень (тільки для цифрових даних)
	local domainMax = tonumber(frame.args.domainMax)
	-- кольорові координати шкали кольорів (тільки для цифрових даних)
	local colorScale = frame.args.colorScale or "category10"
	-- показати легенду
	local legend = frame.args.legend
	-- вивести в форматі JSON
	local formatJson = frame.args.formatjson

	-- картографічні дані — пари ключ-значення: ключі — рядки тільки з великих літер (бажано ISO-коди), які мають відповідати значенням "id" даних контурів карт
	local values = {}
	local isNumbers = nil
	for name, value in pairs(frame.args) do
		if mw.ustring.find(name, "^[^%l]+$") then
			if isNumbers == nil then isNumbers = tonumber(value) end
			local data = { id = name, v = value }
			if isNumbers then data.v = tonumber(data.v) end
			table.insert(values, data)
		end
	end
	if not defaultValue then
		if isNumbers then defaultValue = 0 else defaultValue = "silver" end
	end

	-- створити шкалу виділення
	local scales
	if isNumbers then
		if colorScale == "category10" or colorScale == "category20" then else colorScale = stringArray(colorScale) end
		scales =
		{
			{
				name = "color",
				type = scaleType,
				domain = { data = "highlights", field = "v" },
				range = colorScale,
				nice = true
			}
		}
		if domainMin then scales[1].domainMin = domainMin end
		if domainMax then scales[1].domainMax = domainMax end

		local exponent = string.match(scaleType, "pow%s+(%d+%.?%d+)") -- перевірка експоненти
		if exponent then
			scales[1].type = "pow"
			scales[1].exponent = exponent
		end
	end

	-- створити легенду
	if legend then
		legend =
		{
			{
				fill = "color",
				offset = 120,
				properties =
				{
					title = { fontSize = { value = 14 } },
					labels = { fontSize = { value = 12 } },
					legend =
					{
						stroke = { value = "silver" },
						strokeWidth = { value = 1.5 }
					}
				}
			}
		}
	end

	-- отримати url карти
	local basemapUrl
	if (string.sub(basemap, 1, 10) == "wikiraw://") then
		basemapUrl = basemap
	else
		-- якщо url відсутній або не підтримується, шукати двокрапку як роздільник між простором назв і заголовком. Якщо двокрапки немає, подати першою директорію карти за замовчанням.
		if not string.find(basemap, ":") then basemap = baseMapDirectory .. basemap end
		basemapUrl = "wikiraw:///" .. mw.uri.encode(mw.title.new(basemap).prefixedText, "PATH")
	end

	local output =
	{
		version = 2,
		width = 1,  -- загальне значення, оскільки розмір виводу залежить виключно від розміру карти і масштабного коефіцієнту
		height = 1, -- див. вище
		data =
		{
			{
				-- джерело даних для виділень
				name = "highlights",
				values = values
			},
			{
				-- джерело даних для контурів карт
				name = "countries",
				url = basemapUrl,
				format = { type = "topojson", feature = "countries" },
				transform =
				{
					{
						-- географічна трансформація («геоконтур») даних про контури карт
						type = "geopath",
						value = "data",			-- джерело даних
						scale = scale,
						translate = { 0, 0 },
						projection = projection
					},
					{
						-- об’єднання («зіп») кількох джерел даних: тут — контурів карт і виділень
						type = "lookup",
						keys = { "id" },      -- ключ для даних про контури карт
						on = "highlights",    -- ім’я джерела даних про виділення
						onKey = "id",         -- ключ для джерела даних про виділення
						as = { "zipped" },    -- ім’я вихідної таблиці
						default = { v = defaultValue } -- значення за замовчанням для географічних об’єктів, які не вдалося поєднати
					}
				}
			}
		},
		marks =
		{
			-- вивідна розмітка (контури карт і виділення)
			{
				type = "path",
				from = { data = "countries" },
				properties =
				{
					enter = { path = { field = "layout_path" } },
					update = { fill = { field = "zipped.v" } },
					hover = { fill = { value = "darkgrey" } }
				}
			}
		},
		legends = legend
	}
	if (scales) then
		output.scales = scales
		output.marks[1].properties.update.fill.scale = "color"
	end

	local flags
	if formatJson then flags = mw.text.JSON_PRETTY end
	return mw.text.jsonEncode(output, flags)
end

local function deserializeXData(serializedX, xType, xMin, xMax)
	local x

	if not xType or xType == "integer" 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 = "integer" else 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 == "integer" 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 = "integer" else yType = "number" end
	end
	if yType == "integer" or 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 = {}
	}
	for i = 1, #y do
		for j = 1, #x do
			if j <= #y[i] then table.insert(data.values, { series = seriesTitles[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 = {} }

	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)
	if chartType == "pie" then return end

	local xscale =
	{
		name = "x",
		type = "linear",
		range = "width",
		zero = false, -- не включає нульове значення
		nice = true,  -- примусово виводити круглі числа для шкали y
		domain = { data = "chart", field = "x" }
	}
	if xMin then xscale.domainMin = xMin end
	if xMax then xscale.domainMax = xMax end
	if xMin or xMax then xscale.clamp = true end
	if chartType == "rect" then
		xscale.type = "ordinal"
		if not stacked then xscale.padding = 0.2 end -- доповнити пробілам кожну з груп
	else
		if xType == "date" then xscale.type = "time"
		elseif xType == "string" then xscale.type = "ordinal" end
	end

	return xscale
end

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

	local yscale =
	{
		name = "y",
		type = "linear",
		range = "height",
		-- нижнє граничне значення заливки областей діаграми y=0 (див. marks.properties.enter.y2), тому вони мають починатися з нуля
		zero = chartType ~= "line",
		nice = true
	}
	if yMin then yscale.domainMin = yMin end
	if yMax then yscale.domainMax = yMax end
	if yMin or yMax then yscale.clamp = true end
	if yType == "date" then yscale.type = "time"
	elseif yType == "string" then yscale.type = "ordinal" end
	if stacked then
		yscale.domain = { data = "stats", field = "sum_y" }
	else
		yscale.domain = { data = "chart", field = "y" }
	end

	return yscale
end

local function getColorScale(colors, chartType, xCount, yCount)
	if not colors then
		if (chartType == "pie" and xCount > 10) or yCount > 10 then colors = "category20" else colors = "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
	-- якщо є принаймні один колір формату "#aarrggbb", створити (альфа-) шкалу прозорості
	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 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)
	-- первісне налаштування
	if not plotMarks.properties.enter then plotMarks.properties.enter = {} end
	plotMarks.properties.enter[colorField] = { scale = "color", field = dataField }

	-- дія, коли курсор над позначкою діаграми
	if not plotMarks.properties.hover then plotMarks.properties.hover = {} end
	plotMarks.properties.hover[colorField] = { value = "red" }

	-- дія, коли курсор залишає позначку діаграми: повернення до первісного налаштування
	if not plotMarks.properties.update then plotMarks.properties.update = {} end
	plotMarks.properties.update[colorField] = { scale = "color", field = dataField }
end

local function getPieChartVisualisation(yCount, innerRadius, outerRadius, linewidth, radiusScale)
	local chartvis =
	{
		type = "arc",
		from = { data = "chart", transform = { { field = "y", type = "pie" } } },

		properties =
		{
			enter = {
				innerRadius = { value = innerRadius },
				outerRadius = { },
				startAngle = { field = "layout_start" },
				endAngle = { field = "layout_end" },
				stroke = { value = "white" },
				strokeWidth = { value = linewidth or 1 }
			}
		}
	}

	if radiusScale then
		chartvis.properties.enter.outerRadius.scale = radiusScale.name
		chartvis.properties.enter.outerRadius.field = radiusScale.domain.field
	else
		chartvis.properties.enter.outerRadius.value = outerRadius
	end

	addInteractionToChartVisualisation(chartvis, "fill", "x")

	return chartvis
end

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

	local chartvis =
	{
		type = chartType,
		properties =
		{
			-- хендлер подій створення діаграм
			enter =
			{
				x = { scale = "x", field = "x" },
				y = { scale = "y", field = "y" }
			}
		}
	}
	addInteractionToChartVisualisation(chartvis, colorField, "series")
	if colorField == "stroke" then
		chartvis.properties.enter.strokeWidth = { value = linewidth or 2.5 }
	end

	if interpolate then chartvis.properties.enter.interpolate = { value = interpolate } end

	if alphaScale then chartvis.properties.update[colorField .. "Opacity"] = { scale = "transparency" } end
	-- для стовпчастих і площинних діаграм установити нижнє граничне значення їхніх площин
	if chartType == "rect" or chartType == "area" then
		if stacked then
			-- для стосових діаграм це нижнє граничне значення є кінцем останнього елемента діаграми
			chartvis.properties.enter.y2 = { scale = "y", field = "layout_end" }
		else
			--[[
			для нестосових діаграм нижнє граничне значення y=0
			ЗРОБИТИ: значення "yscale.zero" встановлене як "true" для цього випадку, але як "false" для всіх інших випадків.
			Для аналогічної поведінки "y2" треба насправді розмістити там, де вісь y перетинає вісь x,
			якщо дані мають тільки додатні або від’ємні значення ]]
			chartvis.properties.enter.y2 = { scale = "y", value = 0 }
		end
	end
	-- для стовпчастих діаграм…
	if chartType == "rect" then
		-- задати ширину просвіту між стовпцями в 1 піксел
		chartvis.properties.enter.width = { scale = "x", band = true, offset = -1 }
		-- для серій діаграм маркування стовпців має використовувати «внутрішню» шкалу серії, тоді як «зовнішня» шкала x використувується для групування
		if not stacked and yCount > 1 then
			chartvis.properties.enter.x.scale = "series"
			chartvis.properties.enter.x.field = "series"
			chartvis.properties.enter.width.scale = "series"
		end
	end
	-- стосові діаграми мають власні (стосові) значення y
	if stacked then chartvis.properties.enter.y.field = "layout_start" end

	-- якщо є множинні серії, згрупувати їх
	if yCount == 1 then
		chartvis.from = { data = "chart" }
	else
		-- якщо є множинні серії, прив’язати до них кольори
		chartvis.properties.update[colorField].field = "series"
		if alphaScale then chartvis.properties.update[colorField .. "Opacity"].field = "series" end
		-- застосувати групувальну (поєднальну) трансформацію
		chartvis =
		{
			type = "group",
			marks = { chartvis },
			from =
			{
				data = "chart",
				transform =
				{
					{
						type = "facet",
						groupby = { "series" }
					}
				}
			}
		}
		-- для стосових діаграм застосувати трансформацію накладення
		if stacked then
			table.insert(chartvis.from.transform, 1, { type = "stack", groupby = { "x" }, sortby = { "series" }, field = "y" } )
		else
			-- для стовпчастих діаграм серії групуються впритул по x
			if chartType == "rect" then
				-- для стовпчастих діаграм з багаторазовими серіями: кожна серія групується за значенням x, отже, серії потребують власної шкали в кожній групі x
				local groupScale =
				{
					name = "series",
					type = "ordinal",
					range = "width",
					domain = { field = "series" }
				}

				chartvis.from.transform[1].groupby = "x"
				chartvis.scales = { groupScale }
				chartvis.properties = { enter = { x = { field = "key", scale = "x" }, width = { scale = "x", band = true } } }
			end
		end
	end

	return chartvis
end

local function getTextMarks(chartvis, chartType, outerRadius, scales, radiusScale, yType, showValues)
	local properties
	if chartType == "rect" then
		properties =
		{
			x = { scale = chartvis.properties.enter.x.scale, field = chartvis.properties.enter.x.field },
			y = { scale = chartvis.properties.enter.y.scale, field = chartvis.properties.enter.y.field, offset = -(tonumber(showValues.offset) or -4) },
			--dx = { scale = chartvis.properties.enter.x.scale, band = true, mult = 0.5 }, -- для горизонтального тексту
			dy = { scale = chartvis.properties.enter.x.scale, band = true, mult = 0.5 }, -- для вертикального тексту
			align = { },
			baseline = { value = "middle" },
			fill = { },
			angle = { value = -90 },
			fontSize = { value = tonumber(showValues.fontsize) or 11 }
		}
		if properties.y.offset >= 0 then
			properties.align.value = "right"
			properties.fill.value = showValues.fontcolor or "white"
		else
			properties.align.value = "left"
			properties.fill.value = showValues.fontcolor or "black"
		end
	elseif chartType == "pie" then
		properties =
		{
			x = { group = "width", mult = 0.5 },
			y = { group = "height", mult = 0.5 },
			radius = { offset = tonumber(showValues.offset) or -4 },
			theta = { field = "layout_mid" },
			fill = { value = showValues.fontcolor or "black" },
			baseline = { },
			angle = { },
			fontSize = { value = tonumber(showValues.fontsize) or math.ceil(outerRadius / 10) }
		}
		if (showValues.angle or "midangle") == "midangle" then
			properties.align = { value = "center" }
			properties.angle = { field = "layout_mid", mult = 180.0 / math.pi }

			if properties.radius.offset >= 0 then
				properties.baseline.value = "bottom"
			else
				if not showValues.fontcolor then properties.fill.value = "white" end
				properties.baseline.value = "top"
			end
		elseif tonumber(showValues.angle) then
			-- квантизувати шкалу для вирівнювання тексту вліво на правому півколі і вправо на лівому півколі
			local alignScale = { name = "align", type = "quantize", domainMin = 0.0, domainMax = math.pi * 2, range = { "left", "right" } }
			table.insert(scales, alignScale)

			properties.align = { scale = alignScale.name, field = "layout_mid" }
			properties.angle = { value = tonumber(showValues.angle) }
			properties.baseline.value = "middle"
			if not tonumber(showValues.offset) then properties.radius.offset = 4 end
		end

		if radiusScale then
			properties.radius.scale = radiusScale.name
			properties.radius.field = radiusScale.domain.field
		else
			properties.radius.value = outerRadius
		end
	end

	if properties then
		if showValues.format then
			local template = "datum.y"
			if yType == "integer" or yType == "number" then template = template .. "|number:'" .. showValues.format .. "'"
			elseif yType == "date" then template = template .. "|time:" .. showValues.format .. "'"
			end
			properties.text = { template = "{{" .. template .. "}}" }
		else
			properties.text = { field = "y" }
		end

		local textmarks =
		{
			type = "text",
			properties =
			{
				enter = properties
			}
		}
		if chartvis.from then textmarks.from = copy(chartvis.from) end

		return textmarks
	end
end

local function getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType)
	local xAxis, yAxis
	if chartType ~= "pie" then
		if xType == "integer" and not xAxisFormat then xAxisFormat = "d" end
		xAxis =
		{
			type = "x",
			scale = "x",
			title = xTitle,
			format = xAxisFormat
		}

		if yType == "integer" and not yAxisFormat then yAxisFormat = "d" end
		yAxis =
		{
			type = "y",
			scale = "y",
			title = yTitle,
			format = yAxisFormat
		}
	end

	return xAxis, yAxis
end

local function getLegend(legendTitle, chartType, outerRadius)
	local legend =
	{
		fill = "color",
		stroke = "color",
		title = legendTitle,
	}
	if chartType == "pie" then
		-- перемістити легенду з центральної позиції наверх
		legend.properties = { legend = { y = { value = -outerRadius } } }
	end
	return legend
end

function p.chart(frame)
	-- ширина й висота діаграми
	local graphwidth = tonumber(frame.args.width) or 200
	local graphheight = tonumber(frame.args.height) or 200
	-- тип діаграми
	local chartType = frame.args.type or "line"
	-- режим перекриття кольорів для лінійних і площинних діаграм: 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
	-- позначити кольори (якщо кольори не задані, використовується 10-кольорова палітра за замовчанням)
	local colors = stringArray(frame.args.colors)
	-- для лінійних діаграм — товщина лінії; для секторних діаграм — ширина проміжку між секторами
	local linewidth = tonumber(frame.args.linewidth)
	-- підпис під осями x та y
	local xTitle = frame.args.xAxisTitle
	local yTitle = frame.args.yAxisTitle
	-- типи значень x та y
	local xType = frame.args.xType
	local yType = frame.args.yType
	-- подавити мінімум і максимум осей x та y
	local xMin = frame.args.xAxisMin
	local xMax = frame.args.xAxisMax
	local yMin = frame.args.yAxisMin
	local yMax = frame.args.yAxisMax
	-- подавити форматування ярликів осей x та y
	local xAxisFormat = frame.args.xAxisFormat
	local yAxisFormat = frame.args.yAxisFormat
	-- показати легенду з заданим заголовком
	local legendTitle = frame.args.legend
	-- показати значення як текст
	local showValues = frame.args.showValues
	-- радіуси кругової діаграми
	local innerRadius = tonumber(frame.args.innerRadius) or 0
	local outerRadius = math.min(graphwidth, graphheight)
	-- вивести у форматі JSON
	local formatJson = frame.args.formatjson

	-- отримати значення x
	local x
	x, xType, xMin, xMax = deserializeXData(frame.args.x, xType, xMin, xMax)

	-- отримати значення y (серії)
	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
			-- назвати серію: за замовчанням — "y<number>". Може бути переписана з використанням параметрів "y<number>Title".
			seriesTitles[yNum] = frame.args["y" .. yNum .. "Title"] or name
		end
	end
    local y
	y, yType, yMin, yMax = deserializeYData(yValues, yType, yMin, yMax)

	-- створити кортеж даних: індекс серії, значення x та y
	local data
	if chartType == "pie" then
		-- для секторних діаграм: друга серія розміщується всередині першої серії за значеннями радіусів
		data = convertXYToSingleSeries(x, y, xType, yType, { "y", "r" })
	else
		data = convertXYToManySeries(x, y, xType, yType, seriesTitles)
	end

	-- сконфігурувати стосові діаграми
	local stacked = false
	local stats
	if string.sub(chartType, 1, 7) == "stacked" then
		chartType = string.sub(chartType, 8)
		if #y > 1 then -- ігнорувати стосові діаграми, якщо є тільки одна серія
			stacked = true
			-- зібрати дані за кумулятивними значеннями y
			stats =
			{
				name = "stats", source = "chart", transform =
				{
					{
						type = "aggregate",
						groupby = { "x" },
						summarize = { y = "sum" }
					}
				}
			}
		end
	end

	-- створити шкали
	local scales = {}

	local xscale = getXScale(chartType, stacked, xMin, xMax, xType)
	table.insert(scales, xscale)
	local yscale = getYScale(chartType, stacked, yMin, yMax, yType)
	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 radiusScale
	if chartType == "pie" and #y > 1 then
		radiusScale = getValueScale("r", 0, outerRadius)
		table.insert(scales, radiusScale)
	end

	-- вирішити, що саме необхідно вивести: лінії (контури) чи площини (заливки)
	local colorField
	if chartType == "line" then colorField = "stroke" else colorField = "fill" end

	-- створити мітки діаграм
	local chartvis = getChartVisualisation(chartType, stacked, colorField, #y, innerRadius, outerRadius, linewidth, alphaScale, radiusScale, interpolate)

	-- текстові позначки
	local textmarks
	if showValues then
		if type(showValues) == "string" then -- десеріалізуати як таблицю
			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
		textmarks = getTextMarks(chartmarks, chartType, outerRadius, scales, radiusScale, yType, showValues)
		if chartmarks ~= chartvis then
			table.insert(chartvis.marks, textmarks)
			textmarks = nil
		end
	end

	-- осі
	local xAxis, yAxis = getAxes(xTitle, xAxisFormat, xType, yTitle, yAxisFormat, yType, chartType)

	-- легенда
	local legend
	if legendTitle then legend = getLegend(legendTitle, chartType, outerRadius) end

	-- створити об’єкт для фінального виводу
	local output =
	{
		version = 2,
		width = graphwidth,
		height = graphheight,
		data = { data, stats },
		scales = scales,
		axes = { xAxis, yAxis },
		marks = { chartvis, textmarks },
		legends = { legend }
	}

	local flags
	if formatJson then flags = mw.text.JSON_PRETTY end
	return mw.text.jsonEncode(output, flags)
end

function p.mapWrapper(frame)
	return p.map(frame:getParent())
end

function p.chartWrapper(frame)
	return p.chart(frame:getParent())
end

return p