代码之家  ›  专栏  ›  技术社区  ›  Aenaon

spool.js:自定义svg标记太慢(还有很多点)

  •  2
  • Aenaon  · 技术社区  · 6 年前

    我试图使用一些用户定义的svg图标作为传单上的标记,但我认为整个任务对于我的浏览器来说太重了。

    直到现在我还在用 L.circleMarker 但是我现在必须使用星号、箭头、星星等标记,所以我决定将它们作为svg路径来做,然后将它们插入,而不是使用circlemarker。为了使事情更复杂,我有超过30万分。有了circleMarkers,我能够制作出一个可行的图表,虽然不是闪电般的快,但还是可以接受的,特别是当使用相当深的变焦来区分各个点时(否则一切都像一个大团块,没法研究)。

    circleMarkers 但我可能错了。非常感谢您的帮助。代码片段中,注释/取消注释底部的几行:

    return L.circleMarker(p, style(feature));
    

    console.log("Starting markers.")
    return L.marker(p, {
        renderer: myRenderer,
        icon: makeIcon('6-pointed-star', style(feature).color),
        });
    

    圆圈标记 svg 非常感谢!

    注意:使用svg标记,代码会中断highlight事件,但我完全理解错误的地方 圆圈标记

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="utf-8">
    
    <title>Chart</title>
        <style>
            #tooltip {
              position:absolute;
              background-color: #2B292E;
              color: white;
              font-family: sans-serif;
              font-size: 15px;
              pointer-events: none; /*dont trigger events on the tooltip*/
              padding: 15px 20px 10px 20px;
              text-align: center;
              opacity: 0;
              border-radius: 4px;
            }
    
    		html, body {
    			height: 100%;
    			margin: 0;
    		}
    		#map {
    			width: 600px;
    			height: 600px;
    		}
        </style>
    
    <!-- Reference style.css -->
    <!--    <link rel="stylesheet" type="text/css" href="style.css">-->
    
    <!-- Reference minified version of D3 -->
        <script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
        <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" />
        <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
    </head>
    
    <body>
    
    
    
    <div id="map"></div>
    
    
    <script>
        var data = [];
        var NumOfPoints = 100
        for (let i = 0; i < NumOfPoints; i++) {
            data.push({
                num: i,
                x: Math.random(),
                y: Math.random(),
                year: Math.floor(100*Math.random())
            })
        }
        
        renderChart(data);
    
    
        function make_dots(data) {
            var arr = [];
            var nest = d3.nest()
                .key(function (d) {
                    return Math.floor(d.year / 10);;
                })
                .entries(data);
    
            for (var k = 0; k < nest.length; ++k) {
                arr[k] = helper(nest[k].values);
            }
            return arr;
        }
    
        function helper(data) {
            dots = {
                type: "FeatureCollection",
                features: []
            };
            for (var i = 0; i < data.length; ++i) {
                x = data[i].x;
                y = data[i].y;
                var g = {
                    "type": "Point",
                    "coordinates": [x, y]
                };
    
                //create feature properties
                var p = {
                    "id": i,
                    "popup": "Dot_" + i,
                    "year": parseInt(data[i].year),
                    "size": 30 // Fixed size
                };
    
                //create features with proper geojson structure
                dots.features.push({
                    "geometry": g,
                    "type": "Feature",
                    "properties": p
                });
            }
            return dots;
        }
    
    
        //////////////////////////////////////////////////////////////////////////////////////////////
        //styling and displaying the data as circle markers//
        //////////////////////////////////////////////////////////////////////////////////////////////
    
        //create color ramp
        function getColor(y) {
            return y > 90 ? '#6068F0' :
                y > 80 ? '#6B64DC' :
                y > 70 ? '#7660C9' :
                y > 60 ? '#815CB6' :
                y > 50 ? '#8C58A3' :
                y > 40 ? '#985490' :
                y > 30 ? '#A3507C' :
                y > 20 ? '#AE4C69' :
                y > 10 ? '#B94856' :
                y > 0 ? '#C44443' :
                '#D04030';
        }
    
        //calculate radius so that resulting circles will be proportional by area
        function getRadius(y) {
            r = Math.sqrt(y / Math.PI)
            return r;
        }
    
        // This is very important! Use a canvas otherwise the chart is too heavy for the browser when
        // the number of points is too high, as in this case where we have around 300K points to plot
        var myRenderer = L.canvas({
            padding: 0.5
        });
    
        //create style, with fillColor picked from color ramp
        function style(feature) {
            return {
                radius: getRadius(feature.properties.size),
                fillColor: getColor(feature.properties.year),
                color: "#000",
                weight: 0,
                opacity: 1,
                fillOpacity: 0.9,
                renderer: myRenderer
            };
        }
    
        //create highlight style, with darker color and larger radius
        function highlightStyle(feature) {
            return {
                radius: getRadius(feature.properties.size) + 1.5,
                fillColor: "#FFCE00",
                color: "#FFCE00",
                weight: 1,
                opacity: 1,
                fillOpacity: 0.9
            };
        }
    
        //attach styles and popups to the marker layer
        function highlightDot(e) {
            var layer = e.target;
            dotStyleHighlight = highlightStyle(layer.feature);
            layer.setStyle(dotStyleHighlight);
            if (!L.Browser.ie && !L.Browser.opera) {
                layer.bringToFront();
            }
        }
    
        function resetDotHighlight(e) {
            var layer = e.target;
            dotStyleDefault = style(layer.feature);
            layer.setStyle(dotStyleDefault);
        }
    
        function onEachDot(feature, layer) {
            layer.on({
                mouseover: highlightDot,
                mouseout: resetDotHighlight
            });
            var popup = '<table style="width:110px"><tbody><tr><td><div><b>Marker:</b></div></td><td><div>' + feature.properties.popup +
                '</div></td></tr><tr class><td><div><b>Group:</b></div></td><td><div>' + feature.properties.year +
                '</div></td></tr><tr><td><div><b>X:</b></div></td><td><div>' + feature.geometry.coordinates[0] +
                '</div></td></tr><tr><td><div><b>Y:</b></div></td><td><div>' + feature.geometry.coordinates[1] +
                '</div></td></tr></tbody></table>'
    
            layer.bindPopup(popup);
        }
        
        function makeIcon(name, color) {
    
        if (name == "diamond") {
            // here's the SVG for the marker
            var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='20' height='20'> " +
                "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
                " d='M10,1 5,10 10,19, 15,10Z'/></svg>";
        }
    
    
        // Based on http://www.smiffysplace.com/stars.html
        if (name == "6-pointed-star") {
            // here's the SVG for the marker
            var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='28' height='28'> " +
                "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
                " d='m13 13m0 5l5 3.6599999999999966l-0.6700000000000017 -6.159999999999997l5.670000000000002 -2.5l-5.670000000000002 -2.5l0.6700000000000017 -6.159999999999997l-5 3.6599999999999966l-5 -3.6599999999999966l0.6700000000000017 6.159999999999997l-5.670000000000002 2.5l5.670000000000002 2.5l-0.6700000000000017 6.159999999999997z'/></svg>";
    
        }
    
    
        // here's the trick, base64 encode the URL
        var svgURL = "data:image/svg+xml;base64," + btoa(icon);
    
        // create icon
        var svgIcon = L.icon({
            iconUrl: svgURL,
            iconSize: [20, 20],
            shadowSize: [12, 10],
            iconAnchor: [5, 5],
            popupAnchor: [5, -5]
        });
    
        return svgIcon
    }
        
    
        function renderChart(data) {
            var myDots = make_dots(data);
    
            var minZoom = 0,
                maxZoom = 15;
    
            var map = L.map('map', {
                minZoom: minZoom,
                maxZoom: maxZoom
            }).setView([0.5, 0.5], 10);
    
            L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
                continuousWorld: false,
                minZoom: 0,
                noWrap: true
            }).addTo(map);
    
            var myRenderer = L.canvas({
                padding: 0.5
            });
    
            // Define an array to keep layerGroups
            var dotlayer = [];
    
            //create marker layer and display it on the map
            for (var i = 0; i < myDots.length; i += 1) {
                dotlayer[i] = L.geoJson(myDots[i], {
                    pointToLayer: function (feature, latlng) {
                        var p = latlng;
    //                    return L.circleMarker(p, style(feature));
                        console.log("Starting markers.")
                        return L.marker(p, {
                            renderer: myRenderer,
                            icon: makeIcon('6-pointed-star', style(feature).color),
                        });
                    },
                    onEachFeature: onEachDot
                }).addTo(map);
            }
    
    
            var cl = L.control.layers(null, {}).addTo(map);
            for (j = 0; j < dotlayer.length; j += 1) {
                var name = "Group " + j + "0-" + j + "9";
                cl.addOverlay(dotlayer[j], name);
            }
    
        }
    
    
    </script>
    </body>
    
    </html>
    1 回复  |  直到 6 年前
        1
  •  8
  •   rioV8    6 年前

    您的svg 6点标记不会由画布呈现。看看这些工具,你会发现它们是 img 以base-64编码的svg作为源的标记。如果有大量的标记,这将减慢HTML呈现程序的速度。

    通过创建新的 L.Path 子类你可以在画布上画任何你想要的标记,让传单做它最好的。在任何其他JS代码之前进行这些小册子修改,否则它将抱怨它不是构造函数。

    L.Canvas.include({
        _updateMarker6Point: function (layer) {
            if (!this._drawing || layer._empty()) { return; }
    
            var p = layer._point,
                ctx = this._ctx,
                r = Math.max(Math.round(layer._radius), 1);
    
            this._drawnLayers[layer._leaflet_id] = layer;
    
            ctx.beginPath();
            ctx.moveTo(p.x + r     , p.y );
            ctx.lineTo(p.x + 0.43*r, p.y + 0.25 * r);
            ctx.lineTo(p.x + 0.50*r, p.y + 0.87 * r);
            ctx.lineTo(p.x         , p.y + 0.50 * r);
            ctx.lineTo(p.x - 0.50*r, p.y + 0.87 * r);
            ctx.lineTo(p.x - 0.43*r, p.y + 0.25 * r);
            ctx.lineTo(p.x -      r, p.y );
            ctx.lineTo(p.x - 0.43*r, p.y - 0.25 * r);
            ctx.lineTo(p.x - 0.50*r, p.y - 0.87 * r);
            ctx.lineTo(p.x         , p.y - 0.50 * r);
            ctx.lineTo(p.x + 0.50*r, p.y - 0.87 * r);
            ctx.lineTo(p.x + 0.43*r, p.y - 0.25 * r);
            ctx.closePath();
            this._fillStroke(ctx, layer);
        }
    });
    
    var Marker6Point = L.CircleMarker.extend({
        _updatePath: function () {
            this._renderer._updateMarker6Point(this);
        }
    });
    

    return new Marker6Point(p, style(feature));
    

    代码中有2个 L.canvas 有两个变量 myRenderer . 我保留了全局变量,但仅当在 renderChart()

    在演示中,我使用了一个更大的带有标记的区域,并使用了10000个标记。我的浏览器没有问题。我增加了 size 在属性中,我们得到一个半径为13像素的标记,这样你就能清楚地看到星星。

    我使用了最新的传单版本1.3.3。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="utf-8">
    
    <title>Chart</title>
    <style>
    #tooltip {
        position:absolute;
        background-color: #2B292E;
        color: white;
        font-family: sans-serif;
        font-size: 15px;
        pointer-events: none; /*dont trigger events on the tooltip*/
        padding: 15px 20px 10px 20px;
        text-align: center;
        opacity: 0;
        border-radius: 4px;
    }
    
    html, body {
        height: 100%;
        margin: 0;
    }
    #map {
        width: 600px;
        height: 600px;
    }
    </style>
    
    <!-- Reference style.css -->
    <!--    <link rel="stylesheet" type="text/css" href="style.css">-->
    
    <!-- Reference minified version of D3 -->
    <script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet.js"></script>
    </head>
    
    <body>
    <div id="map"></div>
    <script>
    L.Canvas.include({
        _updateMarker6Point: function (layer) {
            if (!this._drawing || layer._empty()) { return; }
    
            var p = layer._point,
                ctx = this._ctx,
                r = Math.max(Math.round(layer._radius), 1);
    
            this._drawnLayers[layer._leaflet_id] = layer;
    
            ctx.beginPath();
            ctx.moveTo(p.x + r     , p.y );
            ctx.lineTo(p.x + 0.43*r, p.y + 0.25 * r);
            ctx.lineTo(p.x + 0.50*r, p.y + 0.87 * r);
            ctx.lineTo(p.x         , p.y + 0.50 * r);
            ctx.lineTo(p.x - 0.50*r, p.y + 0.87 * r);
            ctx.lineTo(p.x - 0.43*r, p.y + 0.25 * r);
            ctx.lineTo(p.x -      r, p.y );
            ctx.lineTo(p.x - 0.43*r, p.y - 0.25 * r);
            ctx.lineTo(p.x - 0.50*r, p.y - 0.87 * r);
            ctx.lineTo(p.x         , p.y - 0.50 * r);
            ctx.lineTo(p.x + 0.50*r, p.y - 0.87 * r);
            ctx.lineTo(p.x + 0.43*r, p.y - 0.25 * r);
            ctx.closePath();
            this._fillStroke(ctx, layer);
        }
    });
    
    var Marker6Point = L.CircleMarker.extend({
        _updatePath: function () {
            this._renderer._updateMarker6Point(this);
        }
    });
    
    var data = [];
    var NumOfPoints = 10000;
    for (let i = 0; i < NumOfPoints; i++) {
        data.push({
            num: i,
            x: Math.random()*60,
            y: Math.random()*60,
            year: Math.floor(100*Math.random())
        })
    }
    
    renderChart(data);
    
    function make_dots(data) {
        var arr = [];
        var nest = d3.nest()
            .key(function (d) {
                return Math.floor(d.year / 10);
            })
            .entries(data);
    
        for (var k = 0; k < nest.length; ++k) {
            arr[k] = helper(nest[k].values);
        }
        return arr;
    }
    
    function helper(data) {
        dots = {
            type: "FeatureCollection",
            features: []
        };
        for (var i = 0; i < data.length; ++i) {
            x = data[i].x;
            y = data[i].y;
            var g = {
                "type": "Point",
                "coordinates": [x, y]
            };
    
            //create feature properties
            var p = {
                "id": i,
                "popup": "Dot_" + i,
                "year": parseInt(data[i].year),
                "size": 500 // Fixed size circle radius=~13
            };
    
            //create features with proper geojson structure
            dots.features.push({
                "geometry": g,
                "type": "Feature",
                "properties": p
            });
        }
        return dots;
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////
    //styling and displaying the data as circle markers//
    //////////////////////////////////////////////////////////////////////////////////////////////
    
    //create color ramp
    function getColor(y) {
        return y > 90 ? '#6068F0' :
            y > 80 ? '#6B64DC' :
            y > 70 ? '#7660C9' :
            y > 60 ? '#815CB6' :
            y > 50 ? '#8C58A3' :
            y > 40 ? '#985490' :
            y > 30 ? '#A3507C' :
            y > 20 ? '#AE4C69' :
            y > 10 ? '#B94856' :
            y > 0 ? '#C44443' :
            '#D04030';
    }
    
    //calculate radius so that resulting circles will be proportional by area
    function getRadius(y) {
        r = Math.sqrt(y / Math.PI)
        return r;
    }
    
    // This is very important! Use a canvas otherwise the chart is too heavy for the browser when
    // the number of points is too high, as in this case where we have around 300K points to plot
    var myRenderer;
    //  = L.canvas({
    //     padding: 0.5
    // });
    
    //create style, with fillColor picked from color ramp
    function style(feature) {
        return {
            radius: getRadius(feature.properties.size),
            fillColor: getColor(feature.properties.year),
            color: "#000",
            weight: 0,
            opacity: 1,
            fillOpacity: 0.9,
            renderer: myRenderer
        };
    }
    
    //create highlight style, with darker color and larger radius
    function highlightStyle(feature) {
        return {
            radius: getRadius(feature.properties.size) + 1.5,
            fillColor: "#FFCE00",
            color: "#FFCE00",
            weight: 1,
            opacity: 1,
            fillOpacity: 0.9
        };
    }
    
    //attach styles and popups to the marker layer
    function highlightDot(e) {
        var layer = e.target;
        dotStyleHighlight = highlightStyle(layer.feature);
        layer.setStyle(dotStyleHighlight);
        if (!L.Browser.ie && !L.Browser.opera) {
            layer.bringToFront();
        }
    }
    
    function resetDotHighlight(e) {
        var layer = e.target;
        dotStyleDefault = style(layer.feature);
        layer.setStyle(dotStyleDefault);
    }
    
    function onEachDot(feature, layer) {
        layer.on({
            mouseover: highlightDot,
            mouseout: resetDotHighlight
        });
        var popup = '<table style="width:110px"><tbody><tr><td><div><b>Marker:</b></div></td><td><div>' + feature.properties.popup +
            '</div></td></tr><tr class><td><div><b>Group:</b></div></td><td><div>' + feature.properties.year +
            '</div></td></tr><tr><td><div><b>X:</b></div></td><td><div>' + feature.geometry.coordinates[0] +
            '</div></td></tr><tr><td><div><b>Y:</b></div></td><td><div>' + feature.geometry.coordinates[1] +
            '</div></td></tr></tbody></table>'
    
        layer.bindPopup(popup);
    }
    
    function makeIcon(name, color) {
    
        if (name == "diamond") {
            // here's the SVG for the marker
            var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='20' height='20'> " +
                "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
                " d='M10,1 5,10 10,19, 15,10Z'/></svg>";
        }
    
        // Based on http://www.smiffysplace.com/stars.html
        if (name == "6-pointed-star") {
            // here's the SVG for the marker
            var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='28' height='28'> " +
                "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
                " d='m13 13m0 5l5 3.66l-0.67 -6.16l5.67 -2.5l-5.67 -2.5l0.67 -6.16l-5 3.66l-5 -3.66l0.67 6.16l-5.67 2.5l5.67 2.5l-0.67 6.16z'/></svg>";
        }
    
        // here's the trick, base64 encode the URL
        var svgURL = "data:image/svg+xml;base64," + btoa(icon);
    
        // create icon
        var svgIcon = L.icon({
            iconUrl: svgURL,
            iconSize: [20, 20],
            shadowSize: [12, 10],
            iconAnchor: [5, 5],
            popupAnchor: [5, -5]
        });
    
        return svgIcon
    }
    
    function renderChart(data) {
        var myDots = make_dots(data);
    
        var minZoom = 0,
            maxZoom = 15;
    
        var map = L.map('map', {
            minZoom: minZoom,
            maxZoom: maxZoom
        }).setView([0.5, 0.5], 5);
    
        L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
            continuousWorld: false,
            minZoom: 0,
            noWrap: true
        }).addTo(map);
    
        myRenderer = L.canvas({ padding: 0.5 });
    
        // Define an array to keep layerGroups
        var dotlayer = [];
    
        //create marker layer and display it on the map
        for (var i = 0; i < myDots.length; i += 1) {
            dotlayer[i] = L.geoJson(myDots[i], {
                pointToLayer: function (feature, latlng) {
                    var p = latlng;
                    // return L.circleMarker(p, style(feature));
    
                    // console.log("Starting markers.")
                    // return L.marker(p, {
                    //     renderer: myRenderer,
                    //     icon: makeIcon('6-pointed-star', style(feature).color),
                    // });
    
                    return new Marker6Point(p, style(feature));
                },
                onEachFeature: onEachDot
            }).addTo(map);
        }
        var cl = L.control.layers(null, {}).addTo(map);
        for (j = 0; j < dotlayer.length; j += 1) {
            var name = "Group " + j + "0-" + j + "9";
            cl.addOverlay(dotlayer[j], name);
        }
    }
    </script>
    </body>
    
    </html>