数据可视化 —— 小练习1 KMeans聚类并数据可视化图像像素点

数据可视化Task1

任务描述:对图片RGB点进行Kmeans聚类,并将结果数据可视化于前端浏览器上

实验平台

  1. Visual Studio Code
  2. HTML/CSS/Javascript
  3. Edge/Chorme/Firefox 浏览器
  4. Echart.min.js 3.8.5版 (过高的版本浏览器无法include,只能使用低版本的echart)

实验目的:制作出可交互的前端框架,使用者上传图片后,可获得图片数据的KMeans聚类可视化信息:如散点分类,中心点。

实验操作过程

  1. Visual Studio Code安装好 Preview on Web Server配件
  2. VSCode中对HTML文件右键,并点击vscode-preview-server: Launch on browser

在这里插入图片描述

  1. 可以对文件进行上传,后续下方会出现 “按钮”与“文本框”

在这里插入图片描述

  1. 输入2-10的数据后,点击生成KMeans图,可以获取三个可视化图片,若输入数字错误,会有错误提示

在这里插入图片描述

  1. 得到响应柱状图数据

在这里插入图片描述

  1. 并且可以得到相应的散点图分析,指向中心点的时候,还可以得到中心点数据

在这里插入图片描述

搭建前端框架

前端框架部分大致如下

head部分
作用主要为引用在线库

<head>
		<meta charset="utf-8">
		<title>lab_01</title>
		<script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.min.js"></script>   
		<script src="http://echarts.baidu.com/resource/echarts-gl-latest/dist/echarts-gl.min.js"></script> 
		<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
		<script type="module" src="./main.js" src > </script>
	</head>

style部分用于进行部分按钮的隐藏

<style>
		div
		{
			/* border:1px solid #c3c3c3; */
			position: relative;
			left : 70px;
			top : 120px;
		}
		.hidden
		{ 
			visibility:hidden;
		}
		.showChart
		{
			height: 500px;
			width: 800px ;
			border: 1px solid #333333;  
			position: absolute; 
			left : 800px;
			top : 0px;
		};
</style>

主体部分

<body>
		
		<div>
			
			<canvas class = '' id = "myCanvas" style="border:1px solid #c3c3c3; height : 500px; width : 700px"			>
				Your browser does not support the canvas element.
			</canvas>

			<canvas class="showChart" id="showTable" style="height: 500px;
			width: 700px ;">
			</canvas>

			<br>
			<input type="file" id= "myFile" style = "position: relative;"/>
			<br><br>
			<input type = 'submit' id = 'KMeans' value = '生成KMeans图'	class = 'hidden'/>
			<input type="text" id = 'KMeansNum' class = 'hidden' width = '300px' value= "2" />
			<br><br>
			<input type = 'button' id = 'barPlot' value = "柱状图" class = 'hidden'/>
			<input type = 'button' id = 'piePlot' value = "玫瑰饼状图" class = 'hidden'/>
			<input type = 'button' id = 'scatterPlotKMeans' value = "聚类散点图" class = 'hidden'/>
		</div>
		
	</body>

按钮响应事件

var myFile = document.getElementById('myFile');//获取对应的文件按钮元素
myFile.onchange = function (event){ //点击按钮相应的事件
	// what to do in the function
}

详情请看main.js文件

Kmeans聚类的实现

核心代码

var cnt = 0;
do{
	for(var i = 0; i < pixelArray.length; i++)
	{
		//对每个点进行遍历,看是否需要归到新类
		var min_dis = Infinity;
		var which_class = -1;
		var dR = 0, dG = 0, dB = 0;
		for(var j = 0; j < numOfClass; j++)
		{
			//使用RGB对应的欧氏空间距离
			//console.log(i, j);
			dR = pixelArray[i][0] - center_point[j][0];
			dG = pixelArray[i][1] - center_point[j][1];
			dB = pixelArray[i][2] - center_point[j][2];
			var dis = Math.pow(dR*dR + dB*dB + dG*dG, 0.5)
			//这里不用pow其实也是可以的
			if(dis < min_dis)
			{
				min_dis = dis;
				which_class = j;
			}
		}
		//遍历所有类之后更新节点
		class_of_point[i] = which_class;
		//记录每个点的新类,为后续更新铺垫
		eachClass[which_class][0] += pixelArray[i][0];
		eachClass[which_class][1] += pixelArray[i][1];
		eachClass[which_class][2] += pixelArray[i][2];
		eachClass[which_class][3] ++;
	}

	//对每个类进行中心点更新
	for(var j = 0; j < center_point.length; j++)
	{   
		var r = eachClass[j][0] / eachClass[j][3];
		var g = eachClass[j][1] / eachClass[j][3];
		var b = eachClass[j][2] / eachClass[j][3];
		center_point[j] = [r, g, b];
	}
	var cnt = 0;//记录变动幅度
	for(var i = 0; i < class_of_point.length; i++)
	{
		if(cmp_array[i] != class_of_point[i])
		{
			cnt++;
		}
	}
	//console.log(1);
	
	var condition = class_of_point.length / 1000;
	if(condition < 5)
	{
		condition = 5;
	}    
}while(cnt > condition)

源码和文件分布

在这里插入图片描述

front_part.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>lab_01</title>
		
		<script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.min.js"></script>   
		<script src="http://echarts.baidu.com/resource/echarts-gl-latest/dist/echarts-gl.min.js"></script> 
		<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
		<script type="module" src="./main.js" src > </script>
		
	</head>
	
	<style>
		div
		{
			/* border:1px solid #c3c3c3; */
			position: relative;
			left : 70px;
			top : 120px;
			
		}
		.hidden
		{ 
			
			visibility:hidden;
		}
		.showChart
		{
			height: 500px;
			width: 800px ;
			border: 1px solid #333333;  
			position: absolute; 
			left : 800px;
			top : 0px;
		};

	</style>
	
	<body>
		
		<div>
			
			<canvas class = '' id = "myCanvas" style="border:1px solid #c3c3c3; height : 500px; width : 700px"
				
			>
				Your browser does not support the canvas element.
			</canvas>

			<canvas class="showChart" id="showTable" style="height: 500px;
			width: 700px ;">
			</canvas>

			<br>
			<input type="file" id= "myFile" style = "position: relative;"/>
			<br><br>
			
			<input 
				type = 'submit' id = 'KMeans' value = '生成KMeans图'
				class = 'hidden'
				/>

				<!-- 这里input模块里面 style一次 只能赋值一个,不然无法点击,就很奇怪-->
			<input type="text" id = 'KMeansNum' class = 'hidden'
				width = '300px'
				value= "请输入2-10的数字" />
				<!-- "请输入2-10的数字" -->
			
			<br><br>
			
			<input 
				type = 'button' id = 'barPlot' value = "柱状图" class = 'hidden'
				/>
			<input 
				type = 'button' id = 'piePlot' value = "玫瑰饼状图" class = 'hidden'
				/>
			<input 
				type = 'button' id = 'scatterPlotKMeans' value = "聚类散点图" class = 'hidden'
				/>
		
		</div>
		
	</body>
</html>

main.js


import * as KMeans from './include/KMeans.js';;
import * as CC from './include/CreateCharts.js';
// import * as echarts from 'https://cdn.bootcdn.net/ajax/libs/echarts/5.4.2/echarts.min.js';
// import * as echarts_gl from 'https://cdn.bootcdn.net/ajax/libs/echarts-gl/2.0.8/echarts-gl.min.js';



var myCanvas = document.getElementById('myCanvas');
var myFile = document.getElementById('myFile');
var myKMeans = document.getElementById('KMeans');
var myKMeansNum = document.getElementById('KMeansNum');

//var myOriginalPicture = document.getElementById('originalPicture');
var myBarPlotBotton = document.getElementById('barPlot');
var myPiePlotBotton = document.getElementById('piePlot');
var myScatterPlotBottonKMeans = document.getElementById('scatterPlotKMeans');
var pixelArray = [];

var myShowTable = document.getElementById('showTable');

// console.log(myCanvas);
// var myChart = echarts.init(myCanvas);
/*  
    onchange事件,用于监视myFile这个变量
    处理前端输入数据部分 
    本来想把function打包放入别的include内部,然后调用外部函数
    但是查了很多资料发现应该是不可以的
*/

var to_cmp_class_num;


myFile.onchange = function (event)
{
	var selectedFile = event.target.files[0];//files属性是一个描述用户已选文件信息的数组,未开多选模式的话只有一个元素
	//console.log(selectedFile, "File.onchange\n");
	
	var reader = new FileReader(); //创建了一个FileReader对象,存于reader
	reader.readAsDataURL(selectedFile);// 文件内容读取成Data URL
	// 把隐藏的按钮显示出来
	myKMeans.style.visibility = 'visible';
    myKMeansNum.style.visibility = 'visible';
	
	reader.onload = function putImageToCanvas(event)
	{
		 var img = new Image();//图片对象
		 img.src = event.target.result;// reader.onload的传递
		 img.onload = function()
		 {
			//console.log(img.scr, "\n");
			//与lab02课堂实验部分无较大差异 canvas的尺寸设置成图像的尺寸
			myCanvas.width = Math.min(700, img.width);
			myCanvas.height = Math.min(500, img.height);
			//console.log(img.width * img.height);
			var context = myCanvas.getContext('2d');
			context.drawImage(img, 0, 0);
			var imgdata = context.getImageData(0, 0, img.width, img.height);//getImageData获取图像像素数据供JavaScript处理
		 
			var len = imgdata.data.length;
			var cnt = 0;
			for(var i = 0; i < len; i += 4)
			{
				var tempArray = [ imgdata.data[i], imgdata.data[i + 1], imgdata.data[i + 2] ];
				pixelArray[cnt++] = tempArray;
			}
			//console.log(pixelArray[0]);
		 }
	}
    return;
}//

var K_ARRAY;//用来存两个数组,一个是每个点对应的类,第二个是每个类的中心点
// 获取Kmeans的参数数量之后实现Kmeans聚类,顺便再产生对应的按钮来切换图片


var forBarChart = [];

myKMeans.onclick = function(event)
{
	//console.log("myKMeans.onchange调用成功");
	var numOfClass = myKMeansNum.value;
	
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
	}
	else 
	{
		// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
		// {
		// 	CC.barChart(myShowTable, forBarChart);
		// 	return;
		// }
		//else
		{
			to_cmp_class_num = numOfClass;
			forBarChart = [];

			//myOriginalPicture.style.visibility = 'visible';
			myBarPlotBotton.style.visibility = 'visible';
			myScatterPlotBottonKMeans.style.visibility = 'visible';
			myPiePlotBotton.style.visibility = 'visible';

			//要把初始图像展现出来
	
			// 实现K means聚类 
				//KMeans_array = KMeans.KMeans()
				
				
			// pixelArray.sort();
			
			K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
			
			for(var i = 0; i < numOfClass; i++)
			{
				forBarChart[i] = 0;
			}
			for(var i = 0; i < K_ARRAY.category.length; i++)
			{
				forBarChart[K_ARRAY.category[i]] += 1;
			}	
			//console.log(forBarChart); 
			forBarChart.sort(function(a, b){return a-b});	
			//console.log(forBarChart); 
			
			//将K means聚类结果转换为可视化图片
			//初始散点
	
			//这里先尝试画出来柱状图
			var newArr = JSON.parse(JSON.stringify(forBarChart));
			CC.barChart(myShowTable, newArr)
			//CC.scatterChart(myShowTable);
		}
		
	}
}


myBarPlotBotton.onclick = function(numOfClass)
{
	var numOfClass = myKMeansNum.value;
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
		return;
	}
	
	//console.log("problem 1")
	// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
	// {
	// 	var newArr = JSON.parse(JSON.stringify(forBarChart));
	// 	CC.barChart(myShowTable, newArr)
	// 	return;
	// }
	// else 
	{
		//console.log("problem 3")
		to_cmp_class_num = numOfClass;
		forBarChart = [];

		K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
		for(var i = 0; i < numOfClass; i++)
		{
			forBarChart[i] = 0;
		}
		for(var i = 0; i < K_ARRAY.category.length; i++)
		{
			forBarChart[K_ARRAY.category[i]] += 1;
		}	
		forBarChart.sort(function(a, b){return a-b});	
		var newArr = JSON.parse(JSON.stringify(forBarChart));
		CC.barChart(myShowTable, newArr)
	}
	
}

myPiePlotBotton.onclick = function(numOfClass)
{
	var numOfClass = myKMeansNum.value;
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
		return;
	}
	
	//console.log("problem 1")
	// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
	// {
	// 	var newArr = JSON.parse(JSON.stringify(forBarChart));
	// 	CC.barChart(myShowTable, newArr)
	// 	return;
	// }
	// else 
	{
		//console.log("problem 3")
		to_cmp_class_num = numOfClass;
		forBarChart = [];

		K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
		for(var i = 0; i < numOfClass; i++)
		{
			forBarChart[i] = 0;
		}
		for(var i = 0; i < K_ARRAY.category.length; i++)
		{
			forBarChart[K_ARRAY.category[i]] += 1;
		}	
		forBarChart.sort(function(a, b){return a-b});	
		var newArr = JSON.parse(JSON.stringify(forBarChart));
		CC.pieChart(myShowTable, newArr)
	}
	
}


myScatterPlotBottonKMeans.onclick = function()
{
	//console.log("test, myscatter plot bottom Kmeans")
	var numOfClass = myKMeansNum.value;
	forBarChart = [];
	
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
		return;
	}


	// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
	// {
	// 	CC.scatterChart(myShowTable, pixelArray, K_ARRAY.category, K_ARRAY.mean_point);
	// 	return;
	// }
	// else 
	{
		to_cmp_class_num = numOfClass;
		K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
		CC.scatterChart(myShowTable, pixelArray, K_ARRAY.category, K_ARRAY.mean_point);
	}
}

// 剩下的几个按钮用于切换不同的图片

CreateCharts.js

//import * as echarts from './forBar/echarts.js'


// export const test = function(variable)
// {
//     console.log("CC", variable);
//     return variable;
// }

const color_for_scatter = [
    '#9943ff',  '#ff7739', '#82ff1c',
    '#cf1e18', '#1f13f8',  '#11f9d2',
    '#bc1fff', '#7f1c10', '#ffde39', '#8f9f5e',
    
]

// const color_for_scatter = [
//     '#63E398', '#934b43'
// ]

const color_for_bar = [
    '#1881b6', '#87a2b4', '#557073',
    '#154a72', '#a57db7',
    '#68abcc', '#caacc4',
    '#9b8fa7', '#7c537f',  '#607b8f'
]

export const barChart = function(myCanvas, forBarChart)
{
    var dom = myCanvas;  //document.getElementById('container');
    var myChart
    if(typeof myChart == 'undefined')
    {
        myChart = echarts.init(
            dom
        );
    }
    myChart.clear();
     
    myChart.resize(
        {
            width: this.width,
            height: this.height
        }
    )
    var app = {};
    
    var option;

    var class_index = []; // 横坐标 xAxis
    for(var i = 0; i < forBarChart.length; i++)
    {
        class_index[i] = i;
        // class_index[i] = 
        // {
        //     value : i,
        // }
    }
    for(var i = 0; i < forBarChart.length; i++)
    {
        var tmp = forBarChart[i];
        forBarChart[i] = 
        {
            value : tmp,
            itemStyle :
            {
                color : color_for_bar[i]
            }

        }
    }

    
    option = {

        grid : {
            left: '3%',
            right: '4%',
            bottom: '3%',
            containLabel:true
        },
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            }
        },
        xAxis: [{
            type: 'category',
            data: class_index,
            show:true,
            // axisTick: {
            //     alignWithLabel: true
            // },
            // axisLabel:{
            //     interval:(index,value) =>{return false}//设置X轴数据不显示
            // }
        }],
        yAxis: [{
            type: 'value',
            show:true
        }],
        grid:{
            containLabel:true
        },
        
        series: [{
            name: "Total",
            data: forBarChart,
            type: 'bar',
            barWidth: '50%',
            // emphasis要卸载itemstyle里面
            //我麻了
            itemStyle:{
                normal:{
                    color:function(para){
                        return color_for_bar[para.dataIndex % color_for_bar.length];
                    },
                    label : {
                        show:false
                    }
                },
                emphasis: {
                    label : {
                        show : true
                    }
                },
            },
        }]


    };

    if (option && typeof option === 'object') 
    {
        myChart.setOption(option);
    }

    window.addEventListener('resize', myChart.resize);
}

export const pieChart = function(myCanvas, forBarChart)
{
    var dom = myCanvas;  //document.getElementById('container');
    var myChart
    if(typeof myChart == 'undefined')
    {
        myChart = echarts.init(
            dom, 
            null, 
            {
                renderer: 'canvas',
                useDirtyRect: false
            }
        );
    }
    myChart.clear();
     
    myChart.resize(
        {
            width: this.width,
            height: this.height
        }
    )
    
    var option;
    var class_index = []; // 横坐标 xAxis
    for(var i = 0; i < forBarChart.length; i++)
    {
        class_index[i] = 
        {
            value : i,
        }
    }
    for(var i = 0; i < forBarChart.length; i++)
    {
        var tmp = forBarChart[i];
        forBarChart[i] = 
        {
            value : tmp,
            name : "category " + i,
            // itemStyle :
            // {
            //     color : color_for_bar[i]
            // }
        }
    }

    
    option = {
        tooltip: {
          trigger: 'item'
        },
        legend: {
          top: '5%',
          left: 'center'
        },
        series: [
          {
            name: 'Access From',
            type: 'pie',
            radius: ['40%', '70%'],
            avoidLabelOverlap: false,
            itemStyle: {
              borderRadius: 10,
              borderColor: '#fff',
              borderWidth: 2
            },
            label: {
              show: false,
              position: 'center'
            },
            emphasis: {
              label: {
                show: true,
                fontSize: 40,
                fontWeight: 'bold'
              }
            },
            labelLine: {
              show: false
            },
            data: forBarChart
            // [
            //   { value: 1048, name: 'Search Engine' },
            //   { value: 735, name: 'Direct' },
            //   { value: 580, name: 'Email' },
            //   { value: 484, name: 'Union Ads' },
            //   { value: 300, name: 'Video Ads' }
            // ]
            ,
            itemStyle:{
                normal:{
                    label : {
                        show:true
                    }
                },
                emphasis: {
                    label : {
                        show : true,
                        //color :  'rgba(0,0,0,1)',
                        formatter : function(para){
                            // console.log(para);
                            // console.log(para.data);

                            return para.data.value;
                        }
                    }
                },
            },
          }
        ]
      };

    if (option && typeof option === 'object') 
    {
        myChart.setOption(option);
    }

    window.addEventListener('resize', myChart.resize);
}

// addChart ();
// $("#btn").on("click", function () {
//     $("#main").remove();
//     var newM = $("<div id='main' style='width: 100%;height:800px; '></div>");
//     newM.appendTo($("body"));
//     addChart();
// });

function duplicate_for_array(pixelArray, category)
{
    let map = new Map();
    let after_duplicate = new Array();  // 数组用于返回结果
    let after_category = new Array()
    for (let i = 0; i < pixelArray.length; i++) {
        var duplicate = pixelArray[i][0] + ' ' + pixelArray[i][1] + ' ' + pixelArray[i][2];

        if (map.has(duplicate)) {  // 如果有该key值
            map.set(duplicate, true);
        } else {
            map.set(duplicate, false); // 如果没有该key值
            after_duplicate.push(pixelArray[i]);
            after_category.push(category[i]);
        }
    }

    // 获得去重的点和数据

    return {
        point : after_duplicate,
        category : after_category
    };
}

function hexToRgba(hex, opacity) {
    return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
            + parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
}

export const scatterChart = function (myCanvas, pixelArray, category, mean_point) {
    var dom = myCanvas; //echarts.init(document.getElementById('myCan'));
   
    var myChart
    if(typeof myChart == 'undefined')
    {
        myChart = echarts.init(
            dom
        );
    }
    myChart.clear();

    myChart.resize(
        {
            width: this.width,
            height: this.height,
            top: this.top,
            left : this.left
        }
    )
    //console.log(myChart);
    // var data = [
    //     [12, 23, 43],
    //     [43, 545, 65],
    //     [92, 23, 33]
    // ];

    //data = pixelArray;
    // 去重
    var after_duplicate = duplicate_for_array(pixelArray, category);
    console.log(after_duplicate.point.length);
    console.log(after_duplicate.category.length);
    
    
    
    //改data,每个class是一类点
    var test_array = []
    for(var i = 0; i < mean_point.length; i++)
    {
        test_array.push([]);
        test_array[i].push(mean_point);
    }

    for(var i = 0; i < after_duplicate.point.length; i++)
    {
        var index = after_duplicate.category[i];
        test_array[index].push(after_duplicate.point[i]);
    }
    console.log(test_array[0].length, test_array[0][0]);
    
    
    //将中心点和去重后的散点分给对应的类
    var data = [] 
    // for(var i = 0; i < 10; i++)
    // {
    //     data.push([]);
    // }
    // for(var i = 0; i < mean_point.length; i++)
    // {
    //     //中心点固定放第一位
    //     data[i].push(mean_point[i]);
    //     console.log(data[i]);
    // }
    // for(var i = 0; i < after_duplicate.point.length; i++)
    // {
    //     var index = after_duplicate.category[i]; //第i个点在哪个类
    //     data[index].push(after_duplicate.point[i]);
    // }
    //console.log(data);

    data[0] = after_duplicate.point;
    data[1] = mean_point;



    myChart.setOption({
        tooltip: {},
        xAxis3D: {
            name: "R",
            type: 'value'
        },

        yAxis3D: {
            name: "G",
            type: 'value',
              scale: true
        },

        zAxis3D: {
            name: "B",
            type: 'value',
        },

        grid3D: {
            // left: 10,
            // containLabel: true,
            // bottom: 10,
            // top: 10,
            // right: 30,
            axisLine: {
                lineStyle: {
                    color: '#000'//轴线颜色
                }
            },
            axisPointer: {
                lineStyle: {
                    color: '#f00'//坐标轴指示线
                },
                show: false//不坐标轴指示线
            },
            viewControl: {
                autoRotate: true,//旋转展示
                autoRotateAfterStill:0.5,//在鼠标静止操作后恢复自动旋转的时间间隔。在开启 autoRotate 后有效
                projection: 'orthographic',
                beta: 10,
                distance: 500
                
            },
            boxWidth: 200,
            boxHeight: 100,
            boxDepth: 200,
            // top: -100
        },

        series: [
            
            {
                type: 'scatter3D',

                data: data[0],//test_array[0],
                symbolSize : function(arr, para)
                {
                    var index = arr.length;
                    return 1.5;
                }
                ,
                
                emphasis: {
                    itemStyle: 
                    {
                        color : 'rgba(0, 0, 0, 1)',
                    },
                    label : {
                        show : true,
                        formatter:function(para){
                            return (para.data[0]) + ' ' 
                                + (para.data[1]) + ' ' + (para.data[2]);
                        }
                    }
                },


                itemStyle: {
                    borderWidth: 0.1,
                    borderColor: 'rgba(255,255,255,0.8)'//边框样式
                },
                itemStyle: {
                    color: function(para){
                        var index = para.dataIndex;
                        var color_index = after_duplicate.category[index];
                        var color = color_for_scatter[color_index];
                        var opacity = 0.3;

                        var rgba = hexToRgba(color, opacity);
                        return rgba;
                        }
                    },
                
            },
            {
                type: 'scatter3D',

                data: data[1],//平均点
                symbolSize : 50,
                
                emphasis: {
                    itemStyle: 
                    {
                        color : 'rgba(0, 0, 0, 1)',
                    },
                    label : {
                        show : true,
                        formatter:function(para){
                            return (para.data[0]).toFixed(2) + ' ' 
                                + (para.data[1]).toFixed(2) + ' ' + (para.data[2]).toFixed(2);
                        }
                    }
                },

                itemStyle: {
                    borderWidth: 0.1,
                    borderColor: 'rgba(255,255,255,0.8)'//边框样式
                },
                itemStyle: {
                    color: function(para){
                        //var index = para.dataIndex;
                        var color = color_for_scatter[para.dataIndex];
                        var opacity = 1;

                        var rgba = hexToRgba(color, opacity);//转换为rgba
                        return rgba;
                        }
                    }
            },
    
    ],

        backgroundColor: { //设置渐变
            type: 'radial',
            x: 0.3,
            y: 0.3,
            r: 2.0,
            colorStops: [
                {
                    offset: 0,
                    color: '#f7f8fa'
                },
                {
                    offset: 1,
                    color: '#cdd0d5'
                }
            ]
        },
        //backgroundColor: "#f0f0f0"
    });
}

KMeans.js





// export const test = function ()
// {
// 	console.log("??");
// }

export const test = function(variable)
{
    console.log("KMeans", variable);
    return variable;
}

export const KMeans = function(pixelArray , numOfClass = 2)
{
    if(typeof(a) == "undefine")
    {
        alert("The pixel array is undefine")
        return []
    }
    else if(!Array.isArray(pixelArray))
    {
        alert("The pixel array is not an array")
        return [];
    }
    else if(numOfClass < 2 || numOfClass > 10)
    {
        alert("The number of classes should be between 2 and 10");
        return [];
    }
    else if(numOfClass > pixelArray.length)
    {
        alert("The number of uploaded image points should be greater than the number of clusters");
        return [];
    }

    var class_of_point = [];//表明对应的点在哪个类
    var center_point = [];//每个类的中心点
    var cmp_array = class_of_point;//用于Kmeans判断点聚类是否进行更新

    for(var i = 0; i < numOfClass; i++)
    {
        var dis = (pixelArray.length - 1) / (numOfClass - 1); //间隔
        var first_point = Math.floor(dis * i); //直接平均分数组取各个平均点作为初始点
        
        center_point.push(pixelArray[first_point]);
        class_of_point[first_point] = i;
        //
    }
    

    var cnt = 0;
    do{
        cmp_array = JSON.parse(JSON.stringify(class_of_point));
        //要用深拷贝,浅拷贝是直接引用
        var eachClass = [];
        for(var j = 0; j < numOfClass; j++)
        {   
            eachClass[j] = [0,0,0,0];//记录R G B 以及对应的类有多少个点
        }

        for(var i = 0; i < pixelArray.length; i++)
        {
            //对每个点进行遍历,看是否需要归到新类
            var min_dis = Infinity;
            var which_class = -1;
            var dR = 0, dG = 0, dB = 0;
            for(var j = 0; j < numOfClass; j++)
            {
                //使用RGB对应的欧氏空间距离
                //console.log(i, j);
                dR = pixelArray[i][0] - center_point[j][0];
                dG = pixelArray[i][1] - center_point[j][1];
                dB = pixelArray[i][2] - center_point[j][2];
                var dis = Math.pow(dR*dR + dB*dB + dG*dG, 0.5)
                //这里不用pow其实也是可以的
                if(dis < min_dis)
                {
                    min_dis = dis;
                    which_class = j;
                }
            }
            //遍历所有类之后更新节点
            class_of_point[i] = which_class;
            //记录每个点的新类,为后续更新铺垫
            eachClass[which_class][0] += pixelArray[i][0];
            eachClass[which_class][1] += pixelArray[i][1];
            eachClass[which_class][2] += pixelArray[i][2];
            eachClass[which_class][3] ++;
        }
        //需要注意,如果有的类没有点了,需要重新分配一个中心点给他
        //这里直接随机分配了
        for(var j = 0; j < numOfClass; j++)
        {   
            if(eachClass[j][3] == 0);//记录R G B 以及对应的类有多少个点
            {
                var ran = Math.floor(Math.random() * pixelArray.length);
                var point = pixelArray[ran];
                class_of_point[point] = j;
                //console.log(point,ran);

                var which_class = class_of_point[ran];
                eachClass[j][0] += point[0];
                eachClass[j][1] += point[1];
                eachClass[j][2] += point[2];
                eachClass[j][3] += 1;

                eachClass[which_class][0] -= point[0];
                eachClass[which_class][1] -= point[1];
                eachClass[which_class][2] -= point[2];
                eachClass[which_class][3] -= 1;
            }
        }

        //对每个类进行中心点更新
        for(var j = 0; j < center_point.length; j++)
        {   
            var r = eachClass[j][0] / eachClass[j][3];
            var g = eachClass[j][1] / eachClass[j][3];
            var b = eachClass[j][2] / eachClass[j][3];
            center_point[j] = [r, g, b];
        }
        //console.log(center_point);
        var cnt = 0;//记录变动幅度
        for(var i = 0; i < class_of_point.length; i++)
        {
            if(cmp_array[i] != class_of_point[i])
            {
                cnt++;
            }
        }
        //console.log(1);
        
        var condition = class_of_point.length / 1000;
        if(condition < 5)
        {
            condition = 5;
        }    
    }while(cnt > condition)
    
    
    
    return {
        category : class_of_point,
        mean_point : center_point
    };
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值