数据可视化:使用D3JS创建动画地图,发射动画

示例图

1516937591.jpg

演示页面

制作思路

  1. 需要绘制一张中国地图,做为背景。
  2. 需要主要城市的经纬坐标,以绘制路线起点和终点。
  3. 从起点绘制一条逐渐延伸的线,到终点。
  4. 接收到物流单的城市,绘制一个闪烁的标记。
  5. 已经有过物流单的目标城市,不再绘制路线。
  6. 每次新产生一笔物流单,都有一个标记沿路线移向目标的动画效果。
  7. 绘制结束后的数据,需要清理掉,以减少对浏览器的资源占用。

开始撸码

1.创建网页模板

  1. 加载D3JS,为了方便调试,直接下载d3.js文件在本地,实际使用的时候,可以换成加载路径。注意,使用的是V4版的D3,和V3版有差异。
  2. 创建一个DIV块,准备绘图。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf8">
        <script type="text/javascript" src="../../static/d3/d3.js"></script>
        <title>地图</title>
    </head>
    <body>
        <div class="fxmap">
        </div>
    </body>
    <script type="text/javascript"></script>
</html

创建SVG,以下所有代码放在<script></script>中

var width=1000 , height=800;  // 定义SVG宽高
var svg = d3.select("body div.fxmap")
                        .append("svg")
                        .attr("width", "width)  
                        .attr("height", height)
                        .style("background","#000");  //

创建SVG图形分组,以备调用

  1. gmp,保存背景地图和起点标记。
  2. mline,保存起点和目标之间的连线,以及目标点。
  3. buttion,测试用的按钮
        gmap = svg.append("g").attr("id", "map").attr("stroke", "white").attr("stroke-width",1);
        mline = svg.append("g").attr("id", "moveto").attr("stroke", "#FFF").attr("stroke-width", 1.5).attr("fill","#FFF");
        button = svg.append("g").attr("id", "button").attr("stroke", "white").attr("stroke-width", 1).attr("fill", "white");

创建投影函数

  1. 经纬度不能直接用来绘图,需要转换成平面坐标。d3js提供了比较丰富的投影方法,本例中使用了geoEquirectangular()方法。
  2. projection 是将经纬度转换为平面坐标的方法
  3. path 是将经纬度转换为连线绘制点坐标的方法(省得自己再写函数构造path路径)
var projection = d3.geoEquirectangular()
                            .center([465,395])  // 指定投影中心,注意[]中的是经纬度
                            .scale(height)
                            .translate([width / 2, height / 2]);
var path = d3.geoPath().projection(projection);

创建marker标记,以便多个连线终点调用

  1. 由于会有多个物流连线的终点,所以都放在一个marker标记中调用。
  2. 这个标记是由中间的 圆形 + 外圈 构成。外圈的闪烁效果另外创建。
        marker = svg.append("defs")
                    .append("marker")
                    .append("marker")
                    .attr("id", "pointer")
                    .attr("viewBox","0 0 12 12")    // 可见范围
                    .attr("markerWidth","12")        // 标记宽度
                    .attr("markerHeight","12")       // 标记高度
                    .attr("orient", "auto")         //
                    .attr("markerUnits", "strokeWidth") // 随连接线宽度进行缩放
                    .attr("refX", "6")              // 连接点坐标
                    .attr("refY", "6")
        // 绘制标记中心圆
        marker.append("circle")
                .attr("cx", "6")
                .attr("cy", "6")
                .attr("r", "3")
                .attr("fill", "white");
        // 绘制标记外圆,之后在timer()中添加闪烁效果
        marker.append("circle")
                .attr("id", "markerC")
                .attr("cx", "6")
                .attr("cy", "6")
                .attr("r", "5")
                .attr("fill-opacity", "0")
                .attr("stroke-width", "1")
                .attr("stroke", "white");

绘制中国地图,并标记起点(长沙)

  1. 地图使用的经纬集为china.json,这个文件网上有很多
        // 记录长沙坐标
        var changsha = projection([112.59,28.12]);
        // 读取地图数据,并绘制中国地图
        mapdata = [];
        d3.json('china.json', function(error, data){
            if (error)
                console.log(error);
            // 读取地图数据
            mapdata = data.features;
            // 绘制地图
            gmap.selectAll("path")
                .data(mapdata)
                .enter()
                .append("path")
                .attr("d", path);
            // 标记长沙
            gmap.append("circle").attr("id","changsha")
                .attr("cx", changsha[0])
                .attr("cy", changsha[1])
                .attr("r", "6")
                .attr("fill", "yellow")
            gmap.append("circle").attr("id","changshaC")
                .attr("cx", changsha[0])
                .attr("cy", changsha[1])
                .attr("r", "10")
                .attr("stroke-width", "2")
                .attr("fill-opacity", "0");
        });

创建方法,绘制一条从指定起点到终点的连线,并在络点绘制marker标记。

  1. 方法需要输入终点城市名称(city)和经纬度(data)
  2. 调用之前建立的project()方法,将终点经纬度转换为平面坐标。
  3. 计算起点(长沙)和终点之前的距离,做为线条长度和动画时间参数。
  4. 在线条上绘制一个圆形标记,并实现从起点到终点的移动动画。
  5. 标记移动到终点后,即删除,节省资源。
  6. 如果线点在之前已经绘制过,则不绘线条,只绘制移动标记。
  7. 每处理一次物流单,则城市记录+1。
        // 创建对象,保存每个城市的物流记录数量
        var citylist = new Object();
        // 创建方法,输入data坐标,绘制发射线
        var moveto = function(city, data){
            var pf = {x:projection([112.59,28.12])[0], y:projection([112.59,28.12])[1]};
            var pt = {x:projection(data)[0], y:projection(data)[1]};
            var distance = Math.sqrt((pt.x - pf.x)**2 + (pt.y - pf.y)**2);
            if (city in citylist){
                citylist[city]++;
            }else{
                mline.append("line")
                        .attr("x1", pf.x)
                        .attr("y1", pf.y)
                        .attr("x2", pt.x)
                        .attr("y2", pt.y)
                        .attr("marker-end","url(#pointer)")
                        .style("stroke-dasharray", " "+distance+", "+distance+" ")
                        .transition()
                        .duration(distance*30)
                        .styleTween("stroke-dashoffset", function(){
                            return d3.interpolateNumber(distance, 0);
                        });
                citylist[city] = 1;
            };
            mline.append("circle")
                .attr("cx", pf.x)
                .attr("cy", pf.y)
                .attr("r", 3)
                .transition()
                .duration(distance*30)
                .attr("transform", "translate("+(pt.x-pf.x)+","+(pt.y-pf.y)+")")
                .remove();
        };

创建动画队例,实现标记外圈的闪烁效果

        var scale = d3.scaleLinear();
        scale.domain([0, 1000, 2000])
            .range([0, 1, 0]);
        var start = Date.now();
        d3.timer(function(){
            var s1 = scale((Date.now() - start)%2000);
            // console.log(s1);
            gmap.select("circle#changshaC")
                .attr("stroke-opacity", s1);
            marker.select("circle#markerC")
                .attr("stroke-opacity", s1);
        });

创建测试按钮和测试的目标城市数据

        var cityordinate = {
            '哈尔滨':[126.5416150000,45.8088260000],
            '石家庄':[116.46,39.92],
            '北京':[116.39564503787867,39.92998577808024],
            '上海':[121.480539,31.235929],
            '广州':[113.271431,23.135336],
            '重庆':[106.558434,29.568996],
            '青岛':[120.38442818368189,36.10521490127382],
            '福州':[119.30347,26.080429],
            '兰州':[103.840521,36.067235],
            '贵阳':[106.636577,26.653325],
            '成都':[104.081534,30.655822],
            '西安':[108.946466,34.347269],
            '长春':[125.3306020000,43.8219540000],
            '台湾':[120.961454,23.80406],
            '呼和浩特':[111.7555090000,40.8484230000],
            '澳门':[113.5494640000,22.1929190000],
            '武汉':[114.3115820000,30.5984670000],
            '昆明':[102.71460113878045,25.049153100453159],
            '乌鲁木齐':[87.56498774111579,43.84038034721766],
            '益阳':[112.36654664522563,28.58808777988717],
            '南京':[118.77807440802562,32.05723550180587],
            '武昌':[114.35362228468498,30.56486029278519],
        };

        // 随机获得目标城市
        var cityname = [], total = 0;
        for (var key in cityordinate){
            cityname[total++] = key;
        };
       
        // 创建操作按钮,每次点击发射一条物流线
        button.append("circle")
                .attr("cx", width*0.9)
                .attr("cy", height*0.8)
                .attr("r", width/20)
                .attr("text","click")
                .attr("fill", "grey");
        button.append("text")
                .attr("x", width*0.87)
                .attr("y", height*0.81)
                .style("font-size", "30px")
                .text("click");
        button.on("click", function(){
            var _index = ~~(Math.random() * total);
            moveto(cityname[_index], cityordinate[cityname[_index]]);
        });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容