local setting = require("bundle://setting");
local fs = require("fs");
local math = require("math");
local json = require("json");
local heatmap_config = setting.heatmap_config;
local root = heatmap_config.root;
local radius_level_value = heatmap_config.radius_level_value;
local raduis_level_max = heatmap_config.raduis_level_max;
local r,err = fs.existsSync(root);
if not r then
fs.mkdirSync(root);
end
-- 半径等级划分原则
local function getLevelByMax(val, max)
local level = math.floor(val*10/max);
if level == 0 then level = 1; end
return level;
end
-- 半径扩大原则
local function getRadiusByLevel(level, base)
return base*level;
end
-- 碰撞检测
local function checkImpact(old, new, hmap)
-- 根据点被点击的次数和点击最大值,计算出这个点的级别,级别越高,点的半径越大
local level = getLevelByMax(old.value, hmap.max);
-- 根据级别和半径单位,获取真实半径
local radius = getRadiusByLevel(level, radius_level_value);
-- 如果新的点,在老的点的范围内,那么就算碰撞了
if radius >= math.floor(math.sqrt(math.pow(old.y - new.y, 2) + math.pow(old.x - new.x, 2))) then
return old;
end
end
local function merge_points(old, new, hmap)
-- 圆心均衡
old.x = math.floor((old.value*old.x + new.x)/(old.value+1));
old.y = math.floor((old.value*old.y + new.y)/(old.value+1));
old.value = 1 + old.value;
end
-- 将数据进行简化
local function analysis(req, parseconfig)
if not req.ps.head or not req.ps.record then return {ret = -1, err="热力图解析失败: req.ps.head或req.ps.record不存在"}; end
local gameid = req.ps.head.gameid or req.ps.gameid;
local source = req.ps.head.source or req.ps.source;
local timestamp = tonumber(req.ps.head.timestamp or req.ps.timestamp);
if not source then return {ret = -1, err = "热力图解析失败: 没有source字段, 请检查数据格式"} end
if not gameid then return {ret = -1, err = "热力图解析失败: 没有gameid字段, 请检查数据格式"} end
if not timestamp then return {ret = -1, err = "热力图解析失败: 没有gameid字段, 请检查数据格式"} end
local openfds = {};
local now = timestamp;
-- 时间目录
local datepath = string.format("%s/%s", root, os.date("%Y%m%d", now));
-- gameid_source目录
local gid_source_path = string.format("%s/%s_%s", datepath, gameid, source);
if not fs.existsSync(datepath) then fs.mkdirSync(datepath) end
if not fs.existsSync(gid_source_path) then fs.mkdirSync(gid_source_path) end
local abs_dir = gid_source_path;
local record = req.ps.record;
for i,v in ipairs(record) do
-- 坐标取整数
local point = {x = math.floor(v.px/100), y = math.floor(v.py/100)};
-- 取不为0的坐标,掐坐标没有意义
if point.x ~= 0 and point.y ~= 0 then
local page = v.page;
local layer = v.layer;
local file;
-- 优先保存到layer
if layer and layer ~= '' then
file = layer;
elseif page and page ~= '' then
file = page;
end
-- 如果有page或者layer存在的情况,点击坐标才有效
if file then
local filepath = string.format("%s/%s.log", abs_dir, file);
-- 缓存的热力图数据
local hmap = openfds[filepath];
-- 没有缓存的热力图数据就去文件读,并保存到缓存中
if not hmap then
local cont = fs.readFileSync(filepath);
hmap = json.decode(cont or '') or {};
openfds[filepath] = hmap;
end
-- heatmap.js需要的数据要有 max字段和data字段
-- max为点击数最大值
-- data为点击坐标及点击数
hmap.max = hmap.max or 1;
hmap.data = hmap.data or {};
local old;
for ii,vv in ipairs(hmap.data) do
-- 碰撞检测,查找point有落入hmap中的任意一个点的范围,就返回这个点
old = checkImpact(vv, point, hmap);
if old then break; end
end
if old then
-- 将point合并到old中,old.value + 1
merge_points(old, point, hmap);
-- 判断最大值,进行对比,hmap.max保持最大值
if hmap.max < old.value then hmap.max = old.value; end
else
-- 如果是新的点,计数一次
point.value = 1;
table.insert(hmap.data, point);
end
end
end
end
-- 保存数据
for filepath,hmap in pairs(openfds) do
fs.writeFileSync(filepath, json.encode(hmap));
end
return {ret = 0};
end
前端数据展示
<!DOCTYPE html>
<!--
* test_heatmap.html 2018-07-18
* Copyright (C) 2018 Chuanpao Su
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
************************************************************************
* FileName: test_heatmap.html
*
* Author: Chuanpao Su
* Version: 1.0
* Description: ----
* Mail: suchuanpao@outlook.com
* Create: 2018-07-18 16:30:18
* Last Modified: 2018-07-18 16:49:38
*
***********************************************************************-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
div {
width:1080;
height:1080px;
border: 1px solid;
border-color: grey;
background-image:url("static/img/page_game_ddz.png");
}
</style>
</head>
<body>
<div id="heatmap"></div>
</body>
<script src="static/js/heatmap.js"></script>
<script type="application/javascript" src="static/js/ai_platform.js"></script>
<script type="text/javascript">
// 创建一个heatmap实例对象
// “h337” 是heatmap.js全局对象的名称.可以使用它来创建热点图实例
// 这里直接指定热点图渲染的div了.heatmap支持自定义的样式方案,网页外包接活具体可看官网api
var heatmapInstance = h337.create({
container: document.querySelector('#heatmap'),
});
requestServer("GET", "/bdata/query_heatmap_data?layer=game_ddz&gameid=3&source=ppb2whmj_oppo&date=20180726", function (res) {
//因为data是一组数据,web切图报价所以直接setData
//console.log(res)
for (var i = 0; i < res.record.data.length; i++) {
res.record.data[i].y = 1080-res.record.data[i].y;
}
heatmapInstance.setData(res.record); //数据绑定还可以使用
})
</script>
</html>