不多说直接先上结果。
最终效果图(死亡配色哈哈哈,改,全都可以改,点位置、颜色、文字、大小什么都可以改)
(本文内容涵括:Echats多图表生成、图表隐藏、雷达图图表层颜色调整、图表点拖拽实现、雷达图的拖拽样式;用JavaScript读取excel数据、把字符串数据转成数字类型、再两两组合成组)
闲着也是闲着,还不如琢磨提升效率的事情。
前景提要:如何处理一表需要八项分类、4个层级的数据,用可视化图表展示分布情况?用什么图?如何实现?
当前难题与解决思路:
- 没有相关图表——自己定制
- 图表制作当前靠手工制作,一个一个在PPT贴好,费时费力——靠程序脚本生成
- 相关程序生成的分布不美观——对不合适的点拖拽到合适地方,点要能交互
- 同事设计出身,不懂程序操作——程序要简单易用
出于对标签可拖拽的事,一直心心念,也觉得之前那图实在是太太太丑了,还好,偶然发现Echats中有符合心意的功能。自己这边情况的电脑不能安装任何软件,所以HTML与JavaScript这种可以靠在线网站实现的最适合我现在用了。
很好就是这种。可惜没有现成的可用,都是直角坐标系,极坐标系得自己整一个,也没学过JavaScript。不过看起来不难,就直接啃吧。
从官方参考资料知道主要是使用 convertToPixel 这个 API,进行了从数据到“像素坐标”的转换,所以我换一个坐标图无所谓,只要把转换坐标改就行,官方也有考虑到,留着“polar”极坐标系。
改成这样 换成”polar“(我不喜欢dataitem,所以换成item名字)
position: myChart.convertToPixel('polar', item),
拖拽功能有现成的,下一步是生成想要的图,我需要的图标签位置和一般雷达图不一样。得亏Echats功能众多,想了个办法——表层图表+隐藏图表。
先准备一个表层的。
标签再建立一个16分层的,隐藏掉轴线与线段,隔段打入标签名字。
Echats实现多图表方法——Examples - Apache ECharts
实现轴线等隐藏,在配置手册也有。
radar: [
{ //表层雷达图
indicator: [
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
],
//调整大小
radius: ["0%", "95%"],
//调整层数,需要4层
splitNumber : 4,
//是否显示坐标轴轴线
axisLine: {
show: true
},
//分层颜色
splitArea: {
areaStyle: {
color: ['rgba(252, 211, 40, 0.68)', 'rgba(250, 178, 30, 0.67)', 'rgba(245, 138, 9, 0.67)', 'rgba(255, 80, 0, 0.66)']
}
}
},
{ //通过标签的雷达图
indicator: [
{ name: '', max: 4 },
{ name: '系统反馈', max: 4 },
{ name: '', max: 4 },
{ name: '内容需求', max: 4 },
{ name: '', max: 4 },
{ name: '功能需求', max: 4 },
{ name: '', max: 4 },
{ name: '信息架构', max: 4 },
{ name: '', max: 4 },
{ name: '交互设计', max: 4 },
{ name: '', max: 4 },
{ name: '信息设计', max: 4 },
{ name: '', max: 4 },
{ name: '界面设计', max: 4 },
{ name: '', max: 4 },
{ name: '视觉呈现', max: 4 },
],
//调整大小
radius: ["0%", "90%"],
//隐藏轴线
axisLine: {
show: false
},
//隐藏分层
splitArea: {
show: false,
},
//线设置为透明
splitLine: {
lineStyle: {
color: "rgba(255, 255, 255, 0)"//不透明度为0
}
},
//标签字体设置
axisName: {
fontWeight: "bold",
fontSize: 15,
color: "rgba(0, 0, 0, 1)"
}
},
],
表面功夫做好了,准备用一个雷达图只显示点也隐藏掉图。为什么?因为原本以外两个就够了,但是要移动点,雷达图轴必须是”value“类型——radiusAxis的type和angleAxis。否则用类别形式数据点不能自由移动,或者图表会随着点位置变化而变化(所以要加上max和min限制)。用radar自由度不高也难实现,所以用polar生成。
效果:
代码段:
var data = [];
var symbolSize = 20;
var ban = [
'sada', '1', '2', '3', '4', '5', '6','7','8','9','10','11','12','13','14','15','16'
];
var duan = ['1', '2', '3', '4'];
option = {
polar: {radius: ["0%", "90%"]//大小
},
angleAxis: [{
type: 'value',
data: ban,//分16瓣
min: 1,//固定1到17瓣,这样value类型时才不会图表随点变化
max:17,
boundaryGap: false,//坐标轴两边留白策略
axisLine: {
show: false//隐藏线
}}],
radiusAxis: [{
type: 'value',
data: duan,//分4层
max: 4,
min: 0,
axisLine: {
show: false
},
axisLabel: {
rotate: 45//长度坐标文字倾斜角度
}
}],
series: [
{
//name: 'Punch Cardbbb',
id: 'a',
type: 'scatter',
coordinateSystem: 'polar',
symbolSize: symbolSize,//点大小
data: data//你所需要的数据
}]};
把它们合在一起,我再显示出第三层和第一层图表,整个图的底层实现逻辑就是这样的关系。用这种方法实现数据按要求归类,也容易确定相应的坐标数据应该是多少。
雷达图实现。JavaScript也就学了一点基础就直接开敲,可能有些多余的或者格式错误。
代码段
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="" rel="external nofollow" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.2.2/echarts.min.js"></script>
</head>
<body>
<div id="main" style="width: 600px;height:400px;"></div>
<p id="demo"></p>
<script type="text/javascript">
var symbolSize = 10;
var data = [[2,4],[1,2],[2,1],[1,1]];
var ban = ['1', '2','3', '4', '5', '6','7','8','9','10','11','12','13','14','15','16'];
var duan = ['1', '2', '3', '4'];
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
radar: [
{ //表层雷达图
indicator: [
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
],
//调整大小
radius: ["0%", "95%"],
//调整层数,需要4层
splitNumber : 4,
//是否显示坐标轴轴线
axisLine: {
show: true
},
//分层颜色
splitArea: {
areaStyle: {
color: ['rgba(252, 211, 40, 0.68)', 'rgba(250, 178, 30, 0.67)', 'rgba(245, 138, 9, 0.67)', 'rgba(255, 80, 0, 0.66)']
}
}
},
{ //通过标签的雷达图
indicator: [
{ name: '', max: 4 },
{ name: '系统反馈', max: 4 },
{ name: '', max: 4 },
{ name: '内容需求', max: 4 },
{ name: '', max: 4 },
{ name: '功能需求', max: 4 },
{ name: '', max: 4 },
{ name: '信息架构', max: 4 },
{ name: '', max: 4 },
{ name: '交互设计', max: 4 },
{ name: '', max: 4 },
{ name: '信息设计', max: 4 },
{ name: '', max: 4 },
{ name: '界面设计', max: 4 },
{ name: '', max: 4 },
{ name: '视觉呈现', max: 4 },
],
//调整大小
radius: ["0%", "90%"],
//隐藏轴线
axisLine: {
show: false
},
//隐藏分层
splitArea: {
show: false,
},
//线设置为透明
splitLine: {
lineStyle: {
color: "rgba(255, 255, 255, 0)"//不透明度为0
}
},
//标签字体设置
axisName: {
fontWeight: "bold",
fontSize: 15,
color: "rgba(0, 0, 0, 1)"
}
},
],
series: [
{
type: 'radar',
symbolSize : symbolSize,
data: data
}
],
//隐藏的雷达图
polar: {radius: ["0%", "95%"]},
angleAxis: [{
type: 'value',
data: ban,
max: 17,
min: 1,
show:false,
boundaryGap: false,
splitLine: {
show: true
}}
],
radiusAxis: [{
type: 'value',
data: duan,
max: 4,
min: 0,
show:false,
axisLine: {
show: false
},
axisLabel: {
rotate: 45
}
}],
series: [
{
//name: 'Punch Cardbbb',
id: 'a',
type: 'scatter',
show: true,
color:"rgba(254, 253, 253, 0.66)",
coordinateSystem: 'polar',
symbolSize: symbolSize,
data: data
}]
});
myChart.setOption({
graphic: echarts.util.map(data, function (item, dataIndex) {
return {
type: 'circle',
position: myChart.convertToPixel('polar', item),
shape: {
r: symbolSize / 3
},
invisible: true,
draggable: true,
ondrag: echarts.util.curry(onPointDragging, dataIndex),
onmousemove: echarts.util.curry(showTooltip, dataIndex),
onmouseout: echarts.util.curry(hideTooltip, dataIndex),
z: 100
};
})
});
window.addEventListener('resize', function () {
myChart.setOption({
graphic: echarts.util.map(data, function (item, dataIndex) {
return {
position: myChart.convertToPixel('polar', item)
};
})
});
});
function showTooltip(dataIndex) {
myChart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: dataIndex
});
}
function hideTooltip(dataIndex) {
myChart.dispatchAction({
type: 'hideTip'
});
}
function onPointDragging(dataIndex, dx, dy) {
data[dataIndex] = myChart.convertFromPixel('polar', this.position);
myChart.setOption({
series: [{
id: 'a',
data: data
}]
});
}
</script>
</body>
</html>
图搞定了,接下来怎么把excel的数据直接用上让它直接生成数据点呢?原表已经处理好了数据,直接引入就行。(注意,实际用时只留数据就行,”y坐标“、”角坐标“要去掉。因为现在读取表只是简单获取)
参考了别人的
jquery获取excel表格数据 - 简书 (jianshu.com)
原本他是生成json类型,但实在是不好用就改成生成csv类型
// 遍历每张表读取
for (var sheet in workbook.Sheets) {
if (workbook.Sheets.hasOwnProperty(sheet)) {
fromTo = workbook.Sheets[sheet]['!ref'];
console.log(fromTo);
persons = persons.concat(XLSX.utils.sheet_to_csv(workbook.Sheets[sheet]));
break;
}
是为了接下来进行将字符串转换成数字,用parseFloat可以处理浮点数,用parseInt反而把小数点以后去掉了。然后再两个两个打包成一组[1.5,2]这样的。代码实现如下
var shuju =0;
$('#excel-file').change(function(e) {
var files = e.target.files;
var fileReader = new FileReader();
var s = [];
fileReader.onload = function(ev) {
try {
var data = ev.target.result,
workbook = XLSX.read(data, {
type: 'binary'
}), // 以二进制流方式读取得到整份excel表格对象
persons = []; // 存储获取到的数据
} catch (e) {
console.log('文件类型不正确');
return;
}
// 表格的表格范围,可用于判断表头是否数量是否正确
var fromTo = '';
// 遍历每张表读取
for (var sheet in workbook.Sheets) {
if (workbook.Sheets.hasOwnProperty(sheet)) {
fromTo = workbook.Sheets[sheet]['!ref'];
console.log(fromTo);
persons = persons.concat(XLSX.utils.sheet_to_csv(workbook.Sheets[sheet]));
break; // 如果只取第一张表,就取消注释这行
}
}
var w = [];
var a = [];
s = persons.toString().split("\n");
s.pop();//莫名其妙数据没少情况下最后多了一个NaN,删掉
//console.log(s);//如果有问题可以取消这里注释看生成了什么
w = s.toString().split(",");
//console.log(w);//如果有问题可以取消这里注释看生成了什么
//变成数字
var turnNum = function(nums){
for(let i=0;i<nums.length;i++){
nums[i] = parseFloat(nums[i])
}
return nums;
}
a = turnNum(w)
//console.log(a);//如果有问题可以取消这里注释看生成了什么
//变成两个一组
var b = [];
var result = [];
var k = 0;
for(var i = 0; i<a.length; ++i){
if(i%2 == 0){
b = [];
for(var j = 0; j<2; ++j){
if(a[i+j] == undefined){
continue;
} else{
b[j] = a[i+j];
}
}
result[k] = b;
k++;
shuju = result;
}
}
//console.log(result); //如果有问题可以取消这里注释看生成了什么
最终生成的result:
最终代码合并一下就是
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
<script src="https://cdn.bootcss.com/xlsx/0.11.5/xlsx.core.min.js"></script>
<script src="" rel="external nofollow" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.2.2/echarts.min.js"></script>
</head>
<body>
<input type="file" id="excel-file">
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
var shuju =0;
$('#excel-file').change(function(e) {
var files = e.target.files;
var fileReader = new FileReader();
var s = [];
fileReader.onload = function(ev) {
try {
var data = ev.target.result,
workbook = XLSX.read(data, {
type: 'binary'
}), // 以二进制流方式读取得到整份excel表格对象
persons = []; // 存储获取到的数据
} catch (e) {
console.log('文件类型不正确');
return;
}
// 表格的表格范围,可用于判断表头是否数量是否正确
var fromTo = '';
// 遍历每张表读取
for (var sheet in workbook.Sheets) {
if (workbook.Sheets.hasOwnProperty(sheet)) {
fromTo = workbook.Sheets[sheet]['!ref'];
console.log(fromTo);
persons = persons.concat(XLSX.utils.sheet_to_csv(workbook.Sheets[sheet]));
break; // 如果只取第一张表,就取消注释这行
}
}
var w = [];
var a = [];
s = persons.toString().split("\n");
s.pop();//莫名其妙数据没少情况下最后多了一个NaN,删掉
//console.log(s);//什么情况可随时取消注释调出来看
w = s.toString().split(",");
//console.log(w);//如果有问题可以取消这里注释看生成了什么
//变成数字
var turnNum = function(nums){
for(let i=0;i<nums.length;i++){
nums[i] = parseFloat(nums[i])
}
return nums;
}
a = turnNum(w)
//console.log(a);//如果有问题可以取消这里注释看生成了什么
//变成组
var b = [];
var result = [];
var k = 0;
for(var i = 0; i<a.length; ++i){
if(i%2 == 0){
b = [];
for(var j = 0; j<2; ++j){
if(a[i+j] == undefined){
continue;
} else{
b[j] = a[i+j];
}
}
result[k] = b;
k++;
shuju = result;//结果赋值给shuju变量,给下面用
}
}
//console.log(result); //如果有问题可以取消这里注释看生成了什么
//图表生成段
var symbolSize = 10;//点大小,虽然没用但是不能没有
var data = shuju;//接收到要呈现的数据
var data1 = [[1,1],[2,2]];//随便给一个
var ban = ['1', '2','3', '4', '5', '6','7','8','9','10','11','12','13','14','15','16'];//隐藏层分16瓣
var duan = ['1', '2', '3', '4'];
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
//表面的雷达图
radar: [
{
indicator: [
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 },
{ name: '', max: 4 }
],
//调整大小
radius: ["0%", "95%"],
//调整层数,需要4层
splitNumber : 4,
//分层颜色
splitArea: {
areaStyle: {
color: ['rgba(252, 211, 40, 0.68)', 'rgba(250, 178, 30, 0.67)', 'rgba(245, 138, 9, 0.67)', 'rgba(255, 80, 0, 0.66)']
}
}
},
{//标签的雷达图
indicator: [
{ name: '', max: 4 },
{ name: '系统反馈', max: 4 },
{ name: '', max: 4 },
{ name: '内容需求', max: 4 },
{ name: '', max: 4 },
{ name: '功能需求', max: 4 },
{ name: '', max: 4 },
{ name: '信息架构', max: 4 },
{ name: '', max: 4 },
{ name: '交互设计', max: 4 },
{ name: '', max: 4 },
{ name: '信息设计', max: 4 },
{ name: '', max: 4 },
{ name: '界面设计', max: 4 },
{ name: '', max: 4 },
{ name: '视觉呈现', max: 4 },
],
//调整大小
radius: ["0%", "90%"],
//隐藏轴线
axisLine: {
show: false
},
//隐藏分层
splitArea: {
show: false,
},
//线设置为透明
splitLine: {
lineStyle: {
color: "rgba(255, 255, 255, 0)"
}
},
//标签字体设置
axisName: {
fontWeight: "bold",
fontSize: 15,
color: "rgba(0, 0, 0, 1)"
}
},
],
series: [
{
type: 'radar',
symbolSize : symbolSize,//这些现在没什么用但是留着是为了随时调整
data: data1
}
],
//隐藏的雷达图
polar: {radius: ["0%", "95%"],},
angleAxis: [{
type: 'value',
data: ban,//分16瓣
max: 17,//固定1到17瓣,这样value类型时才不会图表随点变化
min: 1,
show:false,//隐藏,需要调整时再看到取消注释就行
boundaryGap: false,//坐标轴两边留白策略
splitLine: {
show: true//虽然现在看不到,但到时候需要调整
}}
],
radiusAxis: [{
type: 'value',
data: duan,
max: 4,//分4层
min: 0,
show:false,//隐藏,需要调整时再看到取消注释就行
axisLine: {
show: false//同上,隐藏线
},
axisLabel: {
rotate: 45//长度坐标文字倾斜角度
}
}],
series: [
{
id: 'a',//非常重要,id为锁定用交互的点,注意与下面一致
type: 'scatter',
//show: true,
color:"rgba(93, 153, 249, 0.80)",//点颜色调整
coordinateSystem: 'polar',
symbolSize: symbolSize,//点大小
data: data
}]
});
myChart.setOption({
graphic: echarts.util.map(data, function (item, dataIndex) {
return {
type: 'circle',
position: myChart.convertToPixel('polar', item),
shape: {
r: symbolSize / 2//覆盖在点上的交互点大小
},
//invisible: false,//让点可见,方便随时调整
invisible: true,//交互点是否可见,现在是不可见
draggable: true,
ondrag: echarts.util.curry(onPointDragging, dataIndex),
onmousemove: echarts.util.curry(showTooltip, dataIndex),
onmouseout: echarts.util.curry(hideTooltip, dataIndex),
z: 100
};
})
});
window.addEventListener('resize', function () {
myChart.setOption({
graphic: echarts.util.map(data, function (item, dataIndex) {
return {
position: myChart.convertToPixel('polar', item)
};
})
});
});
function showTooltip(dataIndex) {
myChart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: dataIndex
});
}
function hideTooltip(dataIndex) {
myChart.dispatchAction({
type: 'hideTip'
});
}
function onPointDragging(dataIndex, dx, dy) {
data[dataIndex] = myChart.convertFromPixel('polar', this.position);
myChart.setOption({
series: [{
id: 'a',
data: data
}]
});
}
};
// 以二进制方式打开文件
fileReader.readAsBinaryString(files[0]);
});
</script>
</body>
</html>
最终样式,然后就是慢慢一个一个挪了/(ㄒoㄒ)/~~比起之前的python做的图,在HTML用JavaScript的Echats有更多功能和可实现的地方,交互功能比python更容易达到。现在想想之前随机排实在是太丑了。后面再想办法把python的随机加进来和对数据表excel的判断加进来吧。
其实写的时候遇到许多问题,例如为什么点不能交互?数据为什么传递不过来?但现在是完成了才开始写稿,所以有些过程忘记了许多。HTML也就借个框,剩下都是JavaScript完成。