55

マップのラベルに力の反発を適用して、適切な場所を自動的に見つける方法は?


ボストックの「地図を作ろう」

Mike Bostock のLet's Make a Map (下のスクリーンショット)。デフォルトでは、ラベルはポイントの座標とポリゴン/マルチポリゴンのpath.centroid(d)+ 単純な左揃えまたは右揃えに配置されるため、頻繁に競合が発生します。

ここに画像の説明を入力

手作りラベルの配置

私が出会った改善の 1 つは、人間が作成したIF修正を追加し、必要な数だけ追加することを必要とします。

.attr("dy", function(d){ if(d.properties.name==="Berlin") {return ".9em"} })

再調整するラベルの数が増えるにつれて、全体がますます汚くなります。

//places's labels: point objects
svg.selectAll(".place-label")
    .data(topojson.object(de, de.objects.places).geometries)
  .enter().append("text")
    .attr("class", "place-label")
    .attr("transform", function(d) { return "translate(" + projection(d.coordinates) + ")"; })
    .attr("dy", ".35em")
    .text(function(d) { if (d.properties.name!=="Berlin"&&d.properties.name!=="Bremen"){return d.properties.name;} })
    .attr("x", function(d) { return d.coordinates[0] > -1 ? 6 : -6; })
    .style("text-anchor", function(d) { return d.coordinates[0] > -1 ? "start" : "end"; });

//districts's labels: polygons objects.
svg.selectAll(".subunit-label")
    .data(topojson.object(de, de.objects.subunits).geometries)
  .enter().append("text")
    .attr("class", function(d) { return "subunit-label " + d.properties.name; })
    .attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
    .attr("dy", function(d){
    //handmade IF
        if( d.properties.name==="Sachsen"||d.properties.name==="Thüringen"|| d.properties.name==="Sachsen-Anhalt"||d.properties.name==="Rheinland-Pfalz")
            {return ".9em"}
        else if(d.properties.name==="Brandenburg"||d.properties.name==="Hamburg")
            {return "1.5em"}
        else if(d.properties.name==="Berlin"||d.properties.name==="Bremen")
            {return "-1em"}else{return ".35em"}}
    )
    .text(function(d) { return d.properties.name; });

より良い解決策が必要

これは、大規模なマップや一連のラベルでは扱いにくいものです。これらの両方のクラスに力の反発を追加する方法:.place-label.subunit-label?

締め切りが決まっていないので、この問題は非常にブレイン ストーミングですが、非常に興味があります。この質問は、Migurski/ Dymo.pyの基本的な D3js 実装として考えていました。Dymo.py の README.md ドキュメントは、主要なニーズと機能 (作業の 20%、結果の 80%) を選択するための大規模な目標を設定しています。

  1. 最初の配置: Bostock は、ジオポイントを基準にして左/右の配置で良いスタートを切ります。
  2. ラベル間の反発:異なるアプローチが可能で、Lars と Navarrc はそれぞれ 1 つを提案しました。
  3. ラベルの消滅: 1 つのラベルの全体的な反発が強すぎる場合のラベルの消滅関数。他のラベルの間で圧迫されているため、消滅の優先度はランダムまたはpopulationデータ値に基づいており、NaturalEarth の .shp ファイルから取得できます。
  4. 【高級感】ラベル同士の反発力:固定ドットと可動ラベル付き。しかし、これはむしろ贅沢です。

ラベルの反発がラベルのレイヤーやクラス全体で機能するかどうかは無視します。しかし、国のラベルと都市のラベルが重ならないようにするのも贅沢なことかもしれません。

4

5 に答える 5

42

私の意見では、フォース レイアウトはマップ上にラベルを配置する目的には適していません。理由は簡単です。ラベルは、ラベルを付ける場所にできるだけ近づける必要がありますが、強制レイアウトにはこれを強制するものは何もありません。実際、シミュレーションに関する限り、ラベルを混同しても害はありません。これはマップにとって明らかに望ましくありません。

フォース レイアウトの上に、場所自体を固定ノードとして実装し、場所とそのラベルの間に引力を持ち、ラベル間の力は反発するものを実装することができます。これには、変更されたフォース レイアウトの実装 (または同時に複数のフォース レイアウト) が必要になる可能性が高いため、そのルートをたどるつもりはありません。

私の解決策は、単純に衝突検出に依存しています。ラベルのペアごとに、重複しているかどうかを確認します。このような場合は、移動の方向と大きさがオーバーラップから派生する場所に移動します。このように、実際に重なっているラベルのみが移動し、ラベルは少しだけ移動します。このプロセスは、動きがなくなるまで繰り返されます。

オーバーラップのチェックが非常に面倒なため、コードはやや複雑です。ここではコード全体を掲載しませんが、このデモで見つけることができます(効果を誇張するために、ラベルをかなり大きくしていることに注意してください)。キービットは次のようになります。

function arrangeLabels() {
  var move = 1;
  while(move > 0) {
    move = 0;
    svg.selectAll(".place-label")
       .each(function() {
         var that = this,
             a = this.getBoundingClientRect();
         svg.selectAll(".place-label")
            .each(function() {
              if(this != that) {
                var b = this.getBoundingClientRect();
                if(overlap) {
                  // determine amount of movement, move labels
                }
              }
            });
       });
  }
}

全体が完璧とは言えません。ラベルがラベル付けされた場所からかなり離れているラベルもありますが、この方法は普遍的であり、少なくともラベルの重複を避ける必要があります。

ここに画像の説明を入力

于 2014-04-29T19:33:35.670 に答える
22

1 つのオプションは、複数の焦点で force レイアウトを使用することです。各焦点は、機能の重心に配置する必要があり、対応する焦点によってのみ引き付けられるようにラベルを設定します。このように、各ラベルはフィーチャの重心の近くになる傾向がありますが、他のラベルとの反発により、重複の問題が回避される場合があります。

比較のために:

関連するコード:

// Place and label location
var foci = [],
    labels = [];

// Store the projected coordinates of the places for the foci and the labels
places.features.forEach(function(d, i) {
    var c = projection(d.geometry.coordinates);
    foci.push({x: c[0], y: c[1]});
    labels.push({x: c[0], y: c[1], label: d.properties.name})
});

// Create the force layout with a slightly weak charge
var force = d3.layout.force()
    .nodes(labels)
    .charge(-20)
    .gravity(0)
    .size([width, height]);

// Append the place labels, setting their initial positions to
// the feature's centroid
var placeLabels = svg.selectAll('.place-label')
    .data(labels)
    .enter()
    .append('text')
    .attr('class', 'place-label')
    .attr('x', function(d) { return d.x; })
    .attr('y', function(d) { return d.y; })
    .attr('text-anchor', 'middle')
    .text(function(d) { return d.label; });

force.on("tick", function(e) {
    var k = .1 * e.alpha;
    labels.forEach(function(o, j) {
        // The change in the position is proportional to the distance
        // between the label and the corresponding place (foci)
        o.y += (foci[j].y - o.y) * k;
        o.x += (foci[j].x - o.x) * k;
    });

    // Update the position of the text element
    svg.selectAll("text.place-label")
        .attr("x", function(d) { return d.x; })
        .attr("y", function(d) { return d.y; });
});

force.start();

ここに画像の説明を入力

于 2013-07-02T19:55:41.603 に答える