User:Sameboat/Draw topological map in XML editor

From Wikipedia, the free encyclopedia
Metro map done entirely with text editor.

While it is entirely OK to draw an SVG topological map in Inkscape or Adobe Illustrator, the SVG created in these applications has wide array of issues, lack of code neatness, removal of text alignment properties, overdose of metadata which causes validation error by W3, etc. For a topological map which requires higher uniformity of map objects in terms of distance and alignment than geologically accurate map does, Inkscape usually gives you the opposite result. Saving your file in "plain SVG" format does not automatically solve all the problems it produces, so it is still recommended to actually write the map code manually in XML editor. It is not difficult at all after learning the crucial SVG elements and attributes, and it actually gives you more control over the map design.

Things you need:

  • Any XML editor which supports Unicode to handle text of non-Latin characters. Notepad++ and Sublime Text are rather famous among those.
  • Full set of the free Liberation fonts already installed in your OS. SVG on Wikimedia can use neither fonts not installed on Wikimedia server nor webfont.
  • While many up-to-date browsers are capable of rendering SVG file natively for preview before upload, you should also preview in SVG Check on WMF Labs because it provides a temporary preview by librsvg, the same rendering library used by Wikimedia to create PNG version of the SVG source. librsvg is sadly notorious for behaving differently to mainstream browsers and having lot of unfixed bugs.
  • W3C specification of SVG.[1] The very resource of "how to use SVG".
  • A hand drawn draft of your map
  • An SVG diagram template

Text editor[edit]

Disclaimer: I use Notepad++ and Firefox so this tutorial unavoidably focuses around them.

After installing Notepad++, the first thing you want to do is to allow it to recognize SVG as XML so the syntax is highlighted and it properly reports syntax error, otherwise you will have to set the language manually after opening an SVG file every single time. Go to "Settings" - "Style Configurator...", scroll down the language list to "XML" and add "svg" to "User ext. :" and "save and & close". If you are starting from scratch, you can save the new document as .svg file so it will automatically switch to XML language or again set manually in the "Language" menu. There is really no reason to type everything from scratch, so I have prepared the SVG code of a very basic diagram for you to start off. You can of course use another SVG diagram and modify from it, just to be sure that that SVG file was created in text editor all along, otherwise you would have a funny time to clean up the mess of useless metadata.

SVG diagram template[edit]

<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="300" height="200"
viewBox="0 0 300 200"
>
	<title>Basic SVG template for railway diagram</title>
	<style type="text/css">
<!--type="text/css" is required otherwise librsvg won't load anything from style element.-->
<!--Class name begins with a dot in the style element. It is recalled in other element with the "class" attribute.-->
text {font-family:Arimo,Liberation Sans,Arial,sans-serif}
.me {fill:none;stroke-width:5}
.spiccadilly {stroke:#0019A8}
	</style>
	<defs><!--stores objects to be used for more than once with the "use" element.-->
		<path id="term" class="me" d="M -7.5,0 H 7.5"/>
		<path id="st" class="me" d="M -2,0 H 7.5"/>
		<use xlink:href="#term" id="termpiccadilly" class="spiccadilly"/>	
		<use xlink:href="#st" id="stpiccadilly" class="spiccadilly"/>
		<g id="int">
			<circle cx="0" cy="0" r="8.25" fill="#fff"/>
			<circle cx="0" cy="0" r="6" style="fill:#fff;stroke:#000;stroke-width:3"/>
		</g>
	</defs>
	<rect id="background" x="-10" y="-10" width="500" height="500" fill="#eee"/>
	<g id="Line_route" class="me">
<!--Never leave any space in id name and do not begin the id name with number; avoid punctuation and non-English characters too.-->
<!--You can add more class in the same class attribute. Separate the classes with a space.-->
		<path id="Piccadilly_line_route" class="spiccadilly" d="
		M 40,40
		H 100 q 14,0 24,10
		L 164,90 q 10,10 10,24
		V 170
		"/>
	</g>
	<g id="Piccadilly_line_station_icons">
		<use xlink:href="#termpiccadilly" transform="translate(40,40)rotate(90)"><title>Uxbridge</title>
		<use xlink:href="#stpiccadilly" transform="translate(95,40)rotate(-90)"><title>Sudbury_Town</title>
		<use xlink:href="#int" x="134" y="60"><title>Hammersmith</title>
<!--int icon is omnidirectional so no need for transform/rotate on diagonal line.-->
		<use xlink:href="#stpiccadilly" transform="translate(164,90)rotate(135)"><title>Knightsbridge</title>
		<use xlink:href="#stpiccadilly" x="174" y="130"><title>Russell_Square</title>
		<use xlink:href="#termpiccadilly" x="174" y="170"><title>Cockfosters</title>
	</g>
	<g id="text_labels" font-size="15px" transform="translate(0,5)">
<!--Unlike other attributes, value of font-size attribute should always include the unit (px) otherwise it may be interpreted differently.-->
<!--Translate value equalizes the height difference of text labels above and below the horizontal line.-->
		<g text-anchor="middle">
			<text x="40" y="58">Uxbridge</text>
<!--y of text below horizontal line = y of icon +18.-->
			<text x="95" y="22">Sudbury Town</text>
<!--y of text above horizontal line = y of icon -18.-->
		</g>
		<text x="144" y="50">Hammersmith &amp; City</text>
<!--x,y of text alongside diagonal line = x,y of icon ±10. Use &amp; for &.-->
		<text x="154" y="100" text-anchor="end">Knightsbridge</text>
		<text x="186" y="124">Russell <tspan x="186" dy="13">Square</tspan></text>
<!--x of text on the right-hand side of vertical line = x of icon +12.-->
<!--Use tspan for broken label. Increase dy value for greater line height of text.-->
<!--Subtract y value of the text element by half of the dy value for vertically centred 2-line text.-->
		<text x="162" y="170" text-anchor="end">Cockfosters</text>
<!--x of text on the left-hand side of vertical line = x of icon -12.-->
	</g>
</svg>

Explanation and customization[edit]

Element: svg[edit]

<SVG> element serves primarily to request the opening application to treat the document by SVG library and also defines the canvas dimension of the image itself. The only things you need to change here are values of width, height and viewBox properties/attributes. The 4 values inside viewBox (letter "B" must be uppercase) are in the order of "starting point of x coordinate", "starting point of y coordinate", "native width" and "native height" and MUST be separated only by space, comma would cause compatibility issue. (0,0) is generally at the upper-left corner if the starting point values are untouched. For example, you can write -250 -250 500 500 so (0,0) will sit right in the center of the image.

Properties width and height must be present. If viewBox is absent, its value will be assumed as 0 0 (100% as width value) (100% as height value). If the values of properties width and height differ than the native width and height figures in viewBox, the image canvas is scaled accordingly. But I do not recommend doing this because librsvg is terrible at scaling text: Glphys will appear misaligned/offset irregularly no matter how much you scale by, e.g. 0.5, 2, 4, 8. You should choose the appropriate smallest and regular font-sizes which do not require scaling up either the text alone or the whole image, both scaling practices will give you the aforementioned text glitch anyway.

Element: style[edit]

Element: defs[edit]

When making a topological map, obviously many items will be repeatedly used in different positions of the image such as the station icon. You can create the source objects which act as the "templates" and use these templates with the "use" element in other situation. The change of the source object will instantly affect all use element which link to the source. Also it efficiently reduces the size of your SVG file. These source object can be and should be nested within the "defs" tags. Items in the defs will never show until they are "used" on the "outside". The reason for that is that you want the default position of the source object to stay at x0,y0, so when you use the source object you do not need to calculate the difference of positions of the source object and recycling.

In order to "use" the existing or predefined elements elsewhere within the SVG file, you need to give the source object or the source object group an "id" name. Although nearly all SVG software will assign a random/arbitrary id for every single element in the SVG file, this is not necessary and will lead to a potential mayhem that when you copy your objects to another SVG file which already contains element with the same id, some SVG renderer may refuse to render the image until you have changed or removed the repeated id name. So only assign the id name where it is needed, or for easier identification, write a descriptive id name that will never overlap with the other. If the id name contains white space, remove it or replace it with underscore. Never begin the id name with a numeral, precede it with at least one alphabet. It should not contain any other punctuation or non-English alphabetic character.

To use the object, apply <use xlink:href="#(source object id)"/>. You can change the style and position of the source object if these are not yet defined by using the "style" (CSS) and "transform"[2] attributes. For example, to place a "stations icon of line 1", which has already been defined, at x100,y100, rotate it 90 degree anti-clockwise and make it translucent (for station under construction):

Element: g[edit]

(WIP)