Модуль:ТранспортнаяСхема: различия между версиями
Внешний вид
Tringoli (обсуждение | вклад) мНет описания правки |
Tringoli (обсуждение | вклад) мНет описания правки |
||
| Строка 1: | Строка 1: | ||
local p = {} | local p = {} | ||
-- Извлечение аргументов | -- Извлечение аргументов | ||
local function getArgs(frame) | local function getArgs(frame) | ||
local args = {} | local args = {} | ||
| Строка 16: | Строка 16: | ||
end | end | ||
-- | -- Загрузка JSON данных | ||
local function loadData(args) | local function loadData(args) | ||
local dataParam = args['данные'] or args[1] | local dataParam = args['данные'] or args[1] | ||
| Строка 30: | Строка 30: | ||
table.insert(candidates, 'Шаблон:Интерактивная схема ' .. dataParam .. '/data.json') | table.insert(candidates, 'Шаблон:Интерактивная схема ' .. dataParam .. '/data.json') | ||
table.insert(candidates, 'Шаблон:' .. dataParam .. '/data.json') | table.insert(candidates, 'Шаблон:' .. dataParam .. '/data.json') | ||
table.insert(candidates, dataParam) | table.insert(candidates, dataParam) | ||
end | end | ||
for _, | for _, title in ipairs(candidates) do | ||
local success, jsonData = pcall(mw.loadJsonData, title) | |||
local success, jsonData = pcall(mw.loadJsonData, | if success and jsonData then return jsonData end | ||
if success and jsonData then | |||
local titleObj = mw.title.new(title) | |||
local titleObj = mw.title.new( | |||
if titleObj and titleObj.exists then | if titleObj and titleObj.exists then | ||
local | local raw = titleObj:getContent() | ||
if | if raw and raw ~= '' then | ||
raw = mw.ustring.gsub(raw, '<noinclude>.-</noinclude>', '') | |||
raw = mw.ustring.gsub(raw, '<!--.-?-->', '') | |||
local ok, decoded = pcall(mw.text.jsonDecode, mw.text.trim(raw)) | |||
if ok and decoded then return decoded end | |||
local | |||
if | |||
end | end | ||
end | end | ||
end | end | ||
error('Страница данных не найдена: ' .. dataParam) | |||
error('Страница данных не найдена | end | ||
-- Функция парсинга координат "X Y" | |||
local function parseCoords(str) | |||
if not str then return nil, nil end | |||
local x, y = mw.ustring.match(str, '(%d+%.?%d*)%s+(%d+%.?%d*)') | |||
return tonumber(x), tonumber(y) | |||
end | end | ||
-- | -- Главная функция отрисовки векторной схемы | ||
function p. | function p.vector(frame) | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local | local data = loadData(args) | ||
local | local width = tonumber(args['ширина_svg'] or 850) or 850 | ||
local | local height = tonumber(args['высота_svg'] or 620) or 620 | ||
local | local viewBox = args['viewbox'] or ('0 0 ' .. width .. ' ' .. height) | ||
local | local svg = {} | ||
table.insert(svg, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="' .. viewBox .. '" width="100%" height="auto" style="max-width:' .. width .. 'px; background:#ffffff; border-radius:8px; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif; user-select:none;">') | |||
-- Стили подсветки и анимации при наведении (CSS) | |||
table.insert(svg, '<style>') | |||
table.insert(svg, '.ts-v-line { transition: opacity 0.25s ease, stroke-width 0.2s ease; cursor: pointer; }') | |||
table.insert(svg, '.ts-v-station { transition: transform 0.2s ease, r 0.2s ease; cursor: pointer; }') | |||
table.insert(svg, '.ts-v-station:hover circle { r: 6.5px; stroke-width: 3.5px; }') | |||
table.insert(svg, '.ts-v-label { font-size: 11px; fill: #24292f; font-weight: 500; pointer-events: all; text-shadow: 0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 3px #ffffff; transition: fill 0.2s ease; }') | |||
table.insert(svg, '.ts-v-label:hover { fill: #0969da; font-weight: bold; }') | |||
table.insert(svg, 'svg:hover .ts-v-group:not(:hover) { opacity: 0.35; }') | |||
table.insert(svg, '.ts-v-group { transition: opacity 0.25s ease; }') | |||
table.insert(svg, '</style>') | |||
-- 1. Слой сетки/фона (декоративный) | |||
table.insert(svg, '<rect width="100%" height="100%" fill="#fafbfc" rx="8" />') | |||
-- 2. Слой линий и путей | |||
if data.lines then | |||
for i, line in ipairs(data.lines) do | |||
local color = line.color or '#888888' | |||
local points = {} | |||
local futurePoints = {} | |||
table.insert(svg, '<g class="ts-v-group ts-v-line-' .. (line.number or i) .. '">') | |||
-- Сбор координат станций | |||
if line.stations then | |||
for _, st in ipairs(line.stations) do | |||
local x, y = parseCoords(st.coords) | |||
if x and y then | |||
table.insert(points, x .. ',' .. y) | |||
end | |||
end | |||
end | end | ||
-- | -- Отрисовка основной сплошной трассы | ||
if | if #points > 1 then | ||
table.insert(svg, '<polyline class="ts-v-line" points="' .. table.concat(points, ' ') .. '" fill="none" stroke="' .. color .. '" stroke-width="5.5" stroke-linecap="round" stroke-linejoin="round" />') | |||
end | end | ||
-- | -- Отрисовка станций и подписей | ||
if line.stations then | if line.stations then | ||
for _, st in ipairs(line.stations) do | for _, st in ipairs(line.stations) do | ||
local x, y = parseCoords(st.coords) | |||
local | if x and y then | ||
local stName = st.name or st.article or '' | |||
local article = st.article or stName | |||
local isFuture = st.future == true | |||
table.insert(svg, '<a href="/wiki/' .. mw.uri.encode(article, 'WIKI') .. '" title="' .. mw.text.nowiki(stName) .. '">') | |||
table.insert(svg, '<g class="ts-v-station">') | |||
-- Кружок станции | |||
if isFuture then | |||
table.insert(svg, '<circle cx="' .. x .. '" cy="' .. y .. '" r="4.5" fill="#ffffff" stroke="' .. color .. '" stroke-width="2.5" stroke-dasharray="2,2" />') | |||
else | |||
table.insert(svg, '<circle cx="' .. x .. '" cy="' .. y .. '" r="5" fill="#ffffff" stroke="' .. color .. '" stroke-width="3" />') | |||
end | |||
-- Текстовая подпись станции (с небольшим смещением) | |||
local textOffset = (x > (width / 2)) and (x + 9) or (x - 9) | |||
local textAnchor = (x > (width / 2)) and 'start' or 'end' | |||
local textColor = isFuture and '#6e7781' or '#24292f' | |||
table.insert(svg, '<text class="ts-v-label" x="' .. textOffset .. '" y="' .. (y + 3.5) .. '" text-anchor="' .. textAnchor .. '" fill="' .. textColor .. '">' .. mw.text.nowiki(stName) .. '</text>') | |||
table.insert(svg, '</g></a>') | |||
end | end | ||
end | end | ||
end | end | ||
table.insert(svg, '</g>') | |||
end | end | ||
end | end | ||
table.insert(svg, '</svg>') | |||
return table.concat(svg, '\n') | |||
end | end | ||
return p | return p | ||
Версия от 16:00, 22 августа 2026
Для документации этого модуля может быть создана страница Модуль:ТранспортнаяСхема/doc
local p = {}
-- Извлечение аргументов
local function getArgs(frame)
local args = {}
if frame.getParent then
local parent = frame:getParent()
if parent and parent.args then
for k, v in pairs(parent.args) do args[k] = v end
end
end
if frame.args then
for k, v in pairs(frame.args) do args[k] = v end
end
return args
end
-- Загрузка JSON данных
local function loadData(args)
local dataParam = args['данные'] or args[1]
if not dataParam or dataParam == '' then
error('Не указано имя базы данных или страница JSON (параметр 1 или |данные=)')
end
local candidates = {}
if mw.ustring.find(dataParam, ':') or mw.ustring.find(dataParam, '%.json$') then
table.insert(candidates, dataParam)
else
table.insert(candidates, 'Шаблон:Интерактивная схема ' .. dataParam .. ' метрополитена/data.json')
table.insert(candidates, 'Шаблон:Интерактивная схема ' .. dataParam .. '/data.json')
table.insert(candidates, 'Шаблон:' .. dataParam .. '/data.json')
table.insert(candidates, dataParam)
end
for _, title in ipairs(candidates) do
local success, jsonData = pcall(mw.loadJsonData, title)
if success and jsonData then return jsonData end
local titleObj = mw.title.new(title)
if titleObj and titleObj.exists then
local raw = titleObj:getContent()
if raw and raw ~= '' then
raw = mw.ustring.gsub(raw, '<noinclude>.-</noinclude>', '')
raw = mw.ustring.gsub(raw, '<!--.-?-->', '')
local ok, decoded = pcall(mw.text.jsonDecode, mw.text.trim(raw))
if ok and decoded then return decoded end
end
end
end
error('Страница данных не найдена: ' .. dataParam)
end
-- Функция парсинга координат "X Y"
local function parseCoords(str)
if not str then return nil, nil end
local x, y = mw.ustring.match(str, '(%d+%.?%d*)%s+(%d+%.?%d*)')
return tonumber(x), tonumber(y)
end
-- Главная функция отрисовки векторной схемы
function p.vector(frame)
local args = getArgs(frame)
local data = loadData(args)
local width = tonumber(args['ширина_svg'] or 850) or 850
local height = tonumber(args['высота_svg'] or 620) or 620
local viewBox = args['viewbox'] or ('0 0 ' .. width .. ' ' .. height)
local svg = {}
table.insert(svg, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="' .. viewBox .. '" width="100%" height="auto" style="max-width:' .. width .. 'px; background:#ffffff; border-radius:8px; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif; user-select:none;">')
-- Стили подсветки и анимации при наведении (CSS)
table.insert(svg, '<style>')
table.insert(svg, '.ts-v-line { transition: opacity 0.25s ease, stroke-width 0.2s ease; cursor: pointer; }')
table.insert(svg, '.ts-v-station { transition: transform 0.2s ease, r 0.2s ease; cursor: pointer; }')
table.insert(svg, '.ts-v-station:hover circle { r: 6.5px; stroke-width: 3.5px; }')
table.insert(svg, '.ts-v-label { font-size: 11px; fill: #24292f; font-weight: 500; pointer-events: all; text-shadow: 0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 3px #ffffff; transition: fill 0.2s ease; }')
table.insert(svg, '.ts-v-label:hover { fill: #0969da; font-weight: bold; }')
table.insert(svg, 'svg:hover .ts-v-group:not(:hover) { opacity: 0.35; }')
table.insert(svg, '.ts-v-group { transition: opacity 0.25s ease; }')
table.insert(svg, '</style>')
-- 1. Слой сетки/фона (декоративный)
table.insert(svg, '<rect width="100%" height="100%" fill="#fafbfc" rx="8" />')
-- 2. Слой линий и путей
if data.lines then
for i, line in ipairs(data.lines) do
local color = line.color or '#888888'
local points = {}
local futurePoints = {}
table.insert(svg, '<g class="ts-v-group ts-v-line-' .. (line.number or i) .. '">')
-- Сбор координат станций
if line.stations then
for _, st in ipairs(line.stations) do
local x, y = parseCoords(st.coords)
if x and y then
table.insert(points, x .. ',' .. y)
end
end
end
-- Отрисовка основной сплошной трассы
if #points > 1 then
table.insert(svg, '<polyline class="ts-v-line" points="' .. table.concat(points, ' ') .. '" fill="none" stroke="' .. color .. '" stroke-width="5.5" stroke-linecap="round" stroke-linejoin="round" />')
end
-- Отрисовка станций и подписей
if line.stations then
for _, st in ipairs(line.stations) do
local x, y = parseCoords(st.coords)
if x and y then
local stName = st.name or st.article or ''
local article = st.article or stName
local isFuture = st.future == true
table.insert(svg, '<a href="/wiki/' .. mw.uri.encode(article, 'WIKI') .. '" title="' .. mw.text.nowiki(stName) .. '">')
table.insert(svg, '<g class="ts-v-station">')
-- Кружок станции
if isFuture then
table.insert(svg, '<circle cx="' .. x .. '" cy="' .. y .. '" r="4.5" fill="#ffffff" stroke="' .. color .. '" stroke-width="2.5" stroke-dasharray="2,2" />')
else
table.insert(svg, '<circle cx="' .. x .. '" cy="' .. y .. '" r="5" fill="#ffffff" stroke="' .. color .. '" stroke-width="3" />')
end
-- Текстовая подпись станции (с небольшим смещением)
local textOffset = (x > (width / 2)) and (x + 9) or (x - 9)
local textAnchor = (x > (width / 2)) and 'start' or 'end'
local textColor = isFuture and '#6e7781' or '#24292f'
table.insert(svg, '<text class="ts-v-label" x="' .. textOffset .. '" y="' .. (y + 3.5) .. '" text-anchor="' .. textAnchor .. '" fill="' .. textColor .. '">' .. mw.text.nowiki(stName) .. '</text>')
table.insert(svg, '</g></a>')
end
end
end
table.insert(svg, '</g>')
end
end
table.insert(svg, '</svg>')
return table.concat(svg, '\n')
end
return p