D3js: Automatique des étiquettes de placement pour éviter les chevauchements? (force de répulsion)
Comment faire pour appliquer une force de répulsion sur la carte de labels, de sorte qu'ils trouvent leur place automatiquement ?
Bostock' "nous allons Faire une Carte"
Mike Bostock est Nous allons Faire une Carte (capture d'écran ci-dessous). Par défaut, les étiquettes sont les coordonnées du point et des polygones/multipolygons de path.centroid(d)
+ simple à gauche ou à droite aligner, donc ils sont souvent en conflit.
Label à la main stages
Une amélioration J'ai rencontré nécessite d'ajouter un humain en fait IF
de bugs, et d'en ajouter autant que nécessaire, par exemple :
.attr("dy", function(d){ if(d.properties.name==="Berlin") {return ".9em"} })
L'ensemble du devenir de plus en plus sale que le nombre d'étiquettes à reajust augmentation :
//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; });
Besoin d'une meilleure solution
C'est juste pas gérable pour les grandes cartes et des jeux d'étiquettes. Comment ajouter de la force repulsions de ces deux classes: .place-label
et .subunit-label
?
Cette question est tout à fait un remue-méninges que je n'ai pas de date limite, mais je suis assez curieux à ce sujet. Je pensais à cette question de base D3js mise en œuvre de Migurski/Dymo.py. Dymo.py s'README.md ensemble de la documentation d'un grand ensemble d'objectifs, à partir de laquelle sélectionner la base des besoins et des fonctions (20% de l'activité, 80% du résultat).
- Placement Initial: Bostock donner un bon départ avec gauche/droite positionnement par rapport à la geopoint.
- Inter-étiquettes de répulsion: approche différente sont possibles, Lars & Navarrc proposé un chacun,
- Étiquettes annihilation: Une étiquette d'annihilation de la fonction lorsque l'un label global de répulsion est trop intense, car coincé entre autres labels, avec la priorité à l'anéantissement de l'être aléatoire ou en fonction d'un
population
données de la valeur, que nous pouvons obtenir par NaturalEarth de l' .shp fichier. - [Luxe] d'Étiquette à des points de répulsion: avec points fixes et mobiles des étiquettes. Mais c'est plutôt un luxe.
J'ignore si l'étiquette de la répulsion va travailler à travers des couches et des classes d'étiquettes. Mais pour obtenir des pays les étiquettes et les villes étiquettes non cumul peut être un luxe.
- Je pense que l'ajout de la force de répulsion à l'endroit des étiquettes peut rendre certaines étiquettes aller hors de leur région respective. Autre chose à considérer est que les différents types d'étiquettes peuvent se chevaucher dans certaines cartes, le nom d'une ville peut être au-dessus du nom du pays, avec de très distint polices bien. Je pense que la solution définitive peut être plus complexe que simplement ajouter de la répulsion.
- J'ai utilisé une force de mise en page pour positionner les étiquettes ici: larsko.org/v/igdp/index-alt.html Votre cas est plus complexe, car elle implique deux dimensions, mais vous pourriez être en mesure de réutiliser une partie du code.
- Tout d'abord, comment appliquer la répulsion sur mes articles. Plus tard, la force peut être subtile. Il a besoin d'une répulsion diminue rapidement avec la distance, le type de R = 1/x. Cet ajustement sera un autre problème.
- J'ai mis en place une démo de cette stratégie. Il n'est pas parfait, mais il peut aider. bl.ocks.org/pnavarrc/5913636
- Je sais que ce n'est pas la force de répulsion liés, mais comme Mike Bostock a souligné dans le tutoriel, il y a ce script github.com/migurski/Dymo cela devrait faire l'affaire (je n'étais pas capable de le faire fonctionner tho, j'ai même posté une question ici pour avoir des conseils mais bon j'espère que vous pouvez!)
- Tout puissant, Dymo.py est un Python approche qui est tout à fait contre-productif au sein de D3js / client web-cartographie de l'écosystème. Je suis plutôt de l'ouverture d'un à long terme pour parler d'une pure D3js approche, probablement plus basique, mais efficace et suffisante.
Vous devez vous connecter pour publier un commentaire.
À mon avis, la force de la mise en page est impropre à l'usage de placer les étiquettes sur une carte. La raison en est simple: les étiquettes doivent être aussi proches que possible des lieux qu'ils ont l'étiquette, mais la force de mise en page n'a rien à faire appliquer le présent. En effet, pour la simulation, il n'y a pas de mal à mélanger les étiquettes, ce qui n'est clairement pas souhaitable pour une carte.
Il pourrait être quelque chose de mis en œuvre sur le dessus de la force de mise en page qui a les places se fixe des nœuds et des forces d'attraction entre le lieu et son étiquette, alors que les forces entre les étiquettes serait répulsive. Cela nécessiterait une modification de la force de mise en page mise en œuvre (ou plusieurs mises en vigueur en même temps), donc je ne vais pas aller dans cette voie.
Ma solution s'appuie simplement sur la détection de collision: pour chaque paire d'étiquettes, de vérifier si elles se chevauchent. Si c'est le cas, de les déplacer hors de la voie, où la direction et l'amplitude du mouvement est dérivé de la chevaucher. De cette manière, seules les étiquettes qui fait de chevauchement sont déplacés à tous, et seulement les étiquettes de déplacer un peu. Ce processus est itéré jusqu'à ce qu'aucun mouvement ne se produit.
Le code est un peu compliquée car le contrôle de chevauchement est assez salissant. Je ne vais pas poster tout le code ici, il peut être trouvé dans cette démo (à noter que j'ai fait les étiquettes beaucoup plus à exagérer l'effet). La clé de bits ressembler à ceci:
Toute la chose est loin d'être parfait: à noter que certaines étiquettes sont assez loin de la question de la place de l'étiquette, mais la méthode est universelle et doit au moins d'éviter le chevauchement des étiquettes.
Une option est d'utiliser le la force de mise en page avec de multiples foyers. Chaque foyers doit être situé dans le centre de gravité, mis en place l'étiquette pour être attiré que par les foyers. De cette façon, chaque étiquette aura tendance à être près de la fonctionnalité du centre de gravité, mais la répulsion avec d'autres étiquettes peuvent éviter les problèmes de chevauchement.
Pour la comparaison:
Le code:
force.alpha() < 1e-2
, qui en général arrive en moins de 20 à 30 itérations (en fonction de la structure du réseau).Tout ShareMap-dymo.js de travail, il ne semble pas être très bien documenté. J'ai trouvé une bibliothèque qui fonctionne pour le cas plus général, est bien documenté et utilise également le recuit simulé: D3-Étiqueteuse
J'ai mis en place une utilisation de l'échantillon avec cette jsfiddle.Le D3-Étiqueteuse exemple de page utilise de 1 000 itérations. J'ai trouvé que c'est plutôt inutile et que 50 itérations semble fonctionner assez bien c'est très rapide, même pour quelques centaines de points de données. Je crois qu'il ya place à l'amélioration à la fois dans la manière dont cette bibliothèque s'intègre avec les D3 et en termes d'efficacité, mais je n'aurais pas été en mesure d'obtenir ce bien sur mon propre. Je vais mettre à jour ce fil, je devrais trouver le temps de soumettre un PR.
Voici le code correspondant (voir le mémorandum D3-Étiqueteuse lien pour plus de documentation):
Pour un regard plus en profondeur comment D3-Étiqueteuse œuvres, voir "Un D3 plug-in automatique de placement des étiquettes à l'aide simulé
recuit"
Jeff Heaton "de l'Intelligence Artificielle pour l'Homme, Volume 1" a également fait un excellent travail à l'explication du processus de recuit simulé.
Vous pourriez être intéressé par le d3fc-étiquette de mise en page composant (par D3v5) qui est conçu exactement pour ce but. Le composant fournit un mécanisme pour organiser enfant composantes en fonction de leurs rectangulaire boîtes englobantes. Vous pouvez appliquer une gourmande ou le recuit simulé, la stratégie afin de minimiser les chevauchements.
Voici un extrait de code qui montre comment appliquer cette mise en forme de la composante de Mike Bostock de la carte exemple:
Et c'est une petite capture d'écran du résultat:
Vous pouvez voir un exemple complet ici:
http://bl.ocks.org/ColinEberhardt/389c76c6a544af9f0cab
Divulgation: tel Que discuté dans les commentaires ci dessous, je suis un collaborateur principal de ce projet, donc, clairement, je suis un peu biaisé. Plein crédit pour les autres réponses à cette question qui nous a donné de l'inspiration!
textLabel
s avec plus que juste le texte? J'aimerais avoir quelques<tspan>
éléments de style de choses.Une option est d'utiliser une Mise en page de Voronoi pour calculer où il y a de l'espace entre les points. Il y a un bon exemple de Mike Bostock ici.
Pour le cas 2D
voici quelques exemples de quelque chose de très similaire:
un http://bl.ocks.org/1691430
deux http://bl.ocks.org/1377729
merci Alexandre Skaburskis qui dit cela ici
Pour le cas 1D
Pour ceux qui recherche une solution à un problème similaire en 1-D, je peux partager mon bac à sable JSfiddle où j'ai essayer de le résoudre. C'est loin d'être parfait, mais il sorte de faire la chose.
Gauche: Le modèle de sandbox, à Droite: un exemple d'utilisation
Voici l'extrait de code que vous pouvez courir en appuyant sur le bouton à l'extrémité de la poste, et aussi le code lui-même. Lors de l'exécution, cliquez sur le champ à la position fixe de nœuds.
JS:
CSS:
HTML: