そこで、d3.js、polymaps、Coffeescriptを使用するプロジェクトでマップマーカーのクラスタリングを実行しようとしています。
基になるデータに基づいてクラスターを計算し、クラスター化された配列を.data(clusters)としてd3に渡します。
クラスターの配置は問題ないようです。初期ズームレベルでのクラスタリングは問題ないようで、私の知識に基づくと100%正確です。ズームレベルを変更すると、一見すべて問題ないように見えますが、円にカーソルを合わせると、説明が現在の場所と一致していないように見え、以前のルートがどこにあったかが一致していないようです。今です。
完全なコードを含むこの例をhttp://bl.ocks.org/3161013で準備しました。
これには、クラスタリングとSVGの更新という2つの主要な障害点があります。
クラスタリングのコードはかなり単純で、 Mark Tuupolaのコードに基づいていますが、phpではなくcoffeescriptを使用しています。
cluster: (elements, distance) ->
currentElements = elements.slice(0)
pixelDistance = @pixelDistance()
distLat = distance * pixelDistance.lat
distLon = distance * pixelDistance.lon
clustered = []
while currentElements.length > 0
stop = currentElements.shift()
cluster = []
cluster.push stop
i = 0
while i < currentElements.length
if Math.abs(currentElements[i].lat - stop.lat) < distLat and Math.abs(currentElements[i].lon - stop.lon) < distLon
aStop = currentElements.splice i,1
cluster.push aStop[0]
i--
i++
clustered.push cluster
clustered
SVG更新のコードは、かなり単純なd3コードのようです。マップが移動されるたびに、この更新メソッドが呼び出されます。ズームが変更された場合、または事前にクラスター化されたデータが変更された場合は、再クラスター化してレイアウトします。それ以外の場合は、既存のポイントを変換するだけです。
update: ->
if not @stops
@stops = []
if not @prevNumStops
@prevNumStops = 0
if not @prevZoom
@prevZoom = 0
if @zoomLevel() != @prevZoom or @prevNumStops != @stops.length
@prevZoom = @zoomLevel()
@prevNumStops = @stops.length
start = new Date()
@clusters = @cluster(@stops,10)
console.log @clusters
console.log "clustering: " + ((new Date()) - start)
start = new Date()
marker = @selector.selectAll("g").data(@clusters)
marker.enter().append("g")
.append("circle")
.attr("class", "stop no-tip")
marker.exit().remove()
@selector.selectAll("g").selectAll("circle")
.attr('r', (cluster) -> if cluster.length > 1 then 5 else 3.5)
.attr("text", (cluster) -> "<ul>" + ((("<li>" + route + "</li>") for route in stop.routes).join("") for stop in cluster).join("") + "</ul>")
@selector.selectAll("g").attr("transform", (cluster) =>
@transform cluster[0]
)
ここで見逃しているのはおそらく簡単なことのように感じますが、それでもd3にはかなり慣れていません。