From a1007f5a4ffd9e6091748ddbaa02340f90b203ad Mon Sep 17 00:00:00 2001
From: thekingofcity <3353040+thekingofcity@users.noreply.github.com>
Date: Wed, 19 Mar 2025 20:33:46 +0800
Subject: [PATCH 01/12] #705 Shanghai Metro 2024 style
---
src/constants/constants.ts | 1 +
src/svgs/shmetro/railmap-shmetro.tsx | 41 ++++++++++++++++++++++++++++
src/svgs/shmetro/runin-shmetro.tsx | 28 ++++++++++---------
src/svgs/shmetro/station-shmetro.tsx | 37 ++++++++++++++++++++++---
4 files changed, 90 insertions(+), 17 deletions(-)
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index e038d7027..6ae4273ff 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -192,6 +192,7 @@ export enum PanelTypeGZMTR {
export enum PanelTypeShmetro {
sh = 'sh',
sh2020 = 'sh2020',
+ sh2024 = 'sh2024',
}
/**
diff --git a/src/svgs/shmetro/railmap-shmetro.tsx b/src/svgs/shmetro/railmap-shmetro.tsx
index 9b3af68b0..04034fdb3 100644
--- a/src/svgs/shmetro/railmap-shmetro.tsx
+++ b/src/svgs/shmetro/railmap-shmetro.tsx
@@ -62,6 +62,30 @@ const DefsSHMetro = memo(function DefsSHMetro() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -146,6 +170,23 @@ const DefsSHMetro = memo(function DefsSHMetro() {
{/* Outline filter of white pass color in Pujiang Line */}
+
+ {/* 2024 Station border white outline */}
+
+
+
+
+
+
+
);
});
diff --git a/src/svgs/shmetro/runin-shmetro.tsx b/src/svgs/shmetro/runin-shmetro.tsx
index 82b15e97c..cf607ae68 100644
--- a/src/svgs/shmetro/runin-shmetro.tsx
+++ b/src/svgs/shmetro/runin-shmetro.tsx
@@ -505,8 +505,8 @@ const NextText = (props: { nextName: Translation } & SVGProps) => {
};
const PrevStn = (props: { stnIds: string[] }) => {
- const param = useRootSelector(store => store.param);
- const prevNames = props.stnIds.map(stnId => param.stn_list[stnId].localisedName);
+ const { stn_list, direction, svgWidth, info_panel_type } = useRootSelector(store => store.param);
+ const prevNames = props.stnIds.map(stnId => stn_list[stnId].localisedName);
const prevHintDy =
(props.stnIds.length > 1 ? 15 : 125) +
prevNames.map(name => name.zh?.split('\\')?.length ?? 1).reduce((acc, cur) => acc + cur, -prevNames.length) *
@@ -519,11 +519,13 @@ const PrevStn = (props: { stnIds: string[] }) => {
? (prevZhName.split('\\').length - 1) * -50 + (prevEnName.split('\\').length - 1) * -30
: 0) + 70;
+ const previousText = info_panel_type === 'sh2024' ? 'Previous Stop' : 'Past Stop';
+
return (
{props.stnIds.length > 1 && (
@@ -533,8 +535,8 @@ const PrevStn = (props: { stnIds: string[] }) => {
上一站
-
- Past Stop
+
+ {previousText}
@@ -542,8 +544,8 @@ const PrevStn = (props: { stnIds: string[] }) => {
};
const NextStn = (props: { stnIds: string[] }) => {
- const param = useRootSelector(store => store.param);
- const nextNames = props.stnIds.map(stnId => param.stn_list[stnId].localisedName);
+ const { stn_list, direction, svgWidth } = useRootSelector(store => store.param);
+ const nextNames = props.stnIds.map(stnId => stn_list[stnId].localisedName);
const nextHintDy =
(props.stnIds.length > 1 ? 15 : 125) +
nextNames.map(name => name.zh?.split('\\')?.length ?? 1).reduce((acc, cur) => acc + cur, -nextNames.length) *
@@ -558,13 +560,13 @@ const NextStn = (props: { stnIds: string[] }) => {
return (
-
+
{props.stnIds.length > 1 && (
)}
@@ -572,7 +574,7 @@ const NextStn = (props: { stnIds: string[] }) => {
下一站
-
+
Next Stop
diff --git a/src/svgs/shmetro/station-shmetro.tsx b/src/svgs/shmetro/station-shmetro.tsx
index 25b58c7c9..afe647e5a 100644
--- a/src/svgs/shmetro/station-shmetro.tsx
+++ b/src/svgs/shmetro/station-shmetro.tsx
@@ -1,5 +1,5 @@
import { ColourHex } from '@railmapgen/rmg-palette-resources';
-import { ExtendedInterchangeInfo, Facilities, InterchangeGroup } from '../../constants/constants';
+import { ExtendedInterchangeInfo, Facilities, InterchangeGroup, PanelTypeShmetro } from '../../constants/constants';
import { useRootSelector } from '../../redux';
import { forwardRef, memo, Ref, SVGProps, useEffect, useMemo, useRef, useState } from 'react';
import { Translation } from '@railmapgen/rmg-translate';
@@ -28,7 +28,34 @@ const StationSHMetro = (props: Props) => {
let stationIconStyle: string;
const stationIconColor: { [pos: string]: string } = {};
- if (info_panel_type === 'sh2020') {
+ if (info_panel_type === 'sh2024') {
+ const int_length = stnInfo.transfer.groups.at(0)?.lines?.length ?? 0;
+ const osi_osysi_length = [
+ ...(stnInfo.transfer.groups.at(1)?.lines || []),
+ ...(stnInfo.transfer.groups.at(2)?.lines || []),
+ ].length;
+
+ if (stnInfo.services.length === 3) stationIconStyle = 'stn_sh_2020_direct';
+ else if (stnInfo.services.length === 2) stationIconStyle = 'stn_sh_2020_express';
+ else if (int_length > 0 && osi_osysi_length === 0) {
+ // 仅换乘车站
+ stationIconStyle = 'stn_sh_2024_int';
+ stationIconColor.stroke = stnState === -1 ? 'gray' : color ? color : 'var(--rmg-theme-colour)';
+ } else if (int_length > 0 && osi_osysi_length > 0) {
+ // 站内换乘+出站换乘
+ stationIconStyle = 'stn_sh_2024_int_osysi';
+ stationIconColor.stroke = stnState === -1 ? 'gray' : color ? color : 'var(--rmg-theme-colour)';
+ } else if (int_length === 0 && osi_osysi_length === 1) {
+ // 仅2线出站换乘
+ stationIconStyle = 'stn_sh_2024_osysi2';
+ stationIconColor.stroke = stnState === -1 ? 'gray' : color ? color : 'var(--rmg-theme-colour)';
+ } else if (int_length === 0 && osi_osysi_length === 2) {
+ // 仅3线出站换乘
+ stationIconStyle = 'stn_sh_2024_osysi3';
+ stationIconColor.stroke = stnState === -1 ? 'gray' : color ? color : 'var(--rmg-theme-colour)';
+ } else stationIconStyle = 'stn_sh_2020';
+ stationIconColor.fill = stnState === -1 ? 'gray' : color ? color : 'var(--rmg-theme-colour)';
+ } else if (info_panel_type === 'sh2020') {
if (stnInfo.services.length === 3) stationIconStyle = 'stn_sh_2020_direct';
else if (stnInfo.services.length === 2) stationIconStyle = 'stn_sh_2020_express';
else stationIconStyle = 'stn_sh_2020';
@@ -45,7 +72,8 @@ const StationSHMetro = (props: Props) => {
const bank = bank_ ?? 0;
const dx = (direction === 'l' ? 6 : -6) + branchNameDX + bank * 30;
- const dy = (info_panel_type === 'sh2020' ? -20 : -6) + Math.abs(bank) * (info_panel_type === 'sh2020' ? 25 : 11);
+ const is2020or2024 = info_panel_type === 'sh2020' || info_panel_type === 'sh2024';
+ const dy = (is2020or2024 ? -20 : -6) + Math.abs(bank) * (is2020or2024 ? 25 : 11);
const dr = bank ? 0 : direction === 'l' ? -45 : 45;
return (
<>
@@ -90,6 +118,7 @@ interface StationNameGElementProps {
const StationNameGElement = (props: StationNameGElementProps) => {
const { name, groups, stnState, direction, facility, bank, oneLine, intPadding } = props;
+ const { info_panel_type } = useRootSelector(store => store.param);
// legacy ref to get the exact station name width
const stnNameEl = useRef(null);
@@ -142,7 +171,7 @@ const StationNameGElement = (props: StationNameGElementProps) => {
/>
{/* this is out-of-station text displayed above the IntBoxGroup */}
- {groups[1]?.lines?.length && (
+ {groups[1]?.lines?.length && info_panel_type !== PanelTypeShmetro.sh2024 && (
From 940e6f840b3b68d408606ce47db0c077e3e9740f Mon Sep 17 00:00:00 2001
From: thekingofcity <3353040+thekingofcity@users.noreply.github.com>
Date: Fri, 18 Jul 2025 15:44:51 +0800
Subject: [PATCH 02/12] #659 Shanghai Metro railmap canvas 2024 style
---
.../station-side-panel/more-section.tsx | 6 +-
src/svgs/shmetro/runin-shmetro.tsx | 6 +-
src/svgs/shmetro/station-shmetro.tsx | 293 +++++++++++++-----
3 files changed, 230 insertions(+), 75 deletions(-)
diff --git a/src/components/side-panel/station-side-panel/more-section.tsx b/src/components/side-panel/station-side-panel/more-section.tsx
index 3cf15d8ea..82aff8d4a 100644
--- a/src/components/side-panel/station-side-panel/more-section.tsx
+++ b/src/components/side-panel/station-side-panel/more-section.tsx
@@ -1,7 +1,7 @@
import { Box, Heading } from '@chakra-ui/react';
import { RmgButtonGroup, RmgFields, RmgFieldsField } from '@railmapgen/rmg-components';
import { useTranslation } from 'react-i18next';
-import { FACILITIES, Facilities, RmgStyle, Services, TEMP } from '../../../constants/constants';
+import { FACILITIES, Facilities, PanelTypeShmetro, RmgStyle, Services, TEMP } from '../../../constants/constants';
import { useRootDispatch, useRootSelector } from '../../../redux';
import {
updateStationCharacterSpacing,
@@ -20,7 +20,7 @@ export default function MoreSection() {
const dispatch = useRootDispatch();
const selectedStation = useRootSelector(state => state.app.selectedStation);
- const { style, loop } = useRootSelector(state => state.param);
+ const { style, loop, info_panel_type } = useRootSelector(state => state.param);
const { services, facility, loop_pivot, one_line, int_padding, character_spacing, underConstruction } =
useRootSelector(state => state.param.stn_list[selectedStation]);
@@ -79,7 +79,7 @@ export default function MoreSection() {
label: t('StationSidePanel.more.oneLine'),
isChecked: one_line,
onChange: checked => dispatch(updateStationOneLine(selectedStation, checked)),
- hidden: ![RmgStyle.SHMetro].includes(style),
+ hidden: !(style === RmgStyle.SHMetro && info_panel_type !== PanelTypeShmetro.sh2024),
minW: 'full',
oneLine: true,
},
diff --git a/src/svgs/shmetro/runin-shmetro.tsx b/src/svgs/shmetro/runin-shmetro.tsx
index cf607ae68..04dbeb1ad 100644
--- a/src/svgs/shmetro/runin-shmetro.tsx
+++ b/src/svgs/shmetro/runin-shmetro.tsx
@@ -1,12 +1,12 @@
/* eslint @typescript-eslint/no-non-null-assertion: 0 */
+import { Translation } from '@railmapgen/rmg-translate';
import { memo, SVGProps, useMemo } from 'react';
-import { CanvasType, StationDict } from '../../constants/constants';
+import { CanvasType, PanelTypeShmetro, StationDict } from '../../constants/constants';
import { useRootSelector } from '../../redux';
import { isColineBranch } from '../../redux/param/coline-action';
import { calculateColineStations } from '../methods/shmetro-coline';
import SvgWrapper from '../svg-wrapper';
import PujiangLineDefs from './pujiang-line-filter';
-import { Translation } from '@railmapgen/rmg-translate';
const LINE_WIDTH = 12;
@@ -519,7 +519,7 @@ const PrevStn = (props: { stnIds: string[] }) => {
? (prevZhName.split('\\').length - 1) * -50 + (prevEnName.split('\\').length - 1) * -30
: 0) + 70;
- const previousText = info_panel_type === 'sh2024' ? 'Previous Stop' : 'Past Stop';
+ const previousText = info_panel_type === PanelTypeShmetro.sh2024 ? 'Previous Stop' : 'Past Stop';
return (
{
let stationIconStyle: string;
const stationIconColor: { [pos: string]: string } = {};
- if (info_panel_type === 'sh2024') {
+ if (info_panel_type === PanelTypeShmetro.sh2024) {
const int_length = stnInfo.transfer.groups.at(0)?.lines?.length ?? 0;
const osi_osysi_length = [
...(stnInfo.transfer.groups.at(1)?.lines || []),
@@ -72,7 +72,7 @@ const StationSHMetro = (props: Props) => {
const bank = bank_ ?? 0;
const dx = (direction === 'l' ? 6 : -6) + branchNameDX + bank * 30;
- const is2020or2024 = info_panel_type === 'sh2020' || info_panel_type === 'sh2024';
+ const is2020or2024 = info_panel_type === PanelTypeShmetro.sh2020 || info_panel_type === PanelTypeShmetro.sh2024;
const dy = (is2020or2024 ? -20 : -6) + Math.abs(bank) * (is2020or2024 ? 25 : 11);
const dr = bank ? 0 : direction === 'l' ? -45 : 45;
return (
@@ -132,9 +132,14 @@ const StationNameGElement = (props: StationNameGElementProps) => {
// interchange will have a line under the name, and should be stretched when placed horizontal in loop
const lineDx = bank ? -12 : 0;
+ // int group
const intEl = useRef(null);
const [intWidth, setIntWidth] = useState(0);
- useEffect(() => setIntWidth(intEl.current?.getBBox().width ?? 0), [JSON.stringify(groups)]);
+ useEffect(() => {
+ // IntBoxGroup2024 will use double render to get the width of the text elements
+ // so we need to wait and get the int group width
+ setTimeout(() => setIntWidth(intEl.current?.getBBox().width ?? 0), 10);
+ }, [JSON.stringify(groups), directionPolarity]);
const intDx = intPadding - intWidth;
return (
@@ -144,15 +149,27 @@ const StationNameGElement = (props: StationNameGElementProps) => {
-
+ {info_panel_type !== PanelTypeShmetro.sh2024 ? (
+ <>
+
+ >
+ ) : (
+
+ )}
>
)}
@@ -165,23 +182,26 @@ const StationNameGElement = (props: StationNameGElementProps) => {
- {/* this is out-of-station text displayed above the IntBoxGroup */}
- {groups[1]?.lines?.length && info_panel_type !== PanelTypeShmetro.sh2024 && (
-
-
-
- )}
-
- {/* deal out-of-system here as it's dx is fixed and has nothing to do with IntBoxGroup */}
- {groups[2]?.lines?.length && (
-
-
-
+ {info_panel_type !== PanelTypeShmetro.sh2024 && (
+ <>
+ {/* this is out-of-station text displayed above the IntBoxGroup */}
+ {groups[1]?.lines?.length && (
+
+
+
+ )}
+ {/* deal out-of-system here as it's dx is fixed and has nothing to do with IntBoxGroup */}
+ {groups[2]?.lines?.length && (
+
+
+
+ )}
+ >
)}
>
@@ -206,38 +226,27 @@ const StationName = forwardRef(function StationName(
return (
- {useMemo(
- () => (
- <>
-
- {zhName.split('\\').map((txt, i, arr) => (
-
- {txt}
-
- ))}
-
-
- {enName.split('\\').map((txt, i, arr) => (
-
- {txt}
-
- ))}
-
- >
- ),
- [zhName, enName, oneLine, enDx, directionPolarity]
- )}
+
+ {zhName.split('\\').map((txt, i, arr) => (
+
+ {txt}
+
+ ))}
+
+
+ {enName.split('\\').map((txt, i, arr) => (
+
+ {txt}
+
+ ))}
+
);
});
@@ -317,6 +326,119 @@ const IntBoxGroup = forwardRef(function IntBoxGroup(
);
});
+const IntBoxGroup2024 = forwardRef(function IntBoxGroup2024(
+ props: { groups: InterchangeGroup[]; direction: 'l' | 'r'; stnState: -1 | 0 | 1; intPadding: number },
+ ref: Ref
+) {
+ const { groups, direction, stnState, intPadding } = props;
+ const directionPolarity = direction === 'l' ? 1 : -1;
+
+ const transfer = [groups.at(0)?.lines ?? [], groups.at(1)?.lines ?? [], groups.at(2)?.lines ?? []];
+
+ const [outOfSystemLine, setOutOfSystemLine] = useState([0, 0]); // also for start point of 出站换乘
+ const [intBoxesDX, setIntBoxesDX] = useState<{ [k in string]: number }>({});
+ const textLineRefs = useRef<{ [k in string]: SVGGElement }>({});
+ const [intBoxGroupWidth, setIntBoxGroupWidth] = useState(0);
+ useEffect(() => {
+ // update the width of each text line
+ const textLineWidth = Object.fromEntries(
+ transfer
+ .flat()
+ .filter(info => !info.name[0].match(/^(\d+)号线$/))
+ .map(info => {
+ const key = info.name[0];
+ return [key, textLineRefs.current[key]?.getBBox().width ?? 0];
+ })
+ );
+
+ const getBBoxWidth = (info: ExtendedInterchangeInfo) => {
+ const key = info.name[0];
+ const lineNumber = key.match(/^(\d+)号线$/);
+ const boxWidth = lineNumber ? (Number(lineNumber[1]) >= 10 ? 22 : 20) : textLineWidth[info.name[0]];
+ intBoxDX[key] = dx * directionPolarity + (lineNumber && direction === 'r' ? -boxWidth : 0);
+ return boxWidth + 2;
+ };
+
+ let dx = 0; // update in every box
+ const intBoxDX: { [k in string]: number } = {};
+ transfer[0].forEach(info => {
+ dx += getBBoxWidth(info);
+ });
+ let outOfSystemLine = [0, 0];
+ if (transfer[1].length) {
+ if (transfer[0].length) {
+ outOfSystemLine = [dx, dx + 22];
+ dx += 22 * 2;
+ } else {
+ dx += 2; // padding
+ // hide this line if no previous transfer
+ outOfSystemLine = [dx, dx];
+ dx += 22;
+ }
+ transfer[1].forEach(info => {
+ dx += getBBoxWidth(info);
+ });
+ }
+ transfer[2].forEach(info => {
+ dx += getBBoxWidth(info);
+ });
+ setIntBoxesDX(intBoxDX);
+ setOutOfSystemLine(outOfSystemLine);
+ setIntBoxGroupWidth(dx);
+ }, [JSON.stringify(transfer), direction]);
+
+ const makeBoxElement = (info: ExtendedInterchangeInfo) => {
+ const key = info.name[0];
+ const isLineNumber = Boolean(key.match(/^(\d+)号线$/));
+ return (
+ {
+ if (el && !isLineNumber) textLineRefs.current[key] = el;
+ }}
+ transform={`translate(${intBoxesDX[key] ?? 0},-11)`}
+ >
+ {isLineNumber ? (
+
+ ) : (
+
+ )}
+
+ );
+ };
+
+ const intBoxDX = (intPadding - intBoxGroupWidth) * directionPolarity;
+ return (
+
+ {transfer[0].map(makeBoxElement)}
+ {transfer[1].length && (
+ <>
+ {transfer[0].length > 0 && (
+
+ )}
+
+ 出站
+ 换乘
+
+ {transfer[1].map(makeBoxElement)}
+ >
+ )}
+ {transfer[2].map(makeBoxElement)}
+
+ );
+});
+
const IntBoxMaglev = memo(
function IntBoxMaglev(props: { info: ExtendedInterchangeInfo }) {
return (
@@ -343,6 +465,22 @@ const IntBoxNumber = memo(
(prevProps, nextProps) => JSON.stringify(prevProps.info) === JSON.stringify(nextProps.info)
);
+const IntBoxNumber2024 = (props: { info: ExtendedInterchangeInfo }) => {
+ const {
+ info: { name, theme },
+ } = props;
+ const num = name[0].match(/^(\d+)号线$/)?.[1] ?? '';
+ const width = num.length > 1 ? 22 : 18; // 22 for 10+ lines, 18 for 1-9 lines
+ return (
+
+
+
+ {num}
+
+
+ );
+};
+
const IntBoxLetter = memo(
function IntBoxLetter(props: { info: ExtendedInterchangeInfo }) {
// box width: 16 * number of characters + 12
@@ -364,6 +502,26 @@ const IntBoxLetter = memo(
(prevProps, nextProps) => JSON.stringify(prevProps.info) === JSON.stringify(nextProps.info)
);
+const IntBoxText2024 = (props: { info: ExtendedInterchangeInfo; state: -1 | 0 | 1; direction: 'l' | 'r' }) => {
+ const {
+ info: { name },
+ state,
+ direction,
+ } = props;
+ return (
+
+ {name[0]}
+ {name[1]}
+
+ );
+};
+
const OSIText = (props: { osiInfos: ExtendedInterchangeInfo[] }) => {
// get the all names from the out of station interchanges
const lineNames = props.osiInfos.map(info => info.name[0]).join(',');
@@ -390,17 +548,14 @@ const OSysIText = (props: { osysiInfos: ExtendedInterchangeInfo[]; direction: 'l
const lineNames = props.osysiInfos.map(info => info.name[0]).join(',');
const lineNamesEn = props.osysiInfos.map(info => info.name[1]).join(', ');
- return useMemo(
- () => (
-
-
- 转乘{lineNames}
-
-
- To {lineNamesEn}
-
-
- ),
- [JSON.stringify(props.osysiInfos), props.direction]
+ return (
+
+
+ 转乘{lineNames}
+
+
+ To {lineNamesEn}
+
+
);
};
From dcb3d292de066283ae6b0a49f57dad013a5d5a27 Mon Sep 17 00:00:00 2001
From: thekingofcity <3353040+thekingofcity@users.noreply.github.com>
Date: Sat, 19 Jul 2025 17:30:01 +0800
Subject: [PATCH 03/12] stn in middle of the railmap
---
src/svgs/shmetro/railmap-shmetro.tsx | 26 +++++++++++++-------------
src/svgs/shmetro/station-shmetro.tsx | 10 ++++------
2 files changed, 17 insertions(+), 19 deletions(-)
diff --git a/src/svgs/shmetro/railmap-shmetro.tsx b/src/svgs/shmetro/railmap-shmetro.tsx
index 04034fdb3..4740d18a9 100644
--- a/src/svgs/shmetro/railmap-shmetro.tsx
+++ b/src/svgs/shmetro/railmap-shmetro.tsx
@@ -66,25 +66,25 @@ const DefsSHMetro = memo(function DefsSHMetro() {
id="stn_sh_2024_int"
fill="var(--rmg-white)"
strokeWidth={2}
- d="M 0,-12 a 5,5 0 1 1 10,0 V0 a 5,5 0 1 1 -10,0 Z"
+ d="M -5,-12 a 5,5 0 1 1 10,0 V0 a 5,5 0 1 1 -10,0 Z"
/>
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/svgs/shmetro/station-shmetro.tsx b/src/svgs/shmetro/station-shmetro.tsx
index 3cdd4aba3..6025adaac 100644
--- a/src/svgs/shmetro/station-shmetro.tsx
+++ b/src/svgs/shmetro/station-shmetro.tsx
@@ -55,7 +55,7 @@ const StationSHMetro = (props: Props) => {
stationIconColor.stroke = stnState === -1 ? 'gray' : color ? color : 'var(--rmg-theme-colour)';
} else stationIconStyle = 'stn_sh_2020';
stationIconColor.fill = stnState === -1 ? 'gray' : color ? color : 'var(--rmg-theme-colour)';
- } else if (info_panel_type === 'sh2020') {
+ } else if (info_panel_type === PanelTypeShmetro.sh2020) {
if (stnInfo.services.length === 3) stationIconStyle = 'stn_sh_2020_direct';
else if (stnInfo.services.length === 2) stationIconStyle = 'stn_sh_2020_express';
else stationIconStyle = 'stn_sh_2020';
@@ -71,7 +71,8 @@ const StationSHMetro = (props: Props) => {
}
const bank = bank_ ?? 0;
- const dx = (direction === 'l' ? 6 : -6) + branchNameDX + bank * 30;
+ const dx2024 = info_panel_type === PanelTypeShmetro.sh2024 ? (direction === 'l' ? -5 : 5) : 0;
+ const dx = (direction === 'l' ? 6 : -6) + branchNameDX + bank * 30 + dx2024;
const is2020or2024 = info_panel_type === PanelTypeShmetro.sh2020 || info_panel_type === PanelTypeShmetro.sh2024;
const dy = (is2020or2024 ? -20 : -6) + Math.abs(bank) * (is2020or2024 ? 25 : 11);
const dr = bank ? 0 : direction === 'l' ? -45 : 45;
@@ -81,10 +82,7 @@ const StationSHMetro = (props: Props) => {
xlinkHref={`#${stationIconStyle}`}
{...stationIconColor} // different styles use either `fill` or `stroke`
// sh and sh2020 have different headings of int_sh, so -1 | 1 is multiplied
- transform={
- `translate(${bank * (info_panel_type === 'sh2020' ? 5 : 0)},0)` +
- `rotate(${bank * 90 * (info_panel_type === 'sh2020' ? 1 : -1)})`
- }
+ transform={`translate(${bank * (is2020or2024 ? 5 : 0)},0)rotate(${bank * 90 * (is2020or2024 ? 1 : -1)})`}
/>
Date: Sat, 19 Jul 2025 20:57:09 +0800
Subject: [PATCH 04/12] fine tune of int box width
---
src/svgs/shmetro/station-shmetro.tsx | 51 ++++++++++++++++++++--------
1 file changed, 37 insertions(+), 14 deletions(-)
diff --git a/src/svgs/shmetro/station-shmetro.tsx b/src/svgs/shmetro/station-shmetro.tsx
index 6025adaac..da6f40a35 100644
--- a/src/svgs/shmetro/station-shmetro.tsx
+++ b/src/svgs/shmetro/station-shmetro.tsx
@@ -4,6 +4,15 @@ import { forwardRef, memo, Ref, SVGProps, useEffect, useMemo, useRef, useState }
import { ExtendedInterchangeInfo, Facilities, InterchangeGroup, PanelTypeShmetro } from '../../constants/constants';
import { useRootSelector } from '../../redux';
+const INT_BOX_SIZE = {
+ width: {
+ singleDigit: 19.8,
+ doubleDigit: 24.2,
+ },
+ height: 22,
+ padding: 2,
+};
+
interface Props {
stnId: string;
stnState: -1 | 0 | 1;
@@ -352,9 +361,13 @@ const IntBoxGroup2024 = forwardRef(function IntBoxGroup2024(
const getBBoxWidth = (info: ExtendedInterchangeInfo) => {
const key = info.name[0];
const lineNumber = key.match(/^(\d+)号线$/);
- const boxWidth = lineNumber ? (Number(lineNumber[1]) >= 10 ? 22 : 20) : textLineWidth[info.name[0]];
+ const boxWidth = lineNumber
+ ? Number(lineNumber[1]) >= 10
+ ? INT_BOX_SIZE.width.doubleDigit
+ : INT_BOX_SIZE.width.singleDigit
+ : textLineWidth[info.name[0]];
intBoxDX[key] = dx * directionPolarity + (lineNumber && direction === 'r' ? -boxWidth : 0);
- return boxWidth + 2;
+ return boxWidth + INT_BOX_SIZE.padding;
};
let dx = 0; // update in every box
@@ -364,14 +377,17 @@ const IntBoxGroup2024 = forwardRef(function IntBoxGroup2024(
});
let outOfSystemLine = [0, 0];
if (transfer[1].length) {
+ // there will be a line and a text element for 出站换乘
+ // each will take 22px
+ const elementWidth = INT_BOX_SIZE.height;
if (transfer[0].length) {
- outOfSystemLine = [dx, dx + 22];
- dx += 22 * 2;
+ outOfSystemLine = [dx, dx + elementWidth];
+ dx += elementWidth * 2;
} else {
- dx += 2; // padding
- // hide this line if no previous transfer
+ dx += INT_BOX_SIZE.padding;
+ // hide this line if there is no previous transfer
outOfSystemLine = [dx, dx];
- dx += 22;
+ dx += elementWidth;
}
transfer[1].forEach(info => {
dx += getBBoxWidth(info);
@@ -394,7 +410,7 @@ const IntBoxGroup2024 = forwardRef(function IntBoxGroup2024(
ref={el => {
if (el && !isLineNumber) textLineRefs.current[key] = el;
}}
- transform={`translate(${intBoxesDX[key] ?? 0},-11)`}
+ transform={`translate(${intBoxesDX[key] ?? 0},${-INT_BOX_SIZE.height / 2})`}
>
{isLineNumber ? (
@@ -407,7 +423,7 @@ const IntBoxGroup2024 = forwardRef(function IntBoxGroup2024(
const intBoxDX = (intPadding - intBoxGroupWidth) * directionPolarity;
return (
-
+
{transfer[0].map(makeBoxElement)}
{transfer[1].length && (
<>
@@ -422,7 +438,7 @@ const IntBoxGroup2024 = forwardRef(function IntBoxGroup2024(
@@ -468,11 +484,18 @@ const IntBoxNumber2024 = (props: { info: ExtendedInterchangeInfo }) => {
info: { name, theme },
} = props;
const num = name[0].match(/^(\d+)号线$/)?.[1] ?? '';
- const width = num.length > 1 ? 22 : 18; // 22 for 10+ lines, 18 for 1-9 lines
+ const width = num.length > 1 ? INT_BOX_SIZE.width.doubleDigit : INT_BOX_SIZE.width.singleDigit;
+ const letterSpacing = num.length > 1 ? -2.5 : 0;
return (
-
-
+
+
{num}
@@ -512,7 +535,7 @@ const IntBoxText2024 = (props: { info: ExtendedInterchangeInfo; state: -1 | 0 |
fill={state < 0 ? 'gray' : 'black'}
dominantBaseline="central"
textAnchor={direction === 'l' ? 'start' : 'end'}
- fontSize="50%"
+ fontSize="7"
>
{name[0]}
{name[1]}
From 915f4b9fdcfc2d2d43c6f9f99c4ca09a095eeb48 Mon Sep 17 00:00:00 2001
From: thekingofcity <3353040+thekingofcity@users.noreply.github.com>
Date: Sat, 19 Jul 2025 21:43:06 +0800
Subject: [PATCH 05/12] #656 Shanghai Metro indoor canvas 2024 style
---
src/svgs/shmetro/indoor/indoor-shmetro.tsx | 38 ++-
src/svgs/shmetro/indoor/station-shmetro.tsx | 291 +++++++++++++++-----
src/svgs/shmetro/station-shmetro.tsx | 17 +-
3 files changed, 272 insertions(+), 74 deletions(-)
diff --git a/src/svgs/shmetro/indoor/indoor-shmetro.tsx b/src/svgs/shmetro/indoor/indoor-shmetro.tsx
index f9c453120..97ba92c75 100644
--- a/src/svgs/shmetro/indoor/indoor-shmetro.tsx
+++ b/src/svgs/shmetro/indoor/indoor-shmetro.tsx
@@ -2,7 +2,7 @@ import { memo, useMemo } from 'react';
import { adjacencyList, criticalPathMethod, getStnState, getXShareMTR } from '../../methods/share';
import StationSHMetro from './station-shmetro';
import { StationsSHMetro } from '../../methods/mtr';
-import { CanvasType, Services, StationDict } from '../../../constants/constants';
+import { CanvasType, PanelTypeShmetro, Services, StationDict } from '../../../constants/constants';
import { useRootSelector } from '../../../redux';
import LoopSHMetro from '../loop/loop-shmetro';
import SvgWrapper from '../../svg-wrapper';
@@ -11,7 +11,13 @@ const CANVAS_TYPE = CanvasType.Indoor;
export default function IndoorWrapperSHMetro() {
const { canvasScale } = useRootSelector(state => state.app);
- const { svgWidth: svgWidths, svg_height: svgHeight, theme, loop } = useRootSelector(store => store.param);
+ const {
+ svgWidth: svgWidths,
+ svg_height: svgHeight,
+ theme,
+ loop,
+ info_panel_type,
+ } = useRootSelector(store => store.param);
const svgWidth = svgWidths[CANVAS_TYPE];
@@ -25,7 +31,7 @@ export default function IndoorWrapperSHMetro() {
>
{loop ? : }
-
+ {info_panel_type !== PanelTypeShmetro.sh2024 && }
);
}
@@ -61,6 +67,32 @@ export const DefsSHMetro = memo(function DefsSHMetro() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
});
diff --git a/src/svgs/shmetro/indoor/station-shmetro.tsx b/src/svgs/shmetro/indoor/station-shmetro.tsx
index 0d1f9eb6b..98a70ae55 100644
--- a/src/svgs/shmetro/indoor/station-shmetro.tsx
+++ b/src/svgs/shmetro/indoor/station-shmetro.tsx
@@ -1,8 +1,9 @@
import { ColourHex } from '@railmapgen/rmg-palette-resources';
+import { Translation } from '@railmapgen/rmg-translate';
import { Fragment, Ref, SVGProps, forwardRef, useEffect, useMemo, useRef, useState } from 'react';
-import { ExtendedInterchangeInfo, InterchangeGroup, Services } from '../../../constants/constants';
+import { ExtendedInterchangeInfo, InterchangeGroup, PanelTypeShmetro, Services } from '../../../constants/constants';
import { useRootSelector } from '../../../redux';
-import { Translation } from '@railmapgen/rmg-translate';
+import { INT_BOX_SIZE, IntBoxNumber2024, IntBoxText2024 } from '../station-shmetro';
/**
* Which direction to display station name. Currently shmetro only.
@@ -18,15 +19,53 @@ interface Props {
export const StationSHMetro = (props: Props) => {
const { stnId, nameDirection, services, color } = props;
- const stnInfo = useRootSelector(store => store.param.stn_list[stnId]);
+ const { stn_list, info_panel_type } = useRootSelector(store => store.param);
+ const stnInfo = stn_list[stnId];
- const transfer = [...(stnInfo.transfer.groups[0]?.lines || []), ...(stnInfo.transfer.groups[1]?.lines || [])];
let stationIconStyle: string;
- if (stnInfo.services.length === 3) stationIconStyle = 'direct_indoor_sh';
- else if (stnInfo.services.length === 2) stationIconStyle = 'express_indoor_sh';
- else if (stnInfo.transfer.groups[1]?.lines?.length ?? 0 > 0) stationIconStyle = 'osi_indoor_sh';
- else if (transfer.length > 0) stationIconStyle = 'int2_indoor_sh';
- else stationIconStyle = 'stn_indoor_sh';
+ const stationIconColor: { [pos: string]: string } = {};
+ if (info_panel_type === PanelTypeShmetro.sh2024) {
+ const int_length = stnInfo.transfer.groups.at(0)?.lines?.length ?? 0;
+ const osi_osysi_length = [
+ ...(stnInfo.transfer.groups.at(1)?.lines || []),
+ ...(stnInfo.transfer.groups.at(2)?.lines || []),
+ ].length;
+
+ stationIconColor.stroke = 'var(--rmg-theme-colour)';
+ if (stnInfo.services.length === 3) {
+ stationIconStyle = 'stn_sh_2020_direct';
+ } else if (stnInfo.services.length === 2) {
+ stationIconStyle = 'stn_sh_2020_express';
+ } else if (int_length > 0 && osi_osysi_length === 0) {
+ // 仅换乘车站
+ stationIconStyle = 'stn_sh_2024_int';
+ } else if (int_length > 0 && osi_osysi_length > 0) {
+ // 站内换乘+出站换乘
+ stationIconStyle = 'stn_sh_2024_int_osysi';
+ } else if (int_length === 0 && osi_osysi_length === 1) {
+ // 仅2线出站换乘
+ stationIconStyle = 'stn_sh_2024_osysi2';
+ } else if (int_length === 0 && osi_osysi_length === 2) {
+ // 仅3线出站换乘
+ stationIconStyle = 'stn_sh_2024_osysi3';
+ } else {
+ stationIconStyle = 'stn_sh_2024';
+ delete stationIconColor.stroke;
+ stationIconColor.fill = 'var(--rmg-theme-colour)';
+ }
+ } else {
+ const non_osysi_transfer = [
+ ...(stnInfo.transfer.groups[0]?.lines || []),
+ ...(stnInfo.transfer.groups[1]?.lines || []),
+ ];
+ if (stnInfo.services.length === 3) stationIconStyle = 'direct_indoor_sh';
+ else if (stnInfo.services.length === 2) stationIconStyle = 'express_indoor_sh';
+ else if (stnInfo.transfer.groups[1]?.lines?.length ?? 0 > 0) stationIconStyle = 'osi_indoor_sh';
+ else if (non_osysi_transfer.length > 0) stationIconStyle = 'int2_indoor_sh';
+ else stationIconStyle = 'stn_indoor_sh';
+ stationIconColor.stroke =
+ non_osysi_transfer.length > 0 ? 'var(--rmg-black)' : (color ?? 'var(--rmg-theme-colour)');
+ }
const dr = nameDirection === 'left' || nameDirection === 'right' ? 90 : 0;
return (
@@ -39,8 +78,8 @@ export const StationSHMetro = (props: Props) => {
/>