Worthäufigkeiten mit d3.js

d3
statistik
svg
#1

Hallo zusammen,
ich bringe hier eine Frage(n) von mir an Jens mit ein, die wir auf einem anderen Wege diskutiert haben.

Das Ziel ist es mit Hilfe von d3.js Kreise über einen Text zu legen, welche die Häufigkeit der Wörter anziegen.

Ich schrieb:

Moin Jens, heute morgen haben haben wir – wie schon erwähnt – die Projektidee mit der Textanalyse weiterentwickelt.
Was genau ich meine, sieht man im Prototyp-Video meine Bachelorarbeit:
https://youtu.be/g9Gti7ySmqo?t=123
(ab Minute 2)

Ich brauche also eine Funktion die dafür sorgt, dass für jedes Element (Wort) ein Kreis gezeichnet wird und sich die Größe des Kreises (der Radius) nach der Häufigkeit des Wortes im Text richtet.

Nun habe ich eine Tabelle, in welcher sämtliche Wörter und ihre Häufigkeiten erfasst sind, ich brauche aber doch auch noch die Position (Stelle) des Wortes im Text, um die Position des Kreises angeben zu können richtig?
Ist es sinnvoll sich jetzt weiter in RapidMiner reinzuhängen um zu schauen, ob ich diese Position doch irgendwie in meine Tabelle integriert kriege, oder muss ich mir sowieso eine Funktion schreiben um den Text “selber” mit dieser zu analysieren? Denn ich muss in meinem One-Button-System später ja eh den Text irgendwie als Text verfügbar machen…
Ich weiß gerade nicht so richtig, wie ich da ran gehen soll. Weißt du Rat?

Und:

Ich habe in der d3 Dokumentation gefunden wie man Kreise mit Hilfe von d3 zeichnen kann (ich glaube, das war eine der Methoden die du letzte Stunde auch erklärt hattest) jetzt weiß ich allerdings nicht, wie ich ausdrücken kann, dass jedes Listenelement ein kreis werden soll.
Und es bleibt noch das Problem mit der Stelle des Wortes im Text.

0 Likes

#2

Ja, die Position im Text ist notwenig; am Besten in [Zeile, von Zeichen, bis Zeichen]

Naja, mit RapidMiner wäre schon zumindest das [von Zeichen, bis Zeichen] gut herauszubekommen; oder zumindest das erste Zeichen - den Reset kann man sich anhand der Wortlänge ausrechnen

Die Pixel-Positionen rechnet man dann später um

Hmmmjoaaa, ich würde erst einmal davon ansehen, die Textanaylse selbst in JS zu machen; da sind so Nettigkeiten wie Stoppwörter, Füllwörter usw. schon ausgenommen. Nicht sonderlich schwer, aber wenn RapidMiner das schon macht, ist es auch gut. Geht auch später noch :wink:

Den Text brauchen wir auch, ja :slight_smile: am Besten in einer Extra Datei und dann nachaden mit d3 (gibt eine Text-Ladefunktion, ich weiß nur gerade nicht auswendig wie sie heißt)

Die Herangehensweise ist schon super!

Um eine Liste an Daten als Elemente zu “rendern” kannst Du das Beispiel aus der letzten Session nehmen und für Dich abwandeln. Kontrolliere erst einmal über die DevTools des Browsers, ob alle Kreise überhaupt in den DOM kommen, dann würde ich mich um die Positionierung kümmern.

Ich nehme an, das Beispiel bei d3, von dem Du sprichst, benutzt SVG “circle” Element, die haben ein cx, cy und r (radius). Wenn sie das nicht haben, erscheinen sie auch nicht.

Ich erstelle gerade noch ein Beispiel, wie man ein SVG als Container nutzt, wie wir es beim letzten Mal im Kurs mit einfachen HTML-Elementen gemacht haben.

0 Likes

#3

grafik

Wie wir es in den vergangenen Beispielen auf hatten, nehme ich eine eigene render Funktion:

function render() {
    // Zu erst wählen wir alle schon vorhandenen Elemente mit `section.item` aus
    var items = d3.select('svg#data')
        .selectAll('circle.item')
        .data(dataSource) // und binden unser Daten-Array daran
    // Wir ergänzen mir `merge` die Sammlung der schon vorhandenen Elemente
    // um die noch fehlenden aus dem Datensatz
    var allItems = items.merge(items.enter().append('circle'))
    // Dann entfernen wir noch alle, die nicht mehr im Datensatz vorhanden sind
    items.exit().remove()
    // Dann stellen wir die Elemente dar
    renderItems(allItems)
}

Der Body im HTML sieht wie folgt aus:

<body>
    <svg id="data"></svg>
    <script src="main.js"></script>
</body>

Und die renderItems Funktion macht dann dies:

// `renderItems` ist eine Funktion, die Elemente für die Dateneinträge rendert
function renderItems(items) {
    items
    .attr('class', 'item')
    .attr('cx', function(d, index) {
        return window.innerWidth * (index + 1) / dataSource.length
    })
    .attr('cy', function(d) {
        return window.innerHeight / 2
    })
    .attr('r', function(d) {
        return d.Menge
    })
    .style('stroke', function (d) {
        return d.color
    })
    .classed('selected', function(d) {
        return d.selected
    })
}

Der Code ist hier:

0 Likes

#4

Ich habe ein Problem bei der Übergabe der richtigen Parameter um das oben stehende Beispiel auf meinen Fall (Daten kommen direkt aus .txt Datei, nicht .csv und werden direkt in meinem Skript aufbereitet).

Es müsste an dem folgenden Abschnitt in meinem script liegen:

> var dataSource = words


// Wenn der to the points `button` geklickt wird, soll der Text ausgeblentet werden
var showCycles = d3.select('button#showCycles')

showCycles.on('click', function() {

    d3.select('#textbox').classed('ausblenden', true)
})


var showText = true

showCycles.on('click', function() {
    if (showText) {
        showText = false
    } else {
        showText = true
    }
    d3.select('#textbox').classed('ausblenden', showText)
})


// `renderItems` ist eine Funktion, die Elemente für den Text rendert
showCycles.on('click', function renderItems(item) {
    return item
    .attr('class', 'word')
    .attr('cx', function(d, index) {
        return word.length / 2 + col  
    })
    .attr('cy', function(d) {
        return row
    })
    .attr('r', function(d) {
        return d.freq
    })
    .style('fill', function (d) {
        return d.color
    })
    .classed('selected', function(d) {
        return d.selected
    })
})

Der vollständige Code ist hier:

Weiß jemand wo mein Fehler liegen könnte? Bin dankbar für Tipps :slight_smile:

0 Likes

#5

Aha. Der Fehler liegt hier:

showCycles.on('click', function renderItems(item) {
  // Dein Code
})

Du legtst damit die renderItems Funktion als “Klick-Handler” fest. Funktionen die einem Event (in diesem Fall ‘click’) mit .on(... zugewiesen werden, werden mit den Event-Daten als erstem Argument aufgerufen.

Was du wahrscheinlich möchtest:

Rendere ein Kreis mit den Daten aus dem Parameter item

Was passiert:

renderItems wird so aufgerufen:: renderItems(clickEventDaten)

Die Klick-Event-Daten sind nicht das item.

Schau mal in deine render Funktion. Die solltest Du im Klick-Handler aufrufen.
In der render Funktion selbst passiert das Daten-Binding und das darstellen der Kreise.

Du musst also den Klick-Handler so ändern:

showCycles.on('click', function () {
  render()
})

Deine renderItems Funktion bleibt aber im Code:

function renderItems(item) {
    return item
    .attr('class', 'word')
    .attr('cx', function(d, index) {
        return word.length / 2 + col  
    })
    .attr('cy', function(d) {
        return row
    })
    .attr('r', function(d) {
        return d.freq
    })
    .style('fill', function (d) {
        return d.color
    })
    .classed('selected', function(d) {
        return d.selected
    })
}
0 Likes

#6

Und dein Toggle mit dem Text hat auch noch einen Bug: Du hast die Variable showCycles genannt :wink: nicht showText.

0 Likes