Модуль:ТранспортнаяСхема
Внешний вид
Для документации этого модуля может быть создана страница Модуль:ТранспортнаяСхема/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
-- Загрузка данных из data.json
local function loadData(args)
local dataParam = args['данные'] or args[1] or 'Муроморского'
local candidates = {
dataParam,
'Шаблон:Интерактивная схема ' .. dataParam .. ' метрополитена/data.json',
'Шаблон:Интерактивная схема ' .. dataParam .. '/data.json',
'Шаблон:' .. dataParam .. '/data.json',
dataParam .. '/data.json'
}
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('Страница данных JSON не найдена: ' .. 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
-- Генерация карты на чистом HTML/CSS
function p.canvas(frame)
local args = getArgs(frame)
local data = loadData(args)
local mapWidth = tonumber(args['ширина'] or 850) or 850
local mapHeight = tonumber(args['высота'] or 620) or 620
local out = mw.html.create('div')
:addClass('ts-html-transport-map')
:css({
['position'] = 'relative',
['width'] = mapWidth .. 'px',
['height'] = mapHeight .. 'px',
['max-width'] = '100%',
['background-color'] = '#ffffff',
['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)',
['background-size'] = '24px 24px',
['border'] = '1px solid #d0d7de',
['border-radius'] = '10px',
['overflow'] = 'hidden',
['margin'] = '0 auto',
['user-select'] = 'none',
['box-shadow'] = '0 3px 10px rgba(0,0,0,0.04)',
['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
})
-- Плашка логотипа в левом верхнем углу
local header = out:tag('div')
:css({
['position'] = 'absolute',
['top'] = '16px',
['left'] = '18px',
['display'] = 'flex',
['align-items'] = 'center',
['gap'] = '10px',
['z-index'] = '5',
['background'] = 'rgba(255,255,255,0.92)',
['padding'] = '6px 12px',
['border-radius'] = '8px',
['border'] = '1px solid #e1e4e8',
['box-shadow'] = '0 2px 6px rgba(0,0,0,0.04)'
})
header:tag('div')
:css({
['width'] = '24px',
['height'] = '24px',
['background'] = '#ED1B24',
['color'] = '#fff',
['font-weight'] = 'bold',
['font-size'] = '13px',
['display'] = 'flex',
['align-items'] = 'center',
['justify-content'] = 'center',
['border-radius'] = '50%'
}):wikitext('M')
header:tag('div')
:css({['text-align'] = 'left'})
:tag('div'):css({['font-weight'] = 'bold', ['font-size'] = '13px', ['color'] = '#1f2328'}):wikitext(data.title or 'Муроморский скоростной транспорт'):done()
:tag('div'):css({['font-size'] = '10px', ['color'] = '#57606a', ['text-transform'] = 'uppercase', ['letter-spacing'] = '0.5px'}):wikitext('Интерактивная схема линий'):done()
if data.lines then
-- 1. Слой путей (линий между станциями)
for _, line in ipairs(data.lines) do
local color = line.color or '#888888'
local stations = line.stations or {}
for i = 1, #stations - 1 do
local st1, st2 = stations[i], stations[i+1]
local x1, y1 = parseCoords(st1.coords)
local x2, y2 = parseCoords(st2.coords)
if x1 and y1 and x2 and y2 then
local dx = x2 - x1
local dy = y2 - y1
local length = math.sqrt(dx * dx + dy * dy)
local angle = math.deg(math.atan2(dy, dx))
local isFuture = (st1.future and st2.future)
local track = out:tag('div')
:css({
['position'] = 'absolute',
['left'] = x1 .. 'px',
['top'] = (y1 - 3) .. 'px',
['width'] = length .. 'px',
['height'] = isFuture and '0px' or '6px',
['background-color'] = isFuture and 'transparent' or color,
['border-top'] = isFuture and ('5px dashed ' .. color) or 'none',
['transform-origin'] = '0 50%',
['transform'] = 'rotate(' .. angle .. 'deg)',
['border-radius'] = '3px',
['z-index'] = '1',
['pointer-events'] = 'none'
})
end
end
end
-- 2. Слой станций и подписей
for _, line in ipairs(data.lines) do
local color = line.color or '#888888'
for _, st in ipairs(line.stations or {}) do
local x, y = parseCoords(st.coords)
if x and y then
local name = st.name or st.article or ''
local article = st.article or name
local isFuture = (st.future == true)
-- Кружок станции
local node = out:tag('div')
:css({
['position'] = 'absolute',
['left'] = (x - 7) .. 'px',
['top'] = (y - 7) .. 'px',
['width'] = '14px',
['height'] = '14px',
['border-radius'] = '50%',
['background-color'] = '#ffffff',
['border'] = isFuture and ('2.5px dashed ' .. color) or ('3px solid ' .. color),
['box-sizing'] = 'border-box',
['box-shadow'] = '0 1px 3px rgba(0,0,0,0.25)',
['z-index'] = '3',
['cursor'] = 'pointer'
})
node:wikitext('[[' .. article .. '|<span style="display:block;width:100%;height:100%;" title="' .. name .. '"></span>]]')
-- Текстовая подпись станции
local isRightSide = (x >= 430)
local label = out:tag('div')
:css({
['position'] = 'absolute',
['left'] = isRightSide and ((x + 11) .. 'px') or ((x - 11) .. 'px'),
['top'] = y .. 'px',
['transform'] = isRightSide and 'translateY(-50%)' or 'translate(-100%, -50%)',
['text-align'] = isRightSide and 'left' or 'right',
['white-space'] = 'nowrap',
['font-size'] = '11.5px',
['font-weight'] = '500',
['z-index'] = '4',
['text-shadow'] = '0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 4px #ffffff'
})
local labelStyle = isFuture and 'color: #6e7781; font-style: italic; text-decoration: none;' or 'color: #1f2328; text-decoration: none;'
label:wikitext('[[' .. article .. '|<span style="' .. labelStyle .. '">' .. name .. '</span>]]')
end
end
end
end
return tostring(out)
end
return p