1. 要素技術
2. 612 view

# やりたいこと

データ間の距離の関係だけを見て決める。 この多次元尺度構成法のデモをcoffee scriptで実装してみる。

# ソースコード

## モデル

class Point2D
constructor: (x, y, r, vx, vy, label, wnd_width, wnd_height, context) -&amp;gt;
@x = x
@y = y
@r = r
@vx = vx
@vy = vy
@label = label
@wnd_width = wnd_width
@wnd_height = wnd_height
@context = context

#
# Update poistion with velocity
#
update_position: -&amp;gt;
@x += @vx
@y += @vy

#
# Return x and y with array style
#
get_data: -&amp;gt;
return [@x, @y]

#
# Calculate distance between points
# @param p : Point2D class's instance
#
calc_distance: (p)-&amp;gt;
return Math.pow(@x - p.x, 2)+ Math.pow(@y - p.y, 2)

#
# Draw point and label to canvas
#
draw: -&amp;gt;
@context.beginPath()
@context.strokeStyle = '#00F'
@context.fillStyle = 'green'
@context.arc(@x, @y, @r, 0, Math.PI * 2, false)
@context.fill()
@context.stroke()
@context.restore()

@context.beginPath()
@context.font = "18px 'MS Pゴシック'"
@context.fillStyle = "red"
@context.fillText(@label, @x, @y-(@r*2))
@context.restore()

#
# Draw point and label with scaling
#
draw_with_scaling: (max,min) -&amp;gt;
prevX = @x
prevY = @y

@x = (@x - min[0]) / (max[0] - min[0]) * @wnd_width
@y = (@y - min[1]) / (max[1] - min[1]) * @wnd_height

@draw()

@x = prevX
@y = prevY

exports.Point2D = Point2D
Point2D = require('../src/point2d').Point2D

describe "Point2D", -&amp;gt;
p1 = undefined
p2 = undefined
p3 = undefined
p4 = undefined
p5 = undefined

beforeEach -&amp;gt;
p1 = new Point2D(0, 0, 0, 0, 0, "", 0, 0, undefined)
p2 = new Point2D(100, 200, 30, 2.0, 2.0, "test", 640, 480, undefined)
p3 = new Point2D(100, 200, 30, 2.0, 2.0, "test", 640, 480, undefined)
p4 = new Point2D(1, 2, 30, 2.0, 2.0, "test", 640, 480, undefined)
p5 = new Point2D(2, 4, 30, 2.0, 2.0, "test", 640, 480, undefined)

it "should be x and y axis value are 0", -&amp;gt;
expect(p1.x).toEqual 0
expect(p1.y).toEqual 0
expect(p1.r).toEqual 0
expect(p1.vx).toEqual 0
expect(p1.vy).toEqual 0
expect(p1.wnd_width).toEqual 0
expect(p1.wnd_height).toEqual 0
expect(p1.label).toEqual ""

it "should be x and y axis value are 5, 10", -&amp;gt;
expect(p2.x).toEqual 100
expect(p2.y).toEqual 200
expect(p2.r).toEqual 30
expect(p2.vx).toEqual 2.0
expect(p2.vy).toEqual 2.0
expect(p2.wnd_width).toEqual 640
expect(p2.wnd_height).toEqual 480
expect(p2.label).toEqual "test"

it "should add velocity for x and y to 102.0 and 202.0", -&amp;gt;
p2.update_position()
expect(p2.x).toEqual 102.0
expect(p2.y).toEqual 202.0

it "should get x and y value with array style", -&amp;gt;
data = p3.get_data()
expect(data[0]).toEqual 100.0
expect(data[1]).toEqual 200.0

it "should get 5 as distance", -&amp;gt;
expect(p4.calc_distance(p5)).toEqual 5

## Multi-Dimension Data

class DataPoint
constructor: (data, label, column_name) -&amp;gt;
@data = data
@label = label
@column_name = column_name

#
# Calculate distance between two points
# @param  dp : DataPoint's instance
#
calc_distance: (dp) -&amp;gt;
sum = 0.0
for i in [0..@data.length-1]
sum += Math.pow(@data[i] - dp.data[i],2)

return Math.sqrt(sum)

get_data: -&amp;gt;
return @data

draw: -&amp;gt;
return

draw_with_scaling: -&amp;gt;
return

exports.DataPoint = DataPoint
DataPoint = require('../src/datapoint').DataPoint

describe "DataPoint", -&amp;gt;
p1 = undefined
p2 = undefined
p3 = undefined
p4 = undefined

beforeEach -&amp;gt;
column_name = ["a","b","c","d","e"]
p1 = new DataPoint([1, 2, 3, 4, 5], "test", column_name)
p2 = new DataPoint([5, 4, 3, 2, 1], "test", column_name)
p3 = new DataPoint([1, 3, 5, 7, 9], "test", column_name)
p4 = new DataPoint([3, 5, 2, 4, 6], "test", column_name)

it "should be x and y axis value are 5, 10", -&amp;gt;
expect(p1.data[0]).toEqual 1
expect(p1.data[1]).toEqual 2
expect(p1.data[2]).toEqual 3
expect(p1.data[3]).toEqual 4
expect(p1.data[4]).toEqual 5
expect(p1.label).toEqual "test"
expect(p1.column_name[0]).toEqual "a"
expect(p1.column_name[1]).toEqual "b"
expect(p1.column_name[2]).toEqual "c"
expect(p1.column_name[3]).toEqual "d"
expect(p1.column_name[4]).toEqual "e"

it "should get x and y value with array style", -&amp;gt;
data = p2.get_data()
expect(data[0]).toEqual 5
expect(data[1]).toEqual 4
expect(data[2]).toEqual 3
expect(data[3]).toEqual 2
expect(data[4]).toEqual 1

it "should get sqrt(35) as distance", -&amp;gt;
expect(p3.calc_distance(p4)).toEqual Math.sqrt(35)

## Point Cloud Model

class PointCloud
constructor: (points)-&amp;gt;
@points = points

@distances = []
for i in [0..points.length-1]
@distances.push([])
for j in [0..points.length-1]
@distances[i].push(0.0)

#
# Calc distances between points and make distance matrix
#
calc_distance: -&amp;gt;
for i in [0..@points.length-1]
for j in [0..@points.length-1]
@distances[i][j] = @points[i].calc_distance(@points[j])

#
# Draw points
#
draw_points: -&amp;gt;
for i in [0..@points.length-1]
@points[i].draw()

#
# Draw points with scaling
#
draw_points_with_scaling: -&amp;gt;
max = @points[0].get_data()
min = @points[0].get_data()

for i in [0..@points.length-1]
for j in [0..max.length-1]
if max[j] &amp;gt; @points[i].get_data()[j]
max[j] = @points[i].get_data()[j]
if min[j] &amp;lt; @points[i].get_data()[j]
min[j] = @points[i].get_data()[j]

for i in [0..@points.length-1]
@points[i].draw_with_scaling(max, min)

exports.PointCloud = PointCloud
PointCloud = require('../src/pointcloud').PointCloud
Point2D = require('../src/point2d').Point2D

describe "PointCloud", -&amp;gt;
pc = undefined
points = []

beforeEach -&amp;gt;
p1 = new Point2D(1, 2, 30, 2.0, 2.0, "test", 640, 480, undefined)
p2 = new Point2D(2, 4, 30, 2.0, 2.0, "test", 640, 480, undefined)
p3 = new Point2D(5, 3, 30, 2.0, 2.0, "test", 640, 480, undefined)
points.push(p1)
points.push(p2)
points.push(p3)
pc = new PointCloud(points)

it "should calculate distances", -&amp;gt;
pc.calc_distance()
expect(pc.distances[0][0]).toEqual 0
expect(pc.distances[1][1]).toEqual 0
expect(pc.distances[2][2]).toEqual 0

expect(pc.distances[0][1]).toEqual 5
expect(pc.distances[0][2]).toEqual 17

expect(pc.distances[1][0]).toEqual 5
expect(pc.distances[1][2]).toEqual 10

expect(pc.distances[2][0]).toEqual 17
expect(pc.distances[2][1]).toEqual 10

## main

Point2D = require('../src/point2d').Point2D
DataPoint = require('../src/datapoint').DataPoint
PointCloud = require('../src/pointcloud').PointCloud

main = ()-&amp;gt;
# Draw area
canvas = document.getElementById('canvas')
# Canvas interface
context = canvas.getContext('2d')

# Make blog name array
keys = []
for key of blog_data
keys.push(key)
num_points = keys.length

# Make data point's cloud
data_points = []
for i in [0..num_points-1]
data_points.push(new DataPoint(blog_data[keys[i]], keys[i], column_name))
realpc = new PointCloud(data_points)

# Make 2d(for canvas) point's cloud
points = []
for i in [0..num_points-1]
x = canvas.width /2 + Math.floor(Math.random() * 200) - 100
y = canvas.height /2 + Math.floor(Math.random() * 200) - 100
vx = 0.0
vy = 0.0

points.push new Point2D(x, y, 2, vx, vy, keys[i], canvas.width, canvas.height, context)
fakepc = new PointCloud(points)
fakepc.draw_points_with_scaling()

# Calculate realdist
realpc.calc_distance()

# Calculate initial fakedist
fakepc.calc_distance()

# Error tmp
lasterror = 0.0
# Learning Rate
rate = 0.0001
# Done flg of calculation
endflg = false

mainloop = ()-&amp;gt;
context.save()
context.beginPath()
context.clearRect(0, 0, canvas.width, canvas.height)
context.restore()

if endflg
fakepc.draw_points_with_scaling()
else
totalerror = 0

# Calculate initial fakedist
fakepc.calc_distance()

# Calculate velocity of canvas point
for i in [0..num_points-1]
for j in [0..num_points-1]
if i==j
continue

errorterm = (fakepc.distances[j][i] - realpc.distances[j][i])/realpc.distances[j][i]

points[i].vx += ((points[i].x - points[j].x)/fakepc.distances[j][i])*errorterm
points[i].vy += ((points[i].y - points[j].y)/fakepc.distances[j][i])*errorterm

totalerror += Math.abs(errorterm)

# Check local minimum
console.log(totalerror)
if lasterror &amp;gt; 1.0 and lasterror &amp;lt; totalerror
endflg = true
else
# Save totalerror
lasterror = totalerror

# Update canvas point's position
for i in [0..num_points-1]
points[i].x -= rate * points[i].vx
points[i].y -= rate * points[i].vy

# Draw points
fakepc.draw_points_with_scaling()

# Do again after 30 millisecond
setTimeout(mainloop, 30)

mainloop()