preface

When I was looking for inspiration on Echarts two days ago, I saw many similar examples of maps, map positioning and so on, but there seemed to be no subway line map, so I spent some time to make this interactive subway line map Demo. The points on the subway line were randomly downloaded from the Internet. This article records some of my gains (after all, I’m still a rookie) and the implementation of the code, hoping to help some friends. Of course, if you have any opinions, you can tell me directly, so that we can communicate together and make progress.

rendering

http://www.hightopo.com/demo/subway/index.html

Map content is a little bit too much, to display all, the words seem a little small, but it doesn’t matter, you can zoom in and out as required, font and drawn content will not be distorted, after all, are drawn with vector ~

Interface to generate

The underlying div is generated by the HT.graph. GraphView component. Then you can use the method provided by HT for Web to draw whatever you want.

var dm = new ht.DataModel(); Var gv = new ht.graph.graphView (dm); // Topology component gv.addtodom (); // Add the topology component to the bodyCopy the code

The addToDOM function is declared as follows:

addToDOM = function(){ var self = this, view = self.getView(), style = view.style; document.body.appendChild(view); // Add the underlying component div to the body style.left ='0'; // The default component is absolute positioning, so the position is style.right ='0';
    style.top = '0';
    style.bottom = '0';      
    window.addEventListener('resize'.function () { self.iv(); }, false); // Window change event}Copy the code

First I get the points from the subway map I downloaded, and I put them in subway.js. This js file is all the points I downloaded.

mark_Point13 = []; T_Point13 = []; t_Point13 = []; N_Point13 = []; n_Point13 = []; Mark_point13. push({name:'Line 13'Value: [113.4973, 23.1095]}); mark_Point13.push({ name:'Line 13'Value: [113.4155, 23.1080]}); t_Point13.push({ name:'fish bead'Value: [113.41548, 23.10547]}); n_Point13.push({ name:Yue Fung WaiValue: [113.41548, 23.10004]});Copy the code

I declare a lineNum array to hold the numbers of all subway lines in JS, and a color array to hold the colors of all subway lines. The indexes of these colors correspond to the indexes of subway lines in lineNum:

var lineNum = ['1'.'2'.'3'.'30'.'4'.'5'.'6'.'7'.'8'.'9'.'13'.'14'.'32'.'18'.'21'.'22'.'60'.'68'];
var color = ['#f1cd44'.'#0060a1'.'#ed9b4f'.'#ed9b4f'.'#007e3a'.'#cb0447'.'#7a1a57'.'#18472c'.'# 008193'.'#83c39e'.'#8a8c29'.'#82352b'.'#82352b'.'#09a1e0'.'#8a8c29'.'#82352b'.'#b6d300'.'#09a1e0'];
Copy the code

LineNum is then iterated, and the elements and colors in lineNum are transmitted to the createLine function. Metro lines and color matching are drawn according to these two parameters. After all, there is a regular naming method in JS files, which line must be named with corresponding numbers. So we just need to combine the string with this number to get the corresponding array in js:

let lineName = 'Line' + num;
let line = window[lineName];
Copy the code

The createLine definition is also very simple, and my code has quite a few styles, so it seems a bit too much. To create a hT.polyline pipeline, use the polyline.addpoint () function to add points to the variable. Use setSegments to set how points are connected.

functionCreateLine (num, color) {var polyline = new ht.polyline (); // polyline.settag (num); // Set a node tag as a unique identifierif(num === '68') polyline.setToolTip('A P M'); // Set the promptelse if(num === '60') polyline.setToolTip('G F'); 
    else polyline.setToolTip('Line' + num);

    if(color) ({{polylines. S / / s forsetShort for Style, set Style'shape.border.width': 0.4,// Sets the border width of the polygon'shape.border.color': color,// Sets the border color of the polygon'select.width': 0.2,// Sets the border width of the selected node'select.color': color// Set the border color of the selected node}); }let lineName = 'Line' + num;
    let line = window[lineName];
    for(let i = 0; i < line.length; i++) {
        for(let j = 0; j < line[i].coords.length; j++) {
            polyline.addPoint({x: line[i].coords[j][0]*300, y: -line[i].coords[j][1]*300});
            if(num === '68'){//APM line (there are two, but the points are in the same array)if(i === 0 && j === 0) {
                    polyline.setSegments([1]);
                }
                else if(i === 1 && j === 0) {
                    polyline.getSegments().push(1);
                }
                else {
                    polyline.getSegments().push(2);
                }
            }    
        }
    }

    polyline.setLayer('0'); Dm.add (polyline); // Set line to lower layer and point to upper layer. // Add the pipeline to the data container for storage, otherwise the pipeline is "free" and will not be displayed on the topologyreturn polyline;
}
Copy the code

There are several ways to add subway line points in the code above, because there is a “jump” point in Line68 when the line is set in js, so we must “jump” to the past, space is limited, Line68 array specific declaration to see subway.js.

Segments are defined as follows: (addPoint) Segments are defined as follows: (addPoint) segments are defined as follows:

1: moveTo, which occupies 1 point of information representing the starting point of a new path. 2: lineTo, which occupies 1 point of information representing the starting point of a new path from the last point to the point 3: QuadraticCurveTo, occupy 2 points of information, the first point as the control point of the curve, the second point as the end point of the curve 4 ClosePath, which does not occupy the point information, means that the path is drawn and closed to the starting point of the path, so we have to do “jump” and set segments to 1.

Finally, draw the points on the subway line. This section is also separated from subway.js and starts with “mark_Point”, “t_Point” and “n_Point”. I explained these arrays in the previous section of JS.

We add the HT. Node Node to these points, and it will be shown in the topology as soon as the Node is added to the DM data container, provided, of course, that the DM data container is set by the topology component GV. Space is limited, so in the code section for adding points on the subway line, I will only show the points where “transfer stations” are added:

var tName = 't_Point'+ num; var tP = window[tName]; / / big siteif(tP) {// Some lines don't have "transfer stations"for(let i = 0; i < tP.length; i++) {
        letnode = createNode(tP[i].name, tP[i].value, color[index]); // Add node node.s({// set the node's style style'label.scale': 0.05,// Text zooming to avoid browser - limited minimum size problem'label.font': 'bold 12px arial, sans-serif'// Set text font}); Node. SetSize (0.6, 0.6); // Set the node size. Since the offsets between each point in JS are too small, I have to make the nodes smaller.'images/ rotating arrowheads. Json '); // Set the image of the node node.a('alarmColor1'.'rgb(150, 150, 150)'); // The attr property, which can be set to anything. AlarmColor1 is the property bound to the JSON of the image set above, see HT for detailsforWeb vector manual (http://www.hightopo.com/guide/guide/core/vector/ht-vector-guide.html#ref_binding)
        node.a('alarmColor2'.'rgb(150, 150, 150)'); / / same as above node. (a.'tpNode'.true); // This property is only used to distinguish between "transfer sites" and "small sites".Copy the code

All subway lines and stations have been added. But! You may not be able to see your graphs because they are too small, so you can set the fitContent function on the graphView topology component. Let’s also set everything on the topology to be immovable:

gv.fitContent(false, 0.00001); Gv.setmovablefunc (); // Select * from gv; // Select * from gV;function() {return false; // Set the node on gv to be immovable});Copy the code

Now your subway map is displayed. Let’s take a look at the interaction.

interaction

First is the mouse movement event, the mouse over the specific line, the line will become thick, hover for a while can see the number of the line; When the mouse pointer moves to “transfer site” or “small site”, the icon corresponding to the site will become larger and change color, and the font will also become larger. When the mouse pointer moves away, the icon will change back to its original color and the font will become smaller. The difference is that when the mouse moves over the transfer site, the transfer site rotates.

I directly implemented mousemove event based on the underlying DIV of GV, passed event parameter through getDataAt function encapsulated by HT to obtain the corresponding node under the event, and then can operate the node at will:

gv.getView().addEventListener('mousemove'.function(e) { var data = gv.getDataAt(e); // Pass in the logical coordinate point or interactive event parameter to return the primients under the current pointif(name) { originNode(name); // Always keep the node the same size}if(data instanceof ht.polyline) {// Determine the event node type dm.sm().ss(data); // Select pipe name =' ';
        clearInterval(interval);
    }
    else if (data instanceof ht.Node) {
        if(data.getTag() ! == name && data.a('tpNode')) {// If it is not the same Node and the mousemove event object is hT. Node, set the Node rotation interval =setInterval(function() { data.setRotation(data.getRotation() - Math.PI/16); }, 100); }if(data.a('npNode')) {// Stop animation if mouse over "small site" clearInterval(interval); } expandNode(data, name); //// custom zoom node function, relatively easy, I do not stick to the code, you can go to http://hightopo.com/ to check dm.sm().ss(data); // Set the selected node name = data.gettag (); // As a storage variable for "last node", this value can be used to get the node}else{// Otherwise uncheck anything and clear the animation dm.sm().ss(null) on the "transfer site"; name =' '; clearInterval(interval); }});Copy the code

Hover over a subway line to display “specific route information”, which I did by setting tooltip (note: to turn on gV tooltip) :

gv.enableToolTip(); // Turn on the tooltip switchif(num === '68') polyline.setToolTip('A P M'); // Set the promptelse if(num === '60') polyline.setToolTip('G F'); 
else polyline.setToolTip('Line' + num);
Copy the code

Then I use the form form in the lower right corner, click a specific line on the form, or double-click any “site” or line on the topology diagram, then the topology diagram will adapt to the corresponding part, and the double-clicked part will be displayed in the center of the topology diagram.

I don’t think I’ve explained the declaration part of the form… Create a form component from the new hT.Widget. FomePane class. Use form.getView() to get the underlying div of the form component and place it in the bottom right corner of the body. We then use the addRow function to add a row of form items to the form. We can add as many items to this row as we want. We use the addRow function’s second argument (an array) to set the width of the added form items and the third argument to set the height of the row:

function createFormVar form = new ht.widget.formpane (); form.setWidth(200); // Set the width of the form.setheight (416); // Set the form heightletview = form.getView(); document.body.appendChild(view); // Add the form to the body view.style.zindex = 1000; view.style.bottom ='10px'; // Ht components almost always set the absolute path view.style.right ='10px';
    view.style.background = 'rgba (211, 211, 211, 0.8)';

    names.forEach(function(nameString) {form.addrow ([// add a line to the form {// Add the first form item in this line button: {// Add a button icon to the form:'images/Line'+nameString.value+'.json'// Set the button icon background:' ',// Set the button's background to borderColor:' '// Set the button border color clickable:false// Set the button to be unclickable}}, {// the second form item button: {label: namestring. name, labelFont:'bold 14px arial, sans-serif',
                    labelColor: '#fff',
                    background: ' ',
                    borderColor: ' ',
                    onClicked: function() {// Button click callback event gv.sm().ss(dm.getDatabyTag (nameString.value)); // Set the line corresponding to the selected button gv.fitData(gV.sm ().ld()true, 5); // Display the selected subway line in the center of the topology}}}], [0.1, 0.2], 23); The second argument sets the width of the array in the first argument. Less than 1 is the scale, and greater than 1 is the actual width. The third argument is the height of the line}); }Copy the code

Click “site” to display the red label, double-click the node adaptively placed in the center of the topology, double-click the blank to hide the red label content are controlled by monitoring the event of the topology component GV, very clear and easy to understand, the code is as follows:

var node = createRedLight(); // Create a new node with the "red light" style gv.mi(function(e) {// Event listener in topology component in HTif(e.kind === 'clickData' && (e.data.a('tpNode') || e.data.a('npNode'))) {// e.type gets the current event type, and e.ata gets the node under the current event.'2d.visible'.true); // Set node.setPosition(e.data.getPosition().x, e.data.getPosition().y); // Set node's coordinates to the node's position under the current event}else if(e.kind === 'doubleClickData') {// Double-click the node gv.fitData(e.data,false, 10); // Adjust the node under the event to the center of the topology. Parameter 1 is the adaptive node, parameter 2 is whether to animate, parameter 3 is gV and border padding}else if(e.kind === 'doubleClickBackground') {// Double-click node.s('2d.visible'.false); // Make the node invisible to view the HTforThe Web style manual (http://www.hightopo.com/guide/guide/core/theme/ht-theme-guide.html# ref_style)}});Copy the code

Note that s (style) and a (attr) are defined in this way. S is a predefined style attribute of HT, while A is a user-defined attribute of us. The result is usually called by calling a string, which can be a constant or a function.

Finally, a small section is made. If “site” is selected, a red “breathing” icon will be displayed above the “site” to indicate the currently selected “site”.

The “breathing” part is done by using ht setAnimation function. Before using this function, first turn on the animation switch of the data container, and then set the animation:

dm.enableAnimation(); // Turn on the animation switch of the data containerfunction createRedLight() {
    var node = new ht.Node();
    node.setImage('images/red light. Json'); // Set the image of the node node.setsize (1, 1); // Set the size of the node node.setLayer('firstTop'); // Set the node to be displayed on the top layer of the gV node.s('2d.visible'.false); // Node is not visible node.s('select.width', 0); // Node.s ('2d.selectable'.false); SetAnimation ({// Set the animation for HTforHandbook (http://www.hightopo.com/guide/guide/plugin/animation/ht-animation-guide.html) Web animation expandWidth: {property:"width"// If accessType is not set, it passes by defaultsetWidth/getWidth to set and get properties. From: 0.5, // animation start property value to: 1,// animation end property value next:"collapseWidth"// A string that specifies the next animation to execute after the current animation finishes, collapseWidth: {property:"width"From: 1, to: 0.5, next:"expandWidth"
        },
        expandHeight: {
            property: "height"From: 0.5, to: 1, next:"collapseHeight"
        },
        collapseHeight: {
            property: "height"From: 1, to: 0.5, next:"expandHeight"
        },
        start: ["expandWidth"."expandHeight"]// Array to specify one or more animations to start}); dm.add(node);return node;
}
Copy the code

End of code!

conclusion

It took me two days to finish the Demo, which I always felt a little reluctant to do, but sometimes MY thinking could not turn the corner, which took a lot of time, but in general, I gained a lot. I always thought that just adding points to polygons through getPoints().push was enough. After asking for help, For example, before getPoints, you must have points in the polygon. However, in many cases, initialization points are not easy to set, and will cause tedious code. The addPoint method adds points directly to polygon variables. By default, points are connected in straight lines. Segments are not required.

Also, because the default zoom size of HT is 20, and the spacing of my Demo is very small, the metro line map is also very small when scaled to the maximum, so I changed the default zoomMax property of HT in htconfig. Remember that this value must be changed before all HT calls are made. Because the values set in htconfig are immutable by definition.

All in all, these two days my brain cells have died a lot, but also a lot of new growth, people are in continuous progress ~