功能说明
html前端页面展示模仿Echarts的雷达图(六维蛛网图),用户在雷达图上点击图上节点改变在六个维度上的权重数据,同时将改变后的结果显示在网页前端,并将数据传回服务器。
前端效果图
技术方案
前端使用html+css+js,绘制雷达图使用canvas,数据处理与前端展示通过js处理,向服务器发送数据使用ajax,服务器后台使用python3.7+Django框架。
代码实现思路
绘制Canvas思路:
1. 绘制5个六边形。先确定这个六边形的边长,通过边长计算出每个顶点的坐标,从而根据坐标来画出图形。每画完一个六边形使用canvas的fill方法填充颜色。通过canvas对透明效果的叠加实现背景颜色的逐层渐变。
2. 绘制从中心点向六个角的发散线段。将六边形对角的三对顶点连接起来形成蛛网。
3. 在每个顶点绘制圆点。以每个顶点为圆心,画一个固定半径的小圆。
4. 添加覆盖物。思路同1,连接六个点后填充颜色即可,初始状态为每个维度上都放在权值为3的点上。
5. 给canvas添加鼠标点击响应事件,通过鼠标的点击,获取鼠标当前点击的坐标,然后遍历5个六边形的所有顶点,通过计算现在点击的坐标与每个顶点之间的距离,判断是否小于等于这个小圆的半径就可以得出用户是否点击了这个小圆,如果用户点击了这个小圆,那么就对整个canvas进行刷新,也可以叫做重绘,把变化后新的覆盖物的形状画上去。
前端展示权重思路:
通过在上述5(鼠标点击响应)中的重绘函数中添加赋值语句,通过id选择器的方式对页面底部被禁用的输入框进行赋值,完成展示。
数据上传思路:
在上述5(鼠标点击响应)中的重绘函数中添加ajax语句,发起向后端的POST请求。在Django中建立响应url及处理函数,完成数据的接收并将数据打印在Terminal窗口中。
具体代码
说明:canvas的高度、宽度、六边形边长和小圆的半径在html文件的开头,便于配置。同时,前端js需要判断浏览器的类型,因为IE和firefox的offset和layer属性需要减去当前标签到浏览器左上角的距离,而谷歌和safari是不需要管这个标签的外部距离。
在Pycharm中新建Django项目。
urls.py文件:
# -*- coding: utf-8 -*-
from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from . import view
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^$', view.show), # 页面展示
url(r'^uploadData$', view.uploadData), # 上传数据接口
]
在urls.py文件同路径下创建view.py文件:
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.shortcuts import render
import json
import os
import time
import re
def show(request):
return render(request, 'show.html')
def uploadData(request):
data = request.POST.get('data')
print(data)
return HttpResponse('Server Received!')
在项目中的templates文件夹下创建show.html文件:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>模仿Echarts雷达图</title>
<script type='text/javascript' src='https://code.jquery.com/jquery-3.1.1.min.js'></script>
</head>
<body style="width: 100%;height: 100%;text-align:center">
<div style="margin: 0 auto">
<script type="text/javascript">
//配置
var height = 800; //canvas的高度
var width = 800; //canvas的宽度
var edgeLength = 300; //六边形的边长
var pointRadius = 5; //小圆的半径
document.write('<canvas id="myCanvas" width="' + width + '" height="' + height + '" style="display: block;margin: 0 auto ;border:1px solid white;">');
</script>
<canvas style="margin: auto">
Your browser does not support the canvas element.
</canvas>
</div>
<div style="margin: 0 auto;display: block;">
{% csrf_token %}
<text>当前六维坐标的数值为(从最上面一个点开始按顺时针方向排序):</text>
<input type="text" value="" id="val" style="margin: 0 auto;" disabled="disabled"/>
</div>
<script type="text/javascript">
//传入canvas的宽度和高度还有六边形的边长,就可以确定一个六边形的六个点的坐标了
function getHexagonPoints(width, height, edgeLength) {
var paramX = edgeLength * Math.sqrt(3) / 2;
var marginLeft = (width - 2 * paramX) / 2;
var x5 = x6 = marginLeft;
var x2 = x3 = marginLeft + paramX * 2;
var x1 = x4 = marginLeft + paramX;
var paramY = edgeLength / 2;
var marginTop = (height - 4 * paramY) / 2;
var y1 = marginTop;
var y2 = y6 = marginTop + paramY;
var y3 = y5 = marginTop + 3 * paramY;
var y4 = marginTop + 4 * paramY;
var points = new Array();
points[0] = [x1, y1];
points[1] = [x2, y2];
points[2] = [x3, y3];
points[3] = [x4, y4];
points[4] = [x5, y5];
points[5] = [x6, y6];
return points;
}
//画六个六边形
function drawHexagon(sixParam) {
for (var i = 0; i < 6; i++) {
allPoints[i] = getHexagonPoints(width, height, sixParam - i * sixParam / 5);
ctx.beginPath();
ctx.fillStyle = "rgba(0,184,255,0.1)";
ctx.moveTo(allPoints[i][5][0], allPoints[i][5][1]); //5
for (var j = 0; j < 6; j++) {
ctx.lineTo(allPoints[i][j][0], allPoints[i][j][1]); //1
}
ctx.stroke();
ctx.closePath();
ctx.fill();
}
}
//画交叉的线
function drawLines() {
ctx.beginPath();
for (var i = 0; i < 3; i++) {
ctx.moveTo(allPoints[0][i][0], allPoints[0][i][1]); //1-4
ctx.lineTo(allPoints[0][i + 3][0], allPoints[0][i + 3][1]); //1-4
ctx.stroke();
}
ctx.closePath();
}
//画覆盖物
function drawCover() {
ctx.beginPath();
ctx.fillStyle = "rgba(104,153,211,0.8)";
ctx.moveTo(coverPoints[5][0], coverPoints[5][1]); //5
for (var j = 0; j < 6; j++) {
ctx.lineTo(coverPoints[j][0], coverPoints[j][1]);
}
ctx.stroke();
ctx.closePath();
ctx.fill();
}
//描点
function drawPoints(pointRadius) {
ctx.fillStyle = "#4284d3";
for (var i = 0; i < 5; i++) {
for (var k = 0; k < 6; k++) {
ctx.beginPath();
ctx.arc(allPoints[i][k][0], allPoints[i][k][1], pointRadius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
}
}
//判断用户点击的位置是否在小圆的范围内
function judgeRange() {
for (var i = 0; i < 5; i++) {
for (var k = 0; k < 6; k++) {
var distance = Math.sqrt((mx - allPoints[i][k][0]) * (mx - allPoints[i][k][0]) + (my - allPoints[i][k][1]) * (my - allPoints[i][k][1]));
if (distance <= pointRadius) {
clickPoints[k] = 5 - i;
//将变化的值显示出来
$('#val').val(clickPoints);
console.log('当前选择的数值为:', clickPoints);
//返回服务器数据
var csrf = $("input[name='csrfmiddlewaretoken']").val();
var formData = new FormData();
formData.append("csrfmiddlewaretoken", csrf);
formData.append('data', clickPoints); /*获取上传的图片对象*/
$.ajax({
url: '/uploadData',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function (args) {
console.log(args);
}
})
//清空
ctx.clearRect(0, 0, width, height);
//重绘
drawHexagon(edgeLength);
drawLines();
//给覆盖物确定变化
coverPoints[k] = allPoints[i][k];
drawCover();
drawPoints(pointRadius);
return;
}
}
}
}
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.strokeStyle = "#6997D3"
var allPoints = [];
var clickPoints = [2, 2, 2, 2, 2, 2];
var mx, my;
drawHexagon(edgeLength);
drawLines();
//初始化覆盖物
var coverPoints = allPoints[3];
drawCover();
drawPoints(pointRadius);
$(function () {
//显示用户选择的权重
$('#val').val(clickPoints);
});
this.mousedown = function (e) {
//判断浏览器的类型,IE和firefox的offset和layer属性需要减去当前标签到浏览器左上角的距离的。
if (isFirefox = navigator.userAgent.indexOf("Firefox") > 0 || navigator.userAgent.indexOf("MSIE") > 0) {
if (e.layerX || e.layerX == 0) {
mx = e.layerX - c.offsetLeft;
my = e.layerY - c.offsetTop;
} else if (e.offsetX || e.offsetX == 0) {
mx = e.offsetX - c.offsetLeft;
my = e.offsetY - c.offsetTop;
}
} else {
if (e.layerX || e.layerX == 0) {
mx = e.layerX;
my = e.layerY;
} else if (e.offsetX || e.offsetX == 0) {
mx = e.offsetX;
my = e.offsetY;
}
}
judgeRange();
};
c.addEventListener('mousedown', this.mousedown, false); //添加鼠标点击监听事件
</script>
</body>
</html>
结果展示
代码下载
整个工程文件:https://download.csdn.net/download/ximerr/11112016
参考教程
1、Django接收前端Ajax发送的POST数据前端调试console中出现403 (Forbidden) 提示,后端Terminal中出现Forbidden (CSRF token missing or incorrect.)报错,解决方案:
django post请求 403错误解决方法:https://www.cnblogs.com/xtt-w/p/6232559.html
django中form表单post无法提交报错403:Forbidden (CSRF token missing or incorrect.):https://www.cnblogs.com/guangang/articles/9242133.html
2、Ajax技术相关:
不刷新页面请求数据(Django+ajax+jquery):https://www.jianshu.com/p/f90ea95a7896
3、Django下的POST 数据请求
Django教程--参数传递(POST):Django教程--参数传递(POST)_django post-CSDN博客
4、 模仿Echarts的雷达图的绘制-发现一个大神的技术博客-Tony的技术空间
用户可选权重的HTML5六维蛛网图的实现(Tony原创):http://www.tonitech.com/1977.html