When I was looking for inspiration on echarts two days ago, I saw many similar examples of maps, map positioning, etc., but there seemed to be no subway map, so I spent some time tinkering with this interactive subway map demo. , the points on the subway line were randomly downloaded from the Internet. This article records some of my gains (after all, I am still a rookie) and the implementation of the code. I hope it can help some friends. Of course, if you have any opinions, you can tell me directly. Only by communicating together can we make progress.
renderings
http://www.hightopo.com/demo/subway/index.html
The map has a little too much content. If you want to display it all, the fonts will appear a bit small, but it doesn't matter. You can zoom in or out as needed. The fonts and drawn content will not be distorted. After all, they are all drawn with vectors~
Interface generationThe underlying div is generated through the ht.graph.GraphView component. Then you can use the good methods provided by HT for Web and just call the canvas brush to draw casually. Let's first see how to generate the underlying div:
var dm = new ht.DataModel();//Data container var gv = new ht.graph.GraphView(dm);//Topology component gv.addToDOM();//Add the topology graph component into the body
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 div of the component to the body style.left = '0 ';//The default component is absolutely positioned, so set the position style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); //Window change event}
Now I can doodle on this div~ First I get the points on the downloaded subway map, and I put them in subway.js. This js file is all the downloaded content, I didn’t do anything Other changes are mainly to add these points to the array according to the line, such as:
mark_Point13 = [];//The line array contains the starting and ending coordinates of the line and the name of the line t_Point13 = [];//The station array contains the transfer station coordinates in the line and the station name n_Point13 = [];//The small station array contains the coordinates of the small stations on the line and the names of the small stations 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: 'Yu Zhu', value: [113.41548,23.10547 ]}); n_Point13.push({ name: 'Yufengwei', value: [113.41548,23.10004]});
Next, to draw the subway lines, I declared an array lineNum to hold the numbers of all the subway lines in js, and a color array to hold the colors of all the subway lines. The index of these colors is the same as the subway line in lineNum. The numbered indexes correspond one to one:
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'];
Then traverse lineNum, pass the elements and colors in lineNum to the createLine function, and draw the subway lines and color matching according to these two parameters. After all, the naming method in the js file is also regular. Which line is named after Add the corresponding number, so we only need to combine the string with this number to get the corresponding array in js:
let lineName = 'Line' + num; let line = window[lineName]; The definition of createLine is also very simple. My code has set a lot of styles, so it looks a bit much. To create an ht.Polyline pipeline, we can add specific points to this variable through the polyline.addPoint() function, and set the connection method of the points through setSegments. function createLine(num, color) {//Draw a map line var polyline = new ht.Polyline();//Polygon pipeline polyline.setTag(num);//Set the node tag label as the only label if(num === '68') polyline.setToolTip('AP M');//Set prompt information else if(num === '60') polyline.setToolTip('G F'); else polyline.setToolTip('Line' + num); if(color) { polyline.s({//s is the abbreviation of setStyle, set the style 'shape.border.width': 0.4,//Set the border width of the polygon 'shape .border.color': color,//Set the border color of the polygon 'select.width': 0.2,//Set 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');//Set the line On the lower layer, the point is set on the upper layer top dm.add(polyline);//Add the pipeline to the data container for storage, otherwise the pipeline is in a free state and will not be displayed on the topology map return polyline;}
There are several situations for adding points on the subway line in the above code. This is because Line68 has a jumping point when setting the line in js, so we must jump to it. The space is limited. For the specific declaration of the Line68 array, see subway.js. .
One thing to note here is that if you use the addPoint function and do not set segments, the added points will be connected with straight lines by default. The definition of segments is as follows:
1: moveTo, occupies 1 point information, representing the starting point of a new path
2: lineTo, occupies 1 point information, representing the connection from the last point to this point
3: quadraticCurveTo, occupies 2 point information, the first point is used as the curve control point, and the second point is used as the curve end point
4: bezierCurveTo, occupies 3 point information, the first and second points are used as curve control points, and the third point is used as the curve end point
5: closePath, does not occupy point information, represents the end of this path drawing and closes to the starting point of the path
So if we want to do jumping behavior, just set segments to 1.
Finally, these points on the subway line are drawn. This part is also separated in subway.js. The names start with mark_Point, t_Point and n_Point. I explained these arrays in the previous js display part. You can move your middle finger to scroll up to see look.
We add ht.Node nodes at these points. When the nodes are added to the dm data container, they will be displayed on the topology map. Of course, the premise is that the data container set by the topology map component gv is this dm. Due to limited space, I will only show the code part for adding points on the subway line:
var tName = 't_Point' + num;var tP = window[tName];//Large station if(tP) {//Some lines do not have transfer stations for(let i = 0; i < tP.length; i++) { let node = createNode(tP[i].name, tP[i].value, color[index]);//Add node node.s({//Set the node style style 'label.scale': 0.05,//Text scaling to avoid browser restrictions The minimum font size problem'label.font': 'bold 12px arial, sans-serif'//Set the font of the text }); node.setSize(0.6, 0.6);//Set the node size. Since the offset between each point in js is too small, I had to set the node smaller node.setImage('images/rotating arrow.json');//Set the image of the node node.a('alarmColor1 ', 'rgb(150, 150, 150)'); //attr attribute, you can set anything here. alarmColor1 is the attribute bound in the json of the image set above. For details, please refer to the HT for Web 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 setting is only used to distinguish transfer sites and small sites, which will be used later}}
All subway lines and stations have been added. but! You may not be able to see the graphs you drew because they are too small. At this time, you can set the fitContent function on the graphView topology component. By the way, we also set everything on the topology graph to be immovable:
gv.fitContent(false, 0.00001);//Adaptive size, parameter 1 is whether to animate, parameter 2 is the padding value of gv and border gv.setMovableFunc(function(){ return false;//Set the node on gv to be immovable });
Now your subway route map can be displayed~ Let’s take a look at the interaction.
interactionThe first is the mouse movement event. When the mouse slides over a specific line, the line will become thicker. If you hover for a while, you can also see the number of this line. When the mouse moves to a transfer site or a small site, the icon corresponding to the site will become larger and When the color changes, the font will also become larger. When you move the mouse, the icon will return to its original color and the font will become smaller. The difference is that when the mouse moves to the transfer station, the transfer station will rotate.
For the mouse sliding event, I directly perform the mousemove event based on the underlying div of gv, pass in the event parameters through the getDataAt function encapsulated by ht, obtain the corresponding node under the event, and then you 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 event parameter and return the primitive under the current point if(name ) { originNode(name);//Keep the node at its original size at all times} if (data instanceof ht.Polyline) {//Determine the type of event node dm.sm().ss(data);//Select the 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 type, then set the node's rotation interval = setInterval(function() { data.setRotation(data.getRotation() - Math.PI/16); //Rotate based on its own rotation}, 100); } if(data.a('npNode')) {//If the mouse moves to a small site, stop the animation clearInterval(interval); } expandNode(data, name);////Customized zoom node function, relatively easy, I don’t stick to the code anymore, you can go to http://hightopo.com/ to view dm.sm().ss(data);//Set the selected node name = data.getTag();//As a storage variable of the previous node, you can get the node through this value} else {//In any other case, nothing is selected and the animation on the transfer site is cleared dm.sm( ).ss(null); name = ''; clearInterval(interval); }});
When the mouse is hovering over a subway line, the specific line information is displayed. I do this by setting the tooltip (note: the tooltip switch of gv must be turned on):
gv.enableToolTip();//Turn on the tooltip switch if(num === '68') polyline.setToolTip('AP M');//Set the prompt information else if(num === '60') polyline. setToolTip('G F'); else polyline.setToolTip('Line' + num);
Then I use the form form in the lower right corner to click on a specific line on the form, or double-click any site or line on the topology map. The topology map will adapt to the corresponding part, and the double-clicked part will be displayed in the center of the topology map. .
I don't seem to have explained the declaration part of the form yet. . . That is to create a form form component through new ht.widget.FomePane class, obtain the underlying div of the form component through form.getView(), place this div in the lower right corner of the body, and then add a row to the form through the addRow function Form items, you can add any number of items in this row, through addRow The second parameter of the function (an array) sets the width of the added form item, and the third parameter sets the height of the row:
function createForm() {//Create the form form in the lower right corner var form = new ht.widget.FormPane(); form.setWidth(200);//Set the form width form.setHeight(416);//Set the form height let view = form.getView(); document.body.appendChild(view);//Add the form into the body view.style.zIndex = 1000; view.style.bottom = '10px'; // Almost all ht components set absolute paths view.style.right = '10px'; view.style.background = 'rgba(211, 211, 211, 0.8)'; names. forEach(function(nameString) { form.addRow([//Add a row to the form{//The first form item button in this row: {//Add button icon to the form: 'images/Line'+nameString.value+'.json',//Set the button icon background: '',//Set the button background borderColor: '',//Set The border color of the button clickable: false//Set the button to be non-clickable} }, {//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 pressed button gv.fitData(gv.sm().ld(), true, 5);//Display the selected subway line in the center of the topology map} } } ], [0.1, 0.2], 23);//The second parameter is to set the width of the array in the first parameter, less than 1 is Ratio, greater than 1 is the actual width. The third parameter is the height of the row});}.
Click the site to display the red mark, double-click the node to adaptively place it in the center of the topology map, and double-click the blank space to hide the red mark. The content is controlled through event monitoring of the topology component gv. It is very clear and easy to understand. The code is as follows:
var node = createRedLight();//Create a new node, displayed as a red light style gv.mi(function(e) {//Event monitoring in the topology component in ht if(e.kind === 'clickData ' && (e.data.a('tpNode') || e.data.a('npNode'))) {//e.kind gets the current event type, e.data gets the node under the current event node.s('2d.visible', true);//Set the node node to be visible node.setPosition(e.data.getPosition() .x, e.data.getPosition().y);//Set the coordinates of node to the position of the node under the current event} else if(e.kind === 'doubleClickData') {//Double-click the node gv.fitData(e.data, false, 10);//Adapt the node under the event to the center of the topology map. Parameter 1 is the adaptive node, parameter 2 is whether to animate, and parameter 3 is Padding of gv and border } else if(e.kind === 'doubleClickBackground') {//Double-click the blank space node.s('2d.visible', false);//Set the node node to be invisible View HT for Web Style Manual (http://www.hightopo.com/guide/guide/core/theme/ht-theme-guide.html#ref_style) }});
Note that s (style) and a (attr) are defined like this. s is some style attributes predefined by ht, and a is an attribute customized by our users. The result is usually called by calling a string. This string corresponds to can be a constant or a function, which is very flexible.
Finally, a small part is made. When a site is selected, a red breathing icon will be displayed above the site to indicate the currently selected site.
The breathing part is completed using the setAnimation function of ht. Before using this function, you must first turn on the animation switch of the data container, and then set the animation:
dm.enableAnimation();//Turn on the animation switch function of the data container createRedLight() { var node = new ht.Node(); node.setImage('images/RedLight.json');//Set the image node of the node .setSize(1, 1);//Set the size of the node node.setLayer('firstTop');//Set the node to be displayed on the top layer of gv node.s('2d.visible', false);//The node is invisible node.s( 'select.width', 0);//The border when the node is selected is 0 and invisible node.s('2d.selectable', false);//Set this attribute, the node cannot be selected node.setAnimation({//For details on setting animation, please refer to the HT for Web Animation Manual (http://www.hightopo.com/guide/guide/plugin/animation/ht- animation-guide.html) expandWidth: { property: width,//Set this property and the accessType is not set, the default is to set and get the property through setWidth/getWidth. The width here and the height below are used. They are all obtained by the size set previously from: 0.5, //Attribute value at the beginning of the animation to: 1,//Attribute value at the end of the animation next: collapseWidth//String type, specifies what to execute after the current animation is completed The next animation can merge multiple animations}, 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, used to specify one or more animations to be started} ); dm.add(node); return node;}
All code is over!
SummarizeThis demo took me two days to complete, and I always felt a bit unwilling to do so. However, sometimes my thinking couldn’t be turned around, and it took a lot of time, but overall I gained a lot. I used to think that as long as I passed Just use getPoints().push to add points to the polygon. After asking for help from a master, I found that this method is not only detours but also causes various problems. For example, before gettingPoints, you must already have points in the polygon. It is possible, but in many cases, the initialized points are not easy to set, and the code will be very cumbersome. Points are directly added to the polygon variable through the addPoint method, and the points are connected by straight lines by default. There is no need to set segments, what a lovely function.
Also, because the default zoom size of ht is 20, and the spacing of my Demo is very small, the subway line map display is also very small when zoomed to the maximum, so I changed the default zoomMax attribute of ht in htconfig. Remember, change This value must be before all ht calls, because the values set in htconfig cannot be changed later.
The above is the interactive subway line map based on HTML5 Canvas that the editor introduces to you. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. I would also like to thank everyone for your support of the VeVb martial arts website!