Supposons des données analysées à partir d'un tsv comme ceci:
<script src="https://d3js.org/d3.v5.min.js"></script>
Et supposons que nous avons l'intention de créer une matrice carrée représentant ces pourcentages (1 rect = 1%, chaque tranche d'âge = couleur différente) :
var margins = {top:100, bottom:300, left:100, right:100}; var height = 600; var width = 900; var totalWidth = width+margins.left+margins.right; var totalHeight = height+margins.top+margins.bottom; var svg = d3.select('body') .append('svg') .attr('width', totalWidth) .attr('height', totalHeight); var graphGroup = svg.append('g') .attr('transform', "translate("+margins.left+","+margins.top+")"); //var tsvData = d3.tsv('so-demo3.tsv'); var maxColumn = 10; //tsvData.then(function(rawData) { /* var data = rawData.map(function(d) { return {year:+d.year, age3:+d.age3*100, age1:+d.age1*100, age2:+d.age2*100, age4:+d.age4*100, age5:+d.age5*100, age6:+d.age6*100, age7:+d.age7*100} }); */ var data = [ {'age1':33.66, 'age2':14.87, 'age3':18, 'age4':14, 'age5':11, 'age6':5, 'age7':3} ]; var data1 = d3.range(100).map(function(d,i) { var age1 = data[0].age1; var age2 = data[0].age2; var age3 = data[0].age3; var age4 = data[0].age4; var age5 = data[0].age5; var age6 = data[0].age6; var age7 = data[0].age7; if (i<age1) { return 0; } else if (i>age1 && i<(age1+age2)) { return 1; } else if (i>(age1+age2) && i<(age1+age2+age3)) { return 2; } else if (i>(age1+age2+age3) && i<(age1+age2+age3+age4)) { return 3; } else if (i>(age1+age2+age3+age4) && i<(age1+age2+age3+age4+age5)) { return 4; } else if (i>(age1+age2+age3+age4+age5) && i<(age1+age2+age3+age4+age5+age6)) { return 5; } else if (i>(age1+age2+age3+age4+age5+age6) && i<(age1+age2+age3+age4+age5+age6+age7)) { return 6; } }); var colorMap = { 0:"#003366", 1:"#366092", 2:"#4f81b9", 3:"#95b3d7", 4:"#b8cce4", 5:"#e7eef8", 6:"#f6d18b" }; graphGroup.selectAll('rect') .data(data1) .enter() .append('rect') .attr('x', function(d, i) { return (i % maxColumn) * 30 }) .attr('y', function(d, i) { return ~~((i / maxColumn) % maxColumn) * 30 }) .attr('width', 20) .attr('height', 20) .style('fill', function(d) { return colorMap[d]; }); //})
Nous aurions besoin d'un moyen de transformer les données
afin qu'elles contiennent 100 éléments, et ces 100 éléments doivent correspondre aux proportions de données
. Pour simplifier, évaluons simplement à data [0]
. Voici ma solution:
data1 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1...];
Cela genre de travaux, mais ce n'est pas du tout évolutif, mais je ne peux pas imaginer comment on transforme le tableaux. Pour être clair, en transformant des tableaux, je veux dire que nous commençons par:
data = [ {'age1':33.66, 'age2':14.87, 'age3':18, 'age4':14, 'age5':11, 'age6':5, 'age7':3} ];
et finissons par:
var data1 = d3.range(100).map(function(d,i) { var age1 = data[0].age1; var age2 = data[0].age2; var age3 = data[0].age3; var age4 = data[0].age4; var age5 = data[0].age5; var age6 = data[0].age6; var age7 = data[0].age7; if (i<age1) { return 0; } else if (i>age1 && i<(age1+age2)) { return 1; } else if (i>(age1+age2) && i<(age1+age2+age3)) { return 2; } else if (i>(age1+age2+age3) && i<(age1+age2+age3+age4)) { return 3; } else if (i>(age1+age2+age3+age4) && i<(age1+age2+age3+age4+age5)) { return 4; } else if (i>(age1+age2+age3+age4+age5) && i<(age1+age2+age3+age4+age5+age6)) { return 5; } else if (i>(age1+age2+age3+age4+age5+age6) && i<(age1+age2+age3+age4+age5+age6+age7)) { return 6; } });
var maxColumn = 10; var colorMap = { 0:"#003366", 1:"#366092", 2:"#4f81b9", 3:"#95b3d7", 4:"#b8cce4", 5:"#e7eef8", 6:"#f6d18b" }; graphGroup.selectAll('rect') .data(data1) .enter() .append('rect') .attr('x', function(d, i) { return (i % maxColumn) * 30 }) .attr('y', function(d, i) { return ~~((i / maxColumn) % maxColumn) * 30 }) .attr('width', 20) .attr('height', 20) .style('fill', function(d) { return colorMap[d]; });
tsvData.then(function(rawData) { var data = rawData.map(function(d) { return {year:+d.year, age3:+d.age3*100, age1:+d.age1*100, age2:+d.age2*100, age4:+d.age4*100, age5:+d.age5*100, age6:+d.age6*100, age7:+d.age7*100} });
Est-ce que d3 offre quelque chose de plus sublime que mon approche par force brute pour atteindre le tableau souhaité?
3 Réponses :
Je suppose que vous souhaitez créer un pictogramme. Si c'est correct, ce mélange de reduction
et map
peut facilement créer les tableaux individuels que vous voulez, quel que soit le nombre de propriétés dans l'objet de référence:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Votre extrait semble fonctionner très bien, mais lorsque j'ai essayé de l'adapter à toute la longueur des données
en le mettant dans une boucle for et en l'itérant comme data [j] .forEach (obj = > ...)
mais mon journal de console avait une erreur "data [j] n'est pas une fonction" Sûrement forEach ()
peut être appelé de manière itérative?
Vous avez tort. Ne faites pas de data [peu importe]
, faites simplement data
. J'ai fait ce code pour itérer sur tout votre tableau de données, avec tous les objets à l'intérieur. L'erreur que vous avez est parce que data [j]
est un objet , pas un tableau ... forEach
est un tableau b> méthode.
Oh je comprends. Merci. Cela fonctionne maintenant, il suffit de changer le 100e carré et le 1er carré sur certaines matrices. Je vais vérifier que les sommes sont égales à 100, mais faites-moi savoir si vous avez d'autres intuitions.
Vous aviez raison, cela avait absolument à voir avec la somme de 100. Dans mon extrait ci-dessus, je viens d'utiliser des données rapides / sales, mais mes données réelles totalisent exactement 100. Je viens de vérifier l'exactitude, et toujours le problème persiste. Je suppose qu'il y a une sorte de problème d'arrondi / reste où certaines matrices reçoivent des extras tandis que d'autres n'ont pas de carré? Il n'est toujours décalé que d'un carré (certains sont 101, certains sont 99). Faites-moi savoir si vous pouvez penser à un moyen de résoudre ce problème.
Vous devrez créer votre propre fonction mathématique pour gérer l'arrondi. Par exemple, si vous avez [4.5, 4.5, 1]
, vous avez une somme parfaite de 10. Mais si vous les arrondissez avec Math.round
, vous aurez un somme de 11 (c'est-à-dire [5, 5, 1]
). Puisque cela sort du cadre de cette question, je vous suggère de la poser comme une nouvelle question (sans la balise D3, car ce n'est qu'un problème mathématique).
D'accord, il n'y a pas de bien ou de mal dans ce cas. Surtout, étant donné le petit jeu de données, la lisibilité et la clarté devraient avoir une grande priorité, mais ... Néanmoins, puis-je simplement dire que ma solution est au moins deux fois plus rapide que la vôtre :-) jsperf.com/create-pictogram-array/1 La version vanilla optimisée - utilisant des boucles for ... ajoute encore 50 pour cent à cela. Il semble que les nouvelles méthodes Array.from ()
et Array.prototype.fill ()
fonctionnent très mal. Juste dire. @ArashHowaida
@altocumulus jaspe très intéressant. Cependant, contrairement à vos 3 versions (d3, vanilla et optimisé), ma version n'assume pas 7 propriétés dans l'objet, elle peut traiter n'importe quel nombre de propriétés (grâce à Object.keys ()
) .
@GerardoFurtado Plus, le vôtre est robuste et ne dépassera même pas la propriété supplémentaire year
dans les données originales d'OP.
Votre objectif est de représenter les valeurs uniques du tableau de données sous forme de valeurs discrètes dans une matrice a * a. Donc, ma suggestion (en pseudo-code) serait:
Scale values so the total sum is a*a (in our case 10*10 = 100): sumAge = age1 + age2 .... data.forEach(age1/sumAge*100) Now paint the chart: counter = 0 data.forEach age ageCounter = 0; while agecounter < age ageCounter ++ set color while counter < 100 counter ++ draw rectangle at x,y with x = 100 mod a y = 100 div a
Vous mettez à l'échelle les valeurs - vous bouclez pas à pas à travers chaque élément de données (pour définir la couleur), et avez une boucle interne qui est exécutée aussi souvent que nécessaire pour dessiner des carrés.
Il existe évidemment de nombreuses solutions à votre problème. Cependant, puisque vous avez explicitement demandé une solution D3, voici mon point de vue:
<script src="https://d3js.org/d3.v5.min.js"></script>
Jetez un œil à l'extrait de code suivant pour une démonstration fonctionnelle:
p>
var margins = {top:100, bottom:300, left:100, right:100}; var height = 600; var width = 900; var totalWidth = width+margins.left+margins.right; var totalHeight = height+margins.top+margins.bottom; var svg = d3.select('body') .append('svg') .attr('width', totalWidth) .attr('height', totalHeight); var graphGroup = svg.append('g') .attr('transform', "translate("+margins.left+","+margins.top+")"); //var tsvData = d3.tsv('so-demo3.tsv'); var maxColumn = 10; //tsvData.then(function(rawData) { /* var data = rawData.map(function(d) { return {year:+d.year, age3:+d.age3*100, age1:+d.age1*100, age2:+d.age2*100, age4:+d.age4*100, age5:+d.age5*100, age6:+d.age6*100, age7:+d.age7*100} }); */ var data = [ {'age1':33.66, 'age2':14.87, 'age3':18, 'age4':14, 'age5':11, 'age6':5, 'age7':3} ]; const data1 = d3.merge( // 5. Merge all sub-array into one. d3.range(1,8) // 1. For every age group 1-7... .map(d => d3.range( // 2. create an array... Math.round(data[0][`age${d}`]) // 3. given a length as per the age property... ).map(() => d-1) // 4. populated with the value from 2. ) ); var colorMap = { 0:"#003366", 1:"#366092", 2:"#4f81b9", 3:"#95b3d7", 4:"#b8cce4", 5:"#e7eef8", 6:"#f6d18b" }; graphGroup.selectAll('rect') .data(data1) .enter() .append('rect') .attr('x', function(d, i) { return (i % maxColumn) * 30 }) .attr('y', function(d, i) { return ~~((i / maxColumn) % maxColumn) * 30 }) .attr('width', 20) .attr('height', 20) .style('fill', function(d) { return colorMap[d]; }); //})
// STEPS 1-5: const data1 = d3.merge( // 5. Merge all sub-array into one. d3.range(1,8) // 1. For every age group 1-7... .map(d => d3.range( // 2. create an array... Math.round(data[0][`age${d}`]) // 3. given a length as per the age property... ).map(() => d-1) // 4. populated with the value from 2. ) );
Avez-vous toujours exactement 7 valeurs d'âge en entrée? Qu'essayez-vous de visualiser exactement? Les âges ne devraient-ils pas être triés en premier ou quelque chose comme ça? Je comprends ce que vous essayez de faire techniquement, mais je ne suis pas sûr de l'objectif général
@ErikReder Supposons que nous ayons toujours affaire à 7 groupes d'âge, et oui, pour faire simple, supposons une matrice carrée, 10x10.
(Modifié ci-dessus) - vous essayez donc de `` répartir '' les tranches d'âge individuelles? Qu'est-ce que cela devrait représenter alors? Je pense que vous ne verriez que des "travées", mais il n'y a plus de relation avec les valeurs d'âge, non? Est-ce ce que vous voulez réaliser? Si tel est le cas, je suppose que je suggérerais de trier (/ ordonner) les valeurs en premier, de prendre min / man comme limites, de calculer d'abord les écarts entre les valeurs uniques, puis de les mettre simplement en proportion. Serait alors ~ 10 calculs au lieu de 100, je pense.
Cela ressemble à un problème XY pour moi. Comme @ErikReder l'a déjà demandé: dites-nous en plus sur ce que vous essayez d'accomplir au lieu d'essayer de corriger une solution potentiellement sous-optimale ou défectueuse. Les échelles de seuil ou le regroupement peuvent fonctionner pour vous. C'est difficile à dire, cependant, à moins que vous ne nous donniez plus de détails sur vos objectifs.
Ok, je l'ai enfin compris. Les valeurs représentent des pourcentages de certains âges, et ces pourcentages doivent être représentés dans une matrice x * x. La transformation, cependant, de mon point de vue est une chose unidimensionnelle, mettant les valeurs dans des seaux discrets. La visualisation est alors en 2 dimensions ...