美文网首页W3C
iChart--地图显示人口统计

iChart--地图显示人口统计

作者: Cesium4Unreal | 来源:发表于2017-03-16 23:44 被阅读281次

导语

大数据呈现应用越来越广泛,支持大数据呈现的SDK,水平较高的有echarts、highchart、D3;然而在地图呈现的功能上,大都只能绘制矢量地图,而不能呈现具有真实效果的地图;鉴于此,本文重点在于如何制作一张,即可以看到真实效果,又能进行交互的矢量地图;

先睹为快

本文制作的地图 展示人口流动数据 echarts官方地图示例

若有所思

技术选择

想实现上述效果,最先想到的SDK是TWaver,思路也非常的简单;

  1. 使用Node呈现一张地图背景图片,像素越大越好,缩放效果好;
  2. 使用ShapeNode加载地图数据,并设置好位置、缩放比例等因素,恰好与地图重叠;
    3.控制地图的Layer为底层,不可选中;ShapeNode为上层,可交互;
TWaver绘制地图

iChart & ZRender

本文使用ichart + zrender技术,绘制上述的效果;

为什么使用zrender呢?实际上zrender的功能比较简单,用于绘制基本的形状;其实细心的你会发现,echarts的底层就是使用了zrender;

有为什么使用ichart呢?ichart用于绘制常用的图表,底层基于Canvas绘制,也比较容易改造;而echarts使用的是SVG,修改起来就没那么容易啦!

实验天地

目标一:实现柱单节状图效果

实现柱状图效果,还真没那么容易!ichart不支持怎么办? 定制!
找到ichart的柱状图类:Cylinder.js,好就从改造他开始了!

ichart柱状图

找到绘制网元的方法buildPath
<pre>
Cylinder.prototype = {
type: 'cylinder',
/**
* 创建圆形路径
* @param {CanvasRenderingContext2D} ctx
* @param {module:zrender/shape/Cylinder~ICircleStyle} style
*/
buildPath : function (ctx, style) {
//拿到你的画笔,我就随便画啦
}
}
</pre>

如上所言,获取了Canvas的画笔,自由的绘制一个矩形,上下各一个椭圆,不就完事了?
<pre>
this.ellipse(ctx, style.x, style.y, style.a, style.b);
ctx.fillRect(style.x - style.a, style.y - a, style.a * 2 , d);
this.ellipse(ctx, style.x, style.y - a, style.a, style.b);
</pre>
封装完毕,打包,混淆,加上测试代码,看效果;
<pre>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Cylinder</title>
<script type="text/javascript" src="../doc/asset/js/esl/esl.js"></script>
</head>
<body>
<script type="text/javascript">
var fileLocation = '../build/zrender';
require.config({
paths:{
zrender: fileLocation,
'zrender/shape/Circle': fileLocation,
'zrender/shape/Cylinder': fileLocation,
}
});
require(["zrender", 'zrender/shape/Circle','zrender/shape/Cylinder'], function(zrender,CircleShape,CylinderShape){
var zr = zrender.init( document.getElementById("Main") );
var shape = new CylinderShape({
style: {
x: 300,
y: 300,
a: 10,
b: 5,
height:200,
brushType: 'both',
color: 'orange',
strokeColor: 'red',
lineWidth: 1,
text: 'Cylinder'
},
highlightStyle:{
color: 'orange',
strokeColor: 'red',
lineWidth: 2,
text: 'Cylinder'
},
draggable : true,
hoverable:true,
clickable:true,
});
zr.addShape(shape);
})
</script>
<div id="Main" style="width:1000px;height:600px;"></div>
</body>
</html>
</pre>


单节柱状图

目标二:实现柱多节柱状图效果

问题来了,如果想实现多段的柱状图,如何是好呢?我想您自己都已经有思路了;多画几段不就完事了吗?

<pre>
buildPath : function (ctx, style) {
var rect = this.getRect(style);
// ctx.strokeRect(rect.x,rect.y,rect.width,rect.height);
// Better stroking in ShapeBundle
// ctx.moveTo(style.x + style.a, style.y - style.height/2);
// ctx.arc(style.x, style.y, style.r, 0, Math.PI * 2, true);
// ctx.arc(style.x, style.y, style.a, 0, Math.PI * 2, true);
// this.endDraw(style,ctx);
var data = style.data, color = style.color, isPercent = style.isPercent || false, maxHeight = style.maxHeight || 100;
if(isPercent) {
if(data instanceof Array) {
var data2 = [];
var all = 0;
for(var i = 0;i<data.length; i++) {
all += data[i];
}
for(var i = 0;i<data.length; i++) {
data2.push(maxHeight * data[i]/all);
}
data = data2;
}
}
if(data instanceof Array){
ctx.fillStyle = 'black';
ctx.shadowBlur=15;
ctx.shadowColor="black";
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
ctx.lineWidth = 1;
this.ellipse(ctx, style.x, style.y+1, style.a, style.b);
ctx.fill();
ctx.shadowBlur=0;
ctx.lineWidth = 1;
this.ellipse(ctx, style.x, style.y, style.a, style.b);
var a = 0;
for(var i = 0;i < data.length;i++){
var d = data[i];
if(color instanceof Array){
ctx.fillStyle = color[i];
ctx.strokeStyle = color[i];
}
this.endDraw(style,ctx);
a += d;
ctx.fillRect(style.x - style.a, style.y - a, style.a * 2 , d);
this.ellipse(ctx, style.x, style.y - a, style.a, style.b);
if(color instanceof Array){
ctx.fillStyle = color[i];
ctx.strokeStyle = color[i];
}
this.endDraw(style,ctx);
}
}else{
this.ellipse(ctx, style.x, style.y + style.height/2, style.a, style.b);
this.endDraw(style,ctx);
ctx.fillRect(style.x - style.a, style.y - style.height/2, style.a * 2 , style.height);
// ctx.strokeRect(style.x - style.a, style.y - style.height/2, style.a * 2 , style.height);
this.ellipse(ctx, style.x, style.y - style.height/2, style.a, style.b);
this.endDraw(style,ctx);
ctx.moveTo(style.x - style.a, style.y - style.height/2);
ctx.lineTo(style.x - style.a,style.y + style.height/2);
ctx.fill();
ctx.moveTo(style.x + style.a, style.y - style.height/2);
ctx.lineTo(style.x + style.a,style.y + style.height/2);
ctx.fill();
}
// ctx.strokeRect(style.x - style.a, style.y - style.height/2, style.lineWidth , style.height);
// ctx.strokeRect(style.x + style.a, style.y - style.height/2, style.lineWidth , style.height);
// this.ellipse(ctx, style.x, style.y+100, style.r, style.r/3);
return;
}</pre>

多段柱状图

目标三:绘制地图

使用zrender的PolygonShape绘制矢量地图;但是前提是,和底图图片完全吻合的数据哪里来呢?
聪明的我想到了使用TWaver自带编辑器,完美扣除地图数据;得到结果,形如如下数据格式:

<pre>
< px="1209.5549397107488" y="1242.081312831646"/>
< px="1209.5549397107488" y="1233.5993604641965"/>
< px="1179.8681064246748" y="1212.3944795455723"/>
< px="1184.1090826083996" y="1199.6715509943976"/>
< px="1171.3861540572252" y="1161.502765340874"/>
< px="1162.9042016897754" y="1157.2617891571492"/>
</pre>

稍微加工处理下,得到如下数据:
<pre>
{"type": "Feature","properties":{"id":"65","size":"550","name":"新疆","cp":[471.08525328117855,-97.6746544555845],"childNum":18},"geometry":{"type":"Polygon","coordinates":[[[1143.6222085570992,-80.96566177792188],
[1131.0904640488523,-76.78841360850622],
[1131.0904640488523,-93.49740628616884],
[1126.9132158794366,-135.26988798032542],
</pre>

开始加入数据,创建矢量地图;
<pre>
var smoothLine = new PolylineShape({
style : {
pointList : points,
smooth : 'spline',
brushType : 'stroke',
color : 'white',
strokeColor : "white",
lineWidth : 2,
lineType : 'dotted'
},
zlevel:1,
draggable : true,
});
zr.addShape(smoothLine);
</pre>

Paste_Image.png

最后附上完整代码:
<pre>
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<style type="text/css">
#bg{
z-index:1;
width:1300px;
height:700px;
position:absolute;
background-color: black,
}
#chart{
z-index:2;
width:280px;
height:150px;
position:absolute;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius:15px;
}
</style>
</head>
<body>
<div id="bg" ></div>
<div id="chart" ></div>
<script type="text/javascript" src="esl.js"></script>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="echarts-all.js"></script>
<script>
var fileLocation = 'zrender';
require.config({
paths:{
zrender: fileLocation,
'zrender/shape/Image': fileLocation,
'zrender/shape/Polygon': fileLocation,
'zrender/shape/Polyline': fileLocation,
'zrender/shape/Circle': fileLocation,
'zrender/shape/Cylinder': fileLocation,
'zrender/shape/Text': fileLocation,
}
});
var myChart = echarts.init(document.getElementById('chart'));
$.getJSON('china.json', function(json, textStatus) {
require(["zrender", 'zrender/shape/Image','zrender/shape/Polygon', "zrender/shape/Polyline",'zrender/shape/Circle','zrender/shape/Cylinder','zrender/shape/Text'], function(zrender, ImageShape,PolygonShape,PolylineShape,CircleShape, CylinderShape,TextShape){
zr = zrender.init( document.getElementById("bg"));
var config = require('zrender/config');
zr.on(config.EVENT.CLICK,
function(params) {
if (!params.target) {
$('#chart').css('z-index',-1);
myChart.clear();
}
}
);
zr.modLayer(0,{
zoomable:true,
panable:true,
clearColor:'#cdcdcd',
position:[160,50],
rotation:[0,0],
scale:[0.25,0.25],
});
zr.modLayer(1,{
zoomable:true,
panable:true,
clearColor:'rgba(0,0,0,0)',
position:[205.5,240.5],
rotation:[0,0],
scale:[0.25,0.25],
});
var image = new ImageShape({
position : [0, 0],
scale : [1, 1],
style : {
x : 0,
y : 0,
image : "bg_china3.png",
},
draggable : false,
clickable: false,
hoverable:false,
zlevel:0,
});
zr.addShape( image );
json.features.forEach(function (feature) {
var points = [];
if (feature.geometry.type === 'MultiPolygon') {
feature.geometry.coordinates.forEach(function (polygon) {
polygon.forEach(function (coordinate) {
coordinate.forEach(function (point, i) {
points.push(convertPoint(point));
});
});
});
} else if (feature.geometry.type === 'Polygon') {
feature.geometry.coordinates.forEach(function (coordinate) {
coordinate.forEach(function (point, i) {
points.push(convertPoint(point));
});
});
} else {
console.log(feature.geometry.type);
}
var smoothLine = new PolylineShape({
style : {
pointList : points,
smooth : 'spline',
brushType : 'stroke',
color : 'white',
strokeColor : "white",
lineWidth : 2,
lineType : 'dotted'
},
zlevel:1,
draggable : true,
});
zr.addShape(smoothLine);

      zr.addShape(new PolygonShape({
        style : {
          pointList : points,
          lineCape:'butt',
          // text:feature.properties.name,
          // textPosition:'inside',
          // textPosition:'inside',//'inside','top','bottom','left','right': 
          // textColor:'black',
          // textAlign:'start',//
          // textBaseline:'hanging',//'hanging'
          // textFont:'bold 32px verdana',
          // smooth : 0.5,
          // smoothConstraint: [[-Infinity, -Infinity], [200, Infinity]],
          brushType : 'both',
          color : (feature.properties.name === '澳门' || feature.properties.name === '香港') ? '#578096' : 'rgba(220, 20, 60, 0)',
          strokeColor : "white",
          lineWidth : 1,
        },
        highlightStyle:{
          // strokeColor:'white',
        },
        draggable : true,
        zlevel:1,
      }));
      var cp = feature.properties.cp;
      zr.addShape(new TextShape({
       style: {
        text: feature.properties.name,
        x: cp[0],
        y: cp[1] + 30,
        textFont: 'bold 32px verdana',
        textColor:'black',
      },
      draggable : false,
      zlevel:1,
    }));

      // var color = ['#C1232B','#C46924','#FCCE10'];
      var color = ['#be1e20','#ff4e00','#ff8400','#ffce00','#c0b900','#94d600','#63ccca','#00a8e6','#005db9','#ac3c73','#853376'];
      var data = [Math.random() * 100,Math.random() * 100,Math.random() * 100];
      var shape = new CylinderShape({
        style: {
          x: cp[0],
          y: cp[1],
          a: 20,
          b: 10,
          brushType: 'both',
          color: color,
          data:data,
          strokeColor: color,
          lineWidth: 1,
          text: "流入" || feature.properties.name,
          textFont:'bold 32px verdana',
        },
        highlightStyle:{
          color: color,
          strokeColor: color,
          lineWidth: 2,
          text: '流入' || feature.properties.name,
          textFont:'bold 32px verdana',
        },
        hoverable:false,
        clickable:true,
        draggable: false,
        zlevel:1,
        onmousedown: function(e){
          if(e.event.detail == 2){
            option = {
              backgroundColor:'rgba(31,34,37,0.8)',
              title : {
                text:feature.properties.name +'(流入)',
                x:'left',
                textStyle:{
                  color:'white',
                }
              },
              tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
              },
              color:['#be1e20','#ff4e00','#ff8400','#ffce00','#c0b900','#94d600','#63ccca','#00a8e6','#005db9','#ac3c73','#853376'],
              series : [
              {
                name:'北京人口',
                type:'pie',
                radius : '40%',
                center: ['50%', '60%'],
                data:[
                {value:100, name:'第一类人' + '(' + (100/2230 * 100).toFixed(0)+'%)'},
                {value:300, name:'第二类人' + '(' + (200/2230 * 100).toFixed(0)+'%)'},
                {value:400, name:'第三类人' + '(' + (400/2230 * 100).toFixed(0)+'%)'},
                {value:400, name:'第四类人' + '(' + (400/2230 * 100).toFixed(0)+'%)'},
                {value:300, name:'第五类人' + '(' + (300/2230 * 100).toFixed(0)+'%)'},
                {value:250, name:'第六类人' + '(' + (250/2230 * 100).toFixed(0)+'%)'},
                {value:200, name:'第七类人' + '(' + (200/2230 * 100).toFixed(0)+'%)'},
                {value:180, name:'第八类人' + '(' + (180/2230 * 100).toFixed(0)+'%)'},
                {value:100, name:'第九类人' + '(' + (100/2230 * 100).toFixed(0)+'%)'}
                ]
              }
              ]
            };
            var chartDiv = document.getElementById('chart');
            chart.style.left = e.event.clientX + 30 + "px";
            chart.style.top = e.event.clientY - 210/2 + "px";
            myChart.setOption(option);
            $('#chart').css('z-index',2);
          }
        }
      });
      zr.addShape(shape);
      var color = ['#B5C334','#F4E001','#F0805A'];
      var data = [Math.random() * 150,Math.random() * 150,Math.random() * 150];
      var shape = new CylinderShape({
        style: {
          x: cp[0] + 50,
          y: cp[1],
          a: 20,
          b: 10,
          data:data,
          brushType: 'both',
          color: color,
          strokeColor: color,
          lineWidth: 1,
          text: '流出'||feature.properties.name,
          textFont:'bold 32px verdana',
        },
        highlightStyle:{
          color: color,
          strokeColor: color,
          lineWidth: 2,
          text: '流出'||feature.properties.name,
          textFont:'bold 32px verdana',
        },
        hoverable:true,
        clickable:true,
        draggable: false,
        zlevel:1,
        onmousedown: function(e){
          if(e.event.detail == 2){
            option = {
              backgroundColor:'rgba(31,34,37,0.8)',
              title : {
                text:feature.properties.name + '(流出)',
                x:'left',
                textStyle:{
                  color:'white',
                }
              },
              tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
              },
              color:['#be1e20','#ff4e00','#ff8400','#ffce00','#c0b900','#94d600','#63ccca','#00a8e6','#005db9','#ac3c73','#853376'],
              series : [
              {
                type:'pie',
                radius : '40%',
                center: ['50%', '60%'],
                data:[
                {value:100, name:'第一类人' + '(' + (100/2230 * 100).toFixed(0)+'%)'},
                {value:300, name:'第二类人' + '(' + (200/2230 * 100).toFixed(0)+'%)'},
                {value:400, name:'第三类人' + '(' + (400/2230 * 100).toFixed(0)+'%)'},
                {value:400, name:'第四类人' + '(' + (400/2230 * 100).toFixed(0)+'%)'},
                {value:300, name:'第五类人' + '(' + (300/2230 * 100).toFixed(0)+'%)'},
                {value:250, name:'第六类人' + '(' + (250/2230 * 100).toFixed(0)+'%)'},
                {value:200, name:'第七类人' + '(' + (200/2230 * 100).toFixed(0)+'%)'},
                {value:180, name:'第八类人' + '(' + (180/2230 * 100).toFixed(0)+'%)'},
                {value:100, name:'第九类人' + '(' + (100/2230 * 100).toFixed(0)+'%)'}
                ]
              }
              ]
            };
            var chartDiv = document.getElementById('chart');
            chart.style.left = e.event.clientX + 30 + "px";
            chart.style.top = e.event.clientY - 210/2 + "px";
            myChart.setOption(option);
            $('#chart').css('z-index',2);
          }
        }
      });
      zr.addShape(shape);
    });

zr.render();
});
});

function randomColor(){
return '#'+('00000'+(Math.random()*0x1000000<<0).toString(16)).substr(-6);
}
function convertPoint (point) {
return point;
}
</script>
</body>
</html>
</pre>

相关文章

  • iChart--地图显示人口统计

    导语 大数据呈现应用越来越广泛,支持大数据呈现的SDK,水平较高的有echarts、highchart、D3;然而...

  • iOS笔记-地图的基本使用

    地图的基本使用 1.设置地图显示类型// 1.设置地图显示类型 /** MKM...

  • 高德地图

    一.申请key(略) 二.创建地图 1. 显示地图 1)显示以某点为中心的地图 2)加载地图javascript ...

  • Django Widgets

    Geodjango后台地图默认显示原始地图,现需求为另显示高德地图(围栏编辑) 只是显示围栏,不需求编辑使用dja...

  • 显示地图

    理解几何地图 在地图上你要怎么表示点的数据依赖于你想要怎么使用它们.Map Kit支持以下三种基础坐标来表示特定的...

  • iOS Mapkit的使用

    【iOS】Mapkit的使用:地图显示、定位、大头针、气泡等 标签:iOS地图mapkit 1.显示地图 (1)首...

  • 地图和定位(四)

    一、地图的基本使用(MapKit) 代码: 二、地图显示用户位置 三、模拟追踪显示用户位置 1、显示用户位置的蓝点...

  • Vue百度地图center偏移问题

    最近在vue项目中用到了百度地图,UI是在tab切换到“地图”时显示地图。但是遇到了地图显示时,设定的center...

  • 地图研究

    地图的基本使用 设置地图类型'' // 1.设置地图显示类型'' /**'' MKMa...

  • Echart不显示地图问题

    Echart不显示地图的问题 使用百度Echart的时候遇到不会显示地图的问题,是现在Echart不直接提供地图需...

网友评论

    本文标题:iChart--地图显示人口统计

    本文链接:https://www.haomeiwen.com/subject/ksssnttx.html