Модуль:ТранспортнаяСхема: различия между версиями
Tringoli (обсуждение | вклад) мНет описания правки |
Tringoli (обсуждение | вклад) мНет описания правки |
||
| Строка 1: | Строка 1: | ||
local p = {} | local p = {} | ||
-- Извлечение | -- Извлечение аргументов вызова | ||
local function getArgs(frame) | local function getArgs(frame) | ||
local args = {} | local args = {} | ||
| Строка 16: | Строка 16: | ||
end | end | ||
-- Загрузка | -- Загрузка данных из data.json | ||
local function loadData( | local function loadData(args) | ||
local dataParam = args['данные'] or args[1] or 'Муроморского' | |||
local candidates = { | local candidates = { | ||
dataParam, | dataParam, | ||
| Строка 30: | Строка 27: | ||
} | } | ||
for _, | for _, title in ipairs(candidates) do | ||
local success, | local success, jsonData = pcall(mw.loadJsonData, title) | ||
if success and | if success and jsonData then return jsonData end | ||
local titleObj = mw.title.new( | local titleObj = mw.title.new(title) | ||
if titleObj and titleObj.exists then | if titleObj and titleObj.exists then | ||
local raw = titleObj:getContent() | local raw = titleObj:getContent() | ||
| Строка 40: | Строка 37: | ||
raw = mw.ustring.gsub(raw, '<noinclude>.-</noinclude>', '') | raw = mw.ustring.gsub(raw, '<noinclude>.-</noinclude>', '') | ||
raw = mw.ustring.gsub(raw, '<!--.-?-->', '') | raw = mw.ustring.gsub(raw, '<!--.-?-->', '') | ||
local ok, | local ok, decoded = pcall(mw.text.jsonDecode, mw.text.trim(raw)) | ||
if ok and | if ok and decoded then return decoded end | ||
end | end | ||
end | end | ||
end | end | ||
error('Страница данных не найдена | error('Страница данных JSON не найдена: ' .. dataParam) | ||
end | end | ||
-- Генерация | -- Парсер координат "X Y" | ||
function p. | 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 args = getArgs(frame) | ||
local data = loadData(args) | |||
local data = loadData( | |||
local | local mapWidth = tonumber(args['ширина'] or 850) or 850 | ||
local mapHeight = tonumber(args['высота'] or 620) or 620 | |||
local | |||
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 | if data.lines then | ||
-- 1. Слой путей (линий между станциями) | |||
for _, line in ipairs(data.lines) do | 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 | 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 | ||
end | end | ||
end | end | ||
return tostring(out) | |||
return | |||
end | end | ||
return p | return p | ||