需求
看下本次的需求:
希望实现如下图的热力图的展示:
但是个性化的是需要自定义底图,并且实现摇杆的功能。
我们来看一下echarts的文档:
https://echarts.apache.org/examples/zh/index.html#chart-type-heatmap
在提供的热力图的case中,只有两种类型,一种是基于百度底图进行扩展的,另一种是直角坐标系下的方块图。
在github的issue中我们可以看到作者的回复,在echarts3.0版本中是没有提供一个自定义底图的底图热力图版本的。
ECHARTS2.0的case
很多人说2.0版本实现了这个功能,我们来看一下2.0版本,在网上找到了一个2.0版本的case,看下运行效果图:
显然,这个版本的case可以实现两张图叠加的效果(case地址:https://github.com/feddiyao/echarts2.0-case)
显然,我们可以用热力图来进行叠加,看一下实现代码:
function test(heatData) {
require.config({
paths: {
echarts: './static/echarts/src'
}
});
require(
[
'echarts',
'echarts/chart/heatmap'
],
function (ec) {
// 基于准备好的dom,初始化echarts图表
var myChart = ec.init(document.getElementById('main'));
var option = {
series: [
{
type: 'heatmap',
data: heatData,
hoverable: false,
gradientColors: ['green', 'yellow', 'orange', 'red'],
minAlpha: 0.2,
valueScale: 2,
opacity: 1,
blurSize: 5
}
]
};
// 为echarts对象加载数据
myChart.setOption(option);
}
);
};
看下运行结果:
叠加已经完成了,但是注意产品经理的需求,我们需要摇杆,我们的颜色展示是基于密度的,看下echarts的源码:
define("echarts/chart/heatmap", ["require", "./base", "../layer/heatmap", "../config", "../util/ecData", "zrender/tool/util", "zrender/tool/color", "zrender/shape/Image", "../chart"],
function(e) {
function t(e, t, n, a, o) {
i.call(this, e, t, n, a, o),
this.refresh(a)
}
var i = e("./base"),
n = e("../layer/heatmap"),
a = e("../config"),
o = (e("../util/ecData"), e("zrender/tool/util")),
r = (e("zrender/tool/color"), e("zrender/shape/Image"));
return a.heatmap = {
zlevel: 0,
z: 2,
clickable: !0
},
t.prototype = {
type: a.CHART_TYPE_HEATMAP,
refresh: function(e) {
this.clear(),
e && (this.option = e, this.series = e.series),
this._init()
},
_init: function() {
var e = this.series;
this.backupShapeList();
for (var t = e.length,
i = 0; t > i; ++i) if (e[i].type === a.CHART_TYPE_HEATMAP) {
e[i] = this.reformOption(e[i]);
var o = new n(e[i]),
s = o.getCanvas(e[i].data, this.zr.getWidth(), this.zr.getHeight()),
l = new r({
position: [0, 0],
scale: [1, 1],
hoverable: this.option.hoverable,
style: {
x: 0,
y: 0,
image: s,
width: s.width,
height: s.height
}
});
this.shapeList.push(l)
}
this.addShapeList()
}
},
o.inherits(t, i),
e("../chart").define("heatmap", t),
t
}),
define("echarts/layer/heatmap", ["require"],
function() {
function e(e) {
if (this.option = e, e) for (var i in t) this.option[i] = void 0 !== e[i] ? e[i] : t[i];
else this.option = t
}
var t = {
blurSize: 30,
gradientColors: ["blue", "cyan", "lime", "yellow", "red"],
minAlpha: .05,
valueScale: 1,
opacity: 1
},
i = 20,
n = 256;
return e.prototype = {
getCanvas: function(e, t, a) {
var o = this._getBrush(),
r = this._getGradient(),
s = i + this.option.blurSize,
l = document.createElement("canvas");
l.width = t,
l.height = a;
for (var h = l.getContext("2d"), m = e.length, V = 0; m > V; ++V) {
var d = e[V],
U = d[0],
p = d[1],
c = d[2],
u = Math.min(1, Math.max(c * this.option.valueScale || this.option.minAlpha, this.option.minAlpha));
h.globalAlpha = u,
h.drawImage(o, U - s, p - s)
}
for (var g = h.getImageData(0, 0, l.width, l.height), y = g.data, m = y.length / 4; m--;) {
var b = 4 * m + 3,
u = y[b] / 256,
f = Math.floor(u * (n - 1));
y[b - 3] = r[4 * f],
y[b - 2] = r[4 * f + 1],
y[b - 1] = r[4 * f + 2],
y[b] *= this.option.opacity
}
return h.putImageData(g, 0, 0),
l
},
_getBrush: function() {
if (!this._brushCanvas) {
this._brushCanvas = document.createElement("canvas");
var e = i + this.option.blurSize,
t = 2 * e;
this._brushCanvas.width = t,
this._brushCanvas.height = t;
var n = this._brushCanvas.getContext("2d");
n.shadowOffsetX = t,
n.shadowBlur = this.option.blurSize,
n.shadowColor = "black",
n.beginPath(),
n.arc( - e, e, i, 0, 2 * Math.PI, !0),
n.closePath(),
n.fill()
}
return this._brushCanvas
},
_getGradient: function() {
if (!this._gradientPixels) {
var e = n,
t = document.createElement("canvas");
t.width = 1,
t.height = e;
for (var i = t.getContext("2d"), a = i.createLinearGradient(0, 0, 0, e), o = this.option.gradientColors.length, r = 0; o > r; ++r)"string" == typeof this.option.gradientColors[r] ? a.addColorStop((r + 1) / o, this.option.gradientColors[r]) : a.addColorStop(this.option.gradientColors[r].offset, this.option.gradientColors[r].color);
i.fillStyle = a,
i.fillRect(0, 0, 1, e),
this._gradientPixels = i.getImageData(0, 0, 1, e).data
}
return this._gradientPixels
}
},
e
}),
define("echarts/layer/heatmap", ["require"],
function() {
function e(e) {
if (this.option = e, e) for (var i in t) this.option[i] = void 0 !== e[i] ? e[i] : t[i];
else this.option = t
}
var t = {
blurSize: 30,
gradientColors: ["blue", "cyan", "lime", "yellow", "red"],
minAlpha: .05,
valueScale: 1,
opacity: 1
},
i = 20,
n = 256;
return e.prototype = {
getCanvas: function(e, t, a) {
var o = this._getBrush(),
r = this._getGradient(),
s = i + this.option.blurSize,
l = document.createElement("canvas");
l.width = t,
l.height = a;
for (var h = l.getContext("2d"), m = e.length, V = 0; m > V; ++V) {
var d = e[V],
U = d[0],
p = d[1],
c = d[2],
u = Math.min(1, Math.max(c * this.option.valueScale || this.option.minAlpha, this.option.minAlpha));
h.globalAlpha = u,
h.drawImage(o, U - s, p - s)
}
for (var g = h.getImageData(0, 0, l.width, l.height), y = g.data, m = y.length / 4; m--;) {
var b = 4 * m + 3,
u = y[b] / 256,
f = Math.floor(u * (n - 1));
y[b - 3] = r[4 * f],
y[b - 2] = r[4 * f + 1],
y[b - 1] = r[4 * f + 2],
y[b] *= this.option.opacity
}
return h.putImageData(g, 0, 0),
l
},
_getBrush: function() {
if (!this._brushCanvas) {
this._brushCanvas = document.createElement("canvas");
var e = i + this.option.blurSize,
t = 2 * e;
this._brushCanvas.width = t,
this._brushCanvas.height = t;
var n = this._brushCanvas.getContext("2d");
n.shadowOffsetX = t,
n.shadowBlur = this.option.blurSize,
n.shadowColor = "black",
n.beginPath(),
n.arc( - e, e, i, 0, 2 * Math.PI, !0),
n.closePath(),
n.fill()
}
return this._brushCanvas
},
_getGradient: function() {
if (!this._gradientPixels) {
var e = n,
t = document.createElement("canvas");
t.width = 1,
t.height = e;
for (var i = t.getContext("2d"), a = i.createLinearGradient(0, 0, 0, e), o = this.option.gradientColors.length, r = 0; o > r; ++r)"string" == typeof this.option.gradientColors[r] ? a.addColorStop((r + 1) / o, this.option.gradientColors[r]) : a.addColorStop(this.option.gradientColors[r].offset, this.option.gradientColors[r].color);
i.fillStyle = a,
i.fillRect(0, 0, 1, e),
this._gradientPixels = i.getImageData(0, 0, 1, e).data
}
return this._gradientPixels
}
},
e
});
_getBrush
方法按照高斯模糊的设定进行刷子生成, _getGradient
为热点的地方进行涂色。
按照权重选择颜色展示的热力图
但是我们要求的是,按照权重比例进行涂色,我们需要对源码进行修改,看下修改后的逻辑:
define("echarts/chart/heatmap", ["require", "./base", "../layer/heatmap", "../config", "../util/ecData", "zrender/tool/util", "zrender/tool/color", "zrender/shape/Image", "../chart"], function(e) {
function t(e, t, n, a, o) {
i.call(this, e, t, n, a, o), this.refresh(a)
}
var i = e("./base"),
n = e("../layer/heatmap"),
a = e("../config"),
o = (e("../util/ecData"), e("zrender/tool/util")),
r = (e("zrender/tool/color"), e("zrender/shape/Image"));
return a.heatmap = {
zlevel: 0,
z: 2,
clickable: !0
}, t.prototype = {
type: a.CHART_TYPE_HEATMAP,
refresh: function(e) {
this.clear(), e && (this.option = e, this.series = e.series), this._init()
},
_init: function() {
var e = this.series;
this.backupShapeList();
for (var t = e.length, i = 0; t > i; ++i)
if (e[i].type === a.CHART_TYPE_HEATMAP) {
e[i] = this.reformOption(e[i]);
var o = new n(e[i]),
s = o.getCanvas(e[i].data, this.zr.getWidth(), this.zr.getHeight()),
l = new r({
position: [0, 0],
scale: [1, 1],
hoverable: this.option.hoverable,
style: {
x: 0,
y: 0,
image: s,
width: s.width,
height: s.height
}
});
this.shapeList.push(l)
}
this.addShapeList()
}
}, o.inherits(t, i), e("../chart").define("heatmap", t), t
}), define("echarts/layer/heatmap", ["require"], function() {
function e(e) {
if (this.option = e, e)
for (var i in t) this.option[i] = void 0 !== e[i] ? e[i] : t[i];
else this.option = t
}
var t = {
blurSize: 30,
gradientColors: ["blue", "cyan", "lime", "yellow", "red"],
minAlpha: .05,
valueScale: 1,
opacity: 1
},
i = 2,
n = 256;
return e.prototype = {
getCanvas: function(e, t, a) {
var o = this._getBrush(),
r = this._getGradient(),
s = i + this.option.blurSize,
l = document.createElement("canvas");
l.width = t, l.height = a;
for (var h = l.getContext("2d"), m = e.length, V = 0; m > V; ++V) {
var d = e[V],
U = d[0],
p = d[1],
c = d[2],
//u的值为权重 * 价值量和低通滤波的最大值 再和1 取最小值
u = Math.min(1, Math.max(c * this.option.valueScale || this.option.minAlpha, this.option.minAlpha));
//getImageData获得的是四通道的数据
for (var g = o.getContext("2d").getImageData(0, 0, o.width, o.height), y = g.data, k = y.length / 4; k --;) {
var b = 4 * k + 3,
q = (c - this.option.min) / (this.option.max - this.option.min),
f = Math.floor(q * r.length / 4);
y[b - 3] = r[f * 4], y[b - 2] = r[f * 4 + 1], y[b - 1] = r[f * 4 + 2], y[b] *= this.option.opacity
}
o.getContext("2d").putImageData(g, 0, 0)
//context.drawImage(img,x,y);
h.globalAlpha = u, h.drawImage(o, U - s , p - s)
}
return h, l
},
//获取刷子,在一个2*e边长的正方形上画圆,
_getBrush: function() {
if (!this._brushCanvas) {
this._brushCanvas = document.createElement("canvas");
//内径是i 设定均值滤波blursize,模糊后画圆,模糊圆半径为e
var e = i + this.option.blurSize,
t = 2 * e;
//在长宽为2e的画布上画圆
this._brushCanvas.width = t, this._brushCanvas.height = t;
var n = this._brushCanvas.getContext("2d");
//shadowOffsetX 设置或返回形状与阴影的水平距离,shadowBlur 属性设置或返回阴影的模糊级数 arc(圆中心的X坐标,圆中心的y坐标,半径,起始角,结束角)
n.shadowOffsetX = t, n.shadowBlur = this.option.blurSize, n.shadowColor = "black", n.beginPath(), n.arc(-e, e, i, 0, 2 * Math.PI, !0), n.closePath(), n.fill()
}
return this._brushCanvas
},
_getGradient: function() {
if (!this._gradientPixels) {
var e = n,
t = document.getElementById("myCanvas");
t.width = e, t.height = 5;
// 定义渐变点createLinearGradient(渐变开始点x坐标,渐变开始点y坐标,渐变结束点x坐标,渐变结束点y坐标)offset是每个颜色所占的比重
for (var i = t.getContext("2d"), a = i.createLinearGradient(0, 0, e, 0), o = this.option.gradientColors.length, r = 0; o > r; ++r) "string" == typeof this.option.gradientColors[r] ? a.addColorStop((r + 1) / o, this.option.gradientColors[r]) : a.addColorStop(this.option.gradientColors[r].offset, this.option.gradientColors[r].color);
//fillRect(x,y,width,height) 矩形填充颜色
i.fillStyle = a, i.fillRect(0, 0, e, 5), this._gradientPixels = i.getImageData(0, 0, e, 5).data
}
return this._gradientPixels
}
}, e
}), define("echarts/layer/heatmap", ["require"], function() {
function e(e) {
if (this.option = e, e)
for (var i in t) this.option[i] = void 0 !== e[i] ? e[i] : t[i];
else this.option = t
}
var t = {
blurSize: 30,
gradientColors: ["blue", "cyan", "lime", "yellow", "red"],
minAlpha: .05,
valueScale: 1,
opacity: 1
},
i = 20,
n = 256;
return e.prototype = {
getCanvas: function(e, t, a) {
var o = this._getBrush(),
r = this._getGradient(),
s = i + this.option.blurSize,
l = document.createElement("canvas");
l.width = t, l.height = a;
for (var h = l.getContext("2d"), m = e.length, V = 0; m > V; ++V) {
var d = e[V],
U = d[0],
p = d[1],
c = d[2],
u = Math.min(1, Math.max(c * this.option.valueScale || this.option.minAlpha, this.option.minAlpha));
h.globalAlpha = u, h.drawImage(o, U - s, p - s)
}
for (var g = h.getImageData(0, 0, l.width, l.height), y = g.data, m = y.length / 4; m--;) {
var b = 4 * m + 3,
u = y[b] / 256,
f = Math.floor(u * (n - 1));
y[b - 3] = r[4 * f], y[b - 2] = r[4 * f + 1], y[b - 1] = r[4 * f + 2], y[b] *= this.option.opacity
}
return h.putImageData(g, 0, 0), l
},
_getBrush: function() {
if (!this._brushCanvas) {
this._brushCanvas = document.createElement("canvas");
var e = i + this.option.blurSize,
t = 2 * e;
this._brushCanvas.width = t, this._brushCanvas.height = t;
var n = this._brushCanvas.getContext("2d");
n.shadowOffsetX = t, n.shadowBlur = this.option.blurSize, n.shadowColor = "black", n.beginPath(), n.arc(-e, e, i, 0, 2 * Math.PI, !0), n.closePath(), n.fill()
}
return this._brushCanvas
},
_getGradient: function() {
if (!this._gradientPixels) {
var e = n,
t = document.createElement("canvas");
t.width = 1, t.height = e;
for (var i = t.getContext("2d"), a = i.createLinearGradient(0, 0, 0, e), o = this.option.gradientColors.length, r = 0; o > r; ++r) "string" == typeof this.option.gradientColors[r] ? a.addColorStop((r + 1) / o, this.option.gradientColors[r]) : a.addColorStop(this.option.gradientColors[r].offset, this.option.gradientColors[r].color);
i.fillStyle = a, i.fillRect(0, 0, 1, e), this._gradientPixels = i.getImageData(0, 0, 1, e).data
}
return this._gradientPixels
}
}, e
});
在_getGradient
方法中,我们调整进行颜色的过渡,在getCanvas
方法中,我们按照权重在最大最小值中占的比例,来选定刷子的颜色取值,而非之前的直接晕染开来,看下运行后的效果图:
这里调小了每个点的半径。并且在_getGradient
方法中生成摇杆,并且把摇杆横放。
看下主体结构的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>自定义地图样例</title>
<style type="text/css">
.range-list {
width: 320px;
display: flex
}
.range-list span {
display: inline-flex;
flex-grow: 1
}
</style>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="graphic" class="col-md-8" style="width: 1130px;float:none!important;">
<img id="baidu-img" src="./static/test_image/test.jpg">
<div id="main" style="width: 1130px; height: 400px;position: absolute; top: 0; left: 0;"></div>
</div>
<div>
<canvas id="myCanvas" style="border:1px solid #d3d3d3;"></canvas>
<div class="range-list">
<span>0</span>
<span>125</span>
<span>250</span>
<span>375</span>
<span>500</span>
</div>
</div>
<!-- <div id="main" style="height: 400px; width: 45%; min-width: 600px; float: left;" ></div> -->
<!-- ECharts单文件引入 -->
<script src="./static/echarts/src/echarts-oss.js" charset="UTF-8"></script>
<!-- <script src="./static/echarts/jquery.js"></script> -->
<script src="./static/echarts/esl.js"></script>
<script type="text/javascript">
let heatData = [[0,0,1],[50,200,350],[51,200,350],[70,200,499],[71,200,499],[72,200,499],[73,200,499],[74,200,499],[75,200,499],[76,200,499],[77,200,499],[78,200,499],[79,200,499],[80,200,499],[81,200,499]];
//max和min为数据中的最大值和最小值(可通过遍历数据获得)
let max = 500;
let min = 0;
test(heatData);
function test(heatData) {
require.config({
paths: {
echarts: './static/echarts/src'
}
});
require(
[
'echarts',
'echarts/chart/heatmap'
],
function (ec) {
// 基于准备好的dom,初始化echarts图表
var myChart = ec.init(document.getElementById('main'));
var option = {
series: [
{
type: 'heatmap',
data: heatData,
hoverable: false,
gradientColors: ['green', 'yellow', 'orange', 'red'],
minAlpha: 0.2,
valueScale: 2,
opacity: 1,
max: max,
min: min,
blurSize: 5
}
]
};
// 为echarts对象加载数据
myChart.setOption(option);
}
);
};
let myCanvas = document.getElementById("myCanvas")
myCanvas.addEventListener("mousedown", function () {
//标准的获取鼠标点击相对于canvas画布的坐标公式
let x = event.pageX - $("#myCanvas")[0].getBoundingClientRect().left;
let per = x / 256, mindata = per * (max - min) + min
getRangeCanvas(mindata);
});
myCanvas.addEventListener("mousemove", throttle(mouseMoveHandle,1000))
myCanvas.addEventListener("mouseup", function () {
let x = event.clientX - $("#myCanvas")[0].getBoundingClientRect().left;
let per = x / 256, mindata = per * (max - min) + min
getRangeCanvas(mindata);
})
function mouseMoveHandle() {
let x = event.clientX - $("#myCanvas")[0].getBoundingClientRect().left;
let per = x / 256, mindata = per * (max - min) + min
getRangeCanvas(mindata);
}
function getRangeCanvas(mindata) {
let copy_heatdata = heatData;
let points = [].concat.apply([], copy_heatdata.map(function (item) {
if (item[2] > mindata) {
return [item]
}
return [];
}))
test(points)
}
function throttle(fn, wait) {
var pre = Date.now();
return function () {
var context = this;
var args = arguments;
var now = Date.now();
if (now - pre >= wait) {
fn.apply(context, args);
pre = Date.now();
}
}
}
</script>
</body>
我们为摇杆加入了监听事件,并且做了一些函数截流的操作,避免请求过于频繁,这样调整摇杆就能进行数据的筛选,这样就实现了整个热力图的功能
资源地址:https://download.csdn.net/download/yfm120750310/13583102