|
| 1 | +/* |
| 2 | + * David Lettier |
| 3 | + * (C) 2015 |
| 4 | + * http://www.lettier.com/ |
| 5 | +*/ |
| 6 | + |
| 7 | +var DATAPOINTSIZE = 15; |
| 8 | +var DATAPOINTCOLOR = '#999'; |
| 9 | + |
| 10 | +var VISUALMEANCOLOR = '#000'; |
| 11 | +var VISUALMEANSIZE = 8; |
| 12 | + |
| 13 | +var MAXKMEANSITERATIONS = 20000; |
| 14 | +var MAXKMEANSK = 10; |
| 15 | + |
| 16 | +var canvas = document.createElement('canvas'); |
| 17 | +var stage = null; |
| 18 | + |
| 19 | +var kmeans = null; |
| 20 | + |
| 21 | +var clusterColors = uniqueColors(MAXKMEANSK, 360 / MAXKMEANSK); |
| 22 | + |
| 23 | +var makeVisualDataPoint = function (position) { |
| 24 | + var id = uniqueNumber(); |
| 25 | + var shape = new createjs.Shape(); |
| 26 | + var dataPoint = new DataPoint( |
| 27 | + [position.x, position.y] |
| 28 | + ); |
| 29 | + if (kmeans !== null) { |
| 30 | + kmeans.assign(dataPoint); |
| 31 | + color = clusterColors[dataPoint.meanId]; |
| 32 | + shape.graphics.setStrokeStyle( |
| 33 | + 1 |
| 34 | + ).beginFill( |
| 35 | + color |
| 36 | + ).drawCircle( |
| 37 | + 0, |
| 38 | + 0, |
| 39 | + DATAPOINTSIZE |
| 40 | + ); |
| 41 | + } else { |
| 42 | + shape.graphics.setStrokeStyle( |
| 43 | + 1 |
| 44 | + ).beginFill( |
| 45 | + DATAPOINTCOLOR |
| 46 | + ).drawCircle( |
| 47 | + 0, |
| 48 | + 0, |
| 49 | + DATAPOINTSIZE |
| 50 | + ); |
| 51 | + } |
| 52 | + shape.x = dataPoint.features[0]; |
| 53 | + shape.y = dataPoint.features[1]; |
| 54 | + shape.id = id; |
| 55 | + dataPointLayer.addChild(shape); |
| 56 | + shape.on('click', function (event) { |
| 57 | + var id = this.id; |
| 58 | + dataPointsTemp = dataPoints.filter(function (dataPoint) { |
| 59 | + return dataPoint.id !== id; |
| 60 | + }); |
| 61 | + dataPoints = dataPointsTemp; |
| 62 | + dataPointLayer.removeChild(this); |
| 63 | + stage.update(); |
| 64 | + event.stopImmediatePropagation(); |
| 65 | + }); |
| 66 | + dataPoint.id = id; |
| 67 | + dataPoint.shape = shape; |
| 68 | + dataPoints.push(dataPoint); |
| 69 | + stage.update(); |
| 70 | +}; |
| 71 | + |
| 72 | +var dataPointLayer = new createjs.Container(); |
| 73 | +var dataPointLayerBase = new createjs.Shape(); |
| 74 | +var dataPoints = []; |
| 75 | +var visualMeans = []; |
| 76 | + |
| 77 | +var guiControls = null; |
| 78 | +var gui = null; |
| 79 | + |
| 80 | +var GUIControls = function () { |
| 81 | + var that = this; |
| 82 | + |
| 83 | + this.k = MAXKMEANSK; |
| 84 | + this.maxIterations = MAXKMEANSITERATIONS; |
| 85 | + |
| 86 | + this.reset = function () { |
| 87 | + dataPoints.forEach(function (dataPoint) { |
| 88 | + dataPointLayer.removeChild(dataPoint.shape); |
| 89 | + }); |
| 90 | + visualMeans.forEach(function (visualMean) { |
| 91 | + dataPointLayer.removeChild(visualMean); |
| 92 | + }); |
| 93 | + kmeans = null; |
| 94 | + that.silhouetteCoefficient = 'Press runKMeans.'; |
| 95 | + dataPoints.length = 0; |
| 96 | + visualMeans.length = 0; |
| 97 | + stage.update(); |
| 98 | + }; |
| 99 | + |
| 100 | + this.scatter = function () { |
| 101 | + var minX = 0 + (DATAPOINTSIZE * 2); |
| 102 | + var minY = 0 + (DATAPOINTSIZE * 2); |
| 103 | + var maxX = canvas.width - (DATAPOINTSIZE * 2); |
| 104 | + var maxY = canvas.height - (DATAPOINTSIZE * 2); |
| 105 | + for (var i=0; i<15; ++i) { |
| 106 | + var valueX = randomValueInRange(minX, maxX); |
| 107 | + var valueY = randomValueInRange(minY, maxY); |
| 108 | + makeVisualDataPoint({x: valueX, y: valueY}); |
| 109 | + } |
| 110 | + }; |
| 111 | + |
| 112 | + this.runKMeans = function () { |
| 113 | + var updateSilhouetteCoefficient = function (silhouetteCoefficient) { |
| 114 | + that.silhouetteCoefficient = silhouetteCoefficient; |
| 115 | + }; |
| 116 | + var calculateSilhouetteCoefficient = function (kmeans) { |
| 117 | + silhouetteCoefficient = new SilhouetteCoefficient( |
| 118 | + { |
| 119 | + dataPointClusterMembersFunction: kmeans.dataPointClusterMembers.bind( |
| 120 | + kmeans |
| 121 | + ), |
| 122 | + dataPointNearestClusterMembersFunction: kmeans.dataPointNearestClusterMembers.bind( |
| 123 | + kmeans |
| 124 | + ), |
| 125 | + distanceFunction: kmeans.euclideanDistance.bind(kmeans) |
| 126 | + } |
| 127 | + ); |
| 128 | + |
| 129 | + that.silhouetteCoefficient = 'Calculating...'; |
| 130 | + |
| 131 | + silhouetteCoefficient.model(kmeans.dataPoints, updateSilhouetteCoefficient); |
| 132 | + }; |
| 133 | + var onIterate = function (kmeans) { |
| 134 | + var i = 0; |
| 135 | + if (dataPoints.length === 0) {return;} |
| 136 | + kmeans.clusters.forEach(function (cluster) { |
| 137 | + var color = clusterColors[i]; |
| 138 | + if (cluster.length === 0) {return;} |
| 139 | + cluster.forEach(function (dataPoint) { |
| 140 | + dataPoint.shape.graphics.clear(); |
| 141 | + dataPoint.shape.graphics.beginFill(color).drawCircle( |
| 142 | + 0, 0, DATAPOINTSIZE |
| 143 | + ); |
| 144 | + }); |
| 145 | + stage.update(); |
| 146 | + i += 1; |
| 147 | + }); |
| 148 | + visualMeans.forEach(function (visualMean) { |
| 149 | + dataPointLayer.removeChild(visualMean); |
| 150 | + }); |
| 151 | + visualMeans.length = 0; |
| 152 | + kmeans.means.forEach(function (mean) { |
| 153 | + var shape = new createjs.Shape(); |
| 154 | + shape.graphics.beginFill(VISUALMEANCOLOR).drawRect(0, 0, VISUALMEANSIZE, VISUALMEANSIZE); |
| 155 | + shape.x = mean.features[0]; |
| 156 | + shape.y = mean.features[1]; |
| 157 | + dataPointLayer.addChild(shape); |
| 158 | + visualMeans.push(shape); |
| 159 | + }); |
| 160 | + stage.update(); |
| 161 | + }; |
| 162 | + |
| 163 | + if (dataPoints.length === 0) {return;} |
| 164 | + |
| 165 | + if (that.k > dataPoints.length) {that.k = dataPoints.length;} |
| 166 | + |
| 167 | + kmeans = new KMeans({ |
| 168 | + dataPoints: dataPoints, |
| 169 | + maxIterations: Math.round(that.maxIterations), |
| 170 | + k: Math.round(that.k), |
| 171 | + onIterate: onIterate, |
| 172 | + onComplete: calculateSilhouetteCoefficient |
| 173 | + }); |
| 174 | + |
| 175 | + kmeans.iterate(); |
| 176 | + }; |
| 177 | + |
| 178 | + this.silhouetteCoefficient = 'Press runKMeans.'; |
| 179 | +}; |
| 180 | + |
| 181 | +guiControls = new GUIControls(); |
| 182 | +gui = new dat.GUI({width: 330}); |
| 183 | + |
| 184 | +canvas.id = 'canvas'; |
| 185 | +document.body.appendChild(canvas); |
| 186 | +stage = new createjs.Stage('canvas'); |
| 187 | +stage.canvas.width = window.innerWidth; |
| 188 | +stage.canvas.height = window.innerHeight; |
| 189 | + |
| 190 | +gui.add(guiControls, 'k', 1, MAXKMEANSK).listen(); |
| 191 | +gui.add(guiControls, 'maxIterations', 1, MAXKMEANSITERATIONS); |
| 192 | +gui.add(guiControls, 'reset'); |
| 193 | +gui.add(guiControls, 'scatter'); |
| 194 | +gui.add(guiControls, 'runKMeans'); |
| 195 | +gui.add(guiControls, 'silhouetteCoefficient').listen(); |
| 196 | + |
| 197 | +dataPointLayer.name = 'dataPointLayer'; |
| 198 | +dataPointLayer.x = 0; |
| 199 | +dataPointLayer.y = 0; |
| 200 | +dataPointLayerBase.graphics.beginFill('#3C3C3C').drawRect( |
| 201 | + 0, 0, window.innerWidth, window.innerHeight |
| 202 | +); |
| 203 | +dataPointLayer.addChild(dataPointLayerBase); |
| 204 | +stage.addChild(dataPointLayer); |
| 205 | +dataPointLayer.on('click', function (event) { |
| 206 | + makeVisualDataPoint({x: event.stageX, y: event.stageY}); |
| 207 | +}); |
| 208 | + |
| 209 | +stage.update(); |
0 commit comments