| /* |
| title: Clustering Process |
| category: scatter |
| titleCN: 聚合过程可视化 |
| difficulty: 1 |
| */ |
| |
| |
| var originalData = [ |
| [3.275154, 2.957587], |
| [-3.344465, 2.603513], |
| [0.355083, -3.376585], |
| [1.852435, 3.547351], |
| [-2.078973, 2.552013], |
| [-0.993756, -0.884433], |
| [2.682252, 4.007573], |
| [-3.087776, 2.878713], |
| [-1.565978, -1.256985], |
| [2.441611, 0.444826], |
| [-0.659487, 3.111284], |
| [-0.459601, -2.618005], |
| [2.17768, 2.387793], |
| [-2.920969, 2.917485], |
| [-0.028814, -4.168078], |
| [3.625746, 2.119041], |
| [-3.912363, 1.325108], |
| [-0.551694, -2.814223], |
| [2.855808, 3.483301], |
| [-3.594448, 2.856651], |
| [0.421993, -2.372646], |
| [1.650821, 3.407572], |
| [-2.082902, 3.384412], |
| [-0.718809, -2.492514], |
| [4.513623, 3.841029], |
| [-4.822011, 4.607049], |
| [-0.656297, -1.449872], |
| [1.919901, 4.439368], |
| [-3.287749, 3.918836], |
| [-1.576936, -2.977622], |
| [3.598143, 1.97597], |
| [-3.977329, 4.900932], |
| [-1.79108, -2.184517], |
| [3.914654, 3.559303], |
| [-1.910108, 4.166946], |
| [-1.226597, -3.317889], |
| [1.148946, 3.345138], |
| [-2.113864, 3.548172], |
| [0.845762, -3.589788], |
| [2.629062, 3.535831], |
| [-1.640717, 2.990517], |
| [-1.881012, -2.485405], |
| [4.606999, 3.510312], |
| [-4.366462, 4.023316], |
| [0.765015, -3.00127], |
| [3.121904, 2.173988], |
| [-4.025139, 4.65231], |
| [-0.559558, -3.840539], |
| [4.376754, 4.863579], |
| [-1.874308, 4.032237], |
| [-0.089337, -3.026809], |
| [3.997787, 2.518662], |
| [-3.082978, 2.884822], |
| [0.845235, -3.454465], |
| [1.327224, 3.358778], |
| [-2.889949, 3.596178], |
| [-0.966018, -2.839827], |
| [2.960769, 3.079555], |
| [-3.275518, 1.577068], |
| [0.639276, -3.41284] |
| ]; |
| |
| |
| var DIM_CLUSTER_INDEX = 2; |
| var DATA_DIM_IDX = [0, 1]; |
| var CENTER_DIM_IDX = [3, 4]; |
| |
| // See https://github.com/ecomfe/echarts-stat |
| var step = ecStat.clustering.hierarchicalKMeans(originalData, { |
| clusterCount: 6, |
| outputType: 'single', |
| outputClusterIndexDimension: DIM_CLUSTER_INDEX, |
| outputCentroidDimensions: CENTER_DIM_IDX, |
| stepByStep: true |
| }); |
| |
| var colorAll = [ |
| '#bbb', '#37A2DA', '#e06343', '#37a354', '#b55dba', '#b5bd48', '#8378EA', '#96BFFF' |
| ]; |
| var ANIMATION_DURATION_UPDATE = 1500; |
| |
| function renderItemPoint(params, api) { |
| var coord = api.coord([api.value(0), api.value(1)]); |
| var clusterIdx = api.value(2); |
| if (clusterIdx == null || isNaN(clusterIdx)) { |
| clusterIdx = 0; |
| } |
| var isNewCluster = clusterIdx === api.value(3); |
| |
| var extra = { |
| transition: [] |
| }; |
| var contentColor = colorAll[clusterIdx]; |
| |
| return { |
| type: 'circle', |
| x: coord[0], |
| y: coord[1], |
| shape: { |
| cx: 0, |
| cy: 0, |
| r: 10 |
| }, |
| extra: extra, |
| style: { |
| fill: contentColor, |
| stroke: '#333', |
| lineWidth: 1, |
| shadowColor: contentColor, |
| shadowBlur: isNewCluster ? 12 : 0, |
| transition: ['shadowBlur', 'fill'] |
| } |
| }; |
| } |
| |
| function renderBoundary(params, api) { |
| var xVal = api.value(0); |
| var yVal = api.value(1); |
| var maxDist = api.value(2); |
| var center = api.coord([xVal, yVal]); |
| var size = api.size([maxDist, maxDist]); |
| |
| return { |
| type: 'ellipse', |
| shape: { |
| cx: isNaN(center[0]) ? 0 : center[0], |
| cy: isNaN(center[1]) ? 0 : center[1], |
| rx: isNaN(size[0]) ? 0 : size[0] + 15, |
| ry: isNaN(size[1]) ? 0 : size[1] + 15 |
| }, |
| extra: { |
| renderProgress: ++targetRenderProgress, |
| enterFrom: { |
| renderProgress: 0 |
| }, |
| transition: 'renderProgress' |
| }, |
| style: { |
| fill: null, |
| stroke: 'rgba(0,0,0,0.2)', |
| lineDash: [4, 4], |
| lineWidth: 4 |
| } |
| }; |
| } |
| |
| function makeStepOption(option, data, centroids) { |
| var newCluIdx = centroids ? centroids.length - 1 : -1; |
| var maxDist = 0; |
| for (var i = 0; i < data.length; i++) { |
| var line = data[i]; |
| if (line[DIM_CLUSTER_INDEX] === newCluIdx) { |
| var dist0 = Math.pow(line[DATA_DIM_IDX[0]] - line[CENTER_DIM_IDX[0]], 2); |
| var dist1 = Math.pow(line[DATA_DIM_IDX[1]] - line[CENTER_DIM_IDX[1]], 2); |
| maxDist = Math.max(maxDist, dist0 + dist1); |
| } |
| } |
| var boundaryData = centroids |
| ? [ |
| [ |
| centroids[newCluIdx][0], |
| centroids[newCluIdx][1], |
| Math.sqrt(maxDist) |
| ] |
| ] |
| : []; |
| |
| option.options.push({ |
| series: [{ |
| type: 'custom', |
| encode: { |
| tooltip: [0, 1] |
| }, |
| renderItem: renderItemPoint, |
| data: data |
| }, { |
| type: 'custom', |
| renderItem: renderBoundary, |
| animationDuration: 3000, |
| silent: true, |
| data: boundaryData |
| }] |
| }); |
| } |
| |
| var targetRenderProgress = 0; |
| |
| option = { |
| timeline: { |
| top: 'center', |
| right: 50, |
| height: 300, |
| width: 10, |
| inverse: true, |
| autoPlay: false, |
| playInterval: 2500, |
| symbol: 'none', |
| orient: 'vertical', |
| axisType: 'category', |
| label: { |
| formatter: 'step {value}', |
| position: 10 |
| }, |
| checkpointStyle: { |
| animationDuration: ANIMATION_DURATION_UPDATE |
| }, |
| data: [] |
| }, |
| baseOption: { |
| animationDurationUpdate: ANIMATION_DURATION_UPDATE, |
| transition: ['shape'], |
| tooltip: { |
| }, |
| xAxis: { |
| type: 'value' |
| }, |
| yAxis: { |
| type: 'value' |
| }, |
| series: [{ |
| type: 'scatter' |
| }] |
| }, |
| options: [] |
| }; |
| |
| makeStepOption(option, originalData); |
| option.timeline.data.push('0'); |
| for (var i = 1, stepResult; !(stepResult = step.next()).isEnd; i++) { |
| makeStepOption( |
| option, |
| echarts.util.clone(stepResult.data), |
| echarts.util.clone(stepResult.centroids) |
| ); |
| option.timeline.data.push(i + ''); |
| } |