Louise's 2023 retrospective

import io.data2viz.color.* import io.data2viz.geom.* import io.data2viz.math.* import io.data2viz.viz.* import io.data2viz.force.* import io.data2viz.dsv.Dsv import org.w3c.fetch.Response import kotlinx.browser.window import kotlin.js.Promise val vizSize = 900.0 val rockColor = Colors.Web.red val folkColor = Colors.Web.orange val popColor = Colors.Web.blue val punkColor = Colors.Web.green data class Style( val rock: Percent, val folk: Percent, val pop: Percent, val punk: Percent ) { val color = Colors.rgb( ((rockColor.r * rock) + (folkColor.r * folk) + (popColor.r * pop) + (punkColor.r * punk)).toInt(), ((rockColor.g * rock) + (folkColor.g * folk) + (popColor.g * pop) + (punkColor.g * punk)).toInt(), ((rockColor.b * rock) + (folkColor.b * folk) + (popColor.b * pop) + (punkColor.b * punk)).toInt() ) } // An artist with the number of different songs played and musical style data class Artist( val name:String, val songs:Int, val style: Style ) // Parse an artist from the CSV file private fun parsePlaylist(row: List<String>) = Artist( name = row[0], songs = row[1].toInt(), style = Style( rock = (row[2].toInt() * 10.0).pct, folk = (row[3].toInt() * 10.0).pct, pop = (row[4].toInt() * 10.0).pct, punk = (row[5].toInt() * 10.0).pct ) ) fun main() { // Animation counter var animationCount = 0 // Storing the visual nodes (texts and bubbles) val particleTexts = mutableListOf<TextNode>() val particleNodes = mutableListOf<CircleNode>() // Requesting data (CSV file) val request: Promise<Response> = window.fetch("https://docs.google.com/spreadsheets/d/e/2PACX-1vRRlQ8Cd69-0ysrA2TK33hIN1HkqF37XtbjCiS5jvBWj8A6BzuoaWzNUyPdCdFnCLMf-keu-6V8KYDf/pub?gid=0&single=true&output=csv") // On download, parse file and build playlist request.then { result -> result.text().then { csvText -> val parsedArtists = Dsv() .parseRows(csvText) .drop(1) .map { parsePlaylist(it) } // Node 0 is the listener keep it apart as it will be treated differently val playlist = mutableListOf( Artist("Louise", 10, Style(0.pct, 0.pct, 0.pct, 0.pct)) ) playlist.addAll(parsedArtists) // Creating force simulation val simulation = createSimulation(playlist) // Finally create the vizualisation itself viz { size = size(vizSize, vizSize) simulation.nodes.forEachIndexed { index, node -> particleNodes += circle { fill = node.domain.style.color radius = 2.0 + node.domain.songs } particleTexts += text { textColor = node.domain.style.color textContent = playlist[node.index].name textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE) if (index == 0) fontWeight = FontWeight.BOLD } } animation { animationCount++ if (animationCount > 60) { animationCount = 0 } simulation.nodes.forEach { node -> particleTexts[node.index].x = node.x particleTexts[node.index].y = node.y - 15.0 - node.domain.songs particleNodes[node.index].x = node.x particleNodes[node.index].y = node.y } } }.bindRendererOnNewCanvas() } } } private fun createSimulation(playlist: List<Artist>) = forceSimulation<Artist> { // This is used to center the whole simulation on screen forceCenter { center = point(vizSize / 2, vizSize / 2) } // Links between artists and their musical style forcePoint { pointGet = { Point(100.0, 100.0) } strengthGet = { domain.style.rock / 5.0 } } forcePoint { pointGet = { Point(vizSize - 100.0, 100.0) } strengthGet = { domain.style.folk / 5.0 } } forcePoint { pointGet = { Point(vizSize - 100.0, vizSize - 100.0) } strengthGet = { domain.style.pop / 5.0 } } forcePoint { pointGet = { Point(100.0, vizSize - 100.0) } strengthGet = { domain.style.punk / 5.0 } } // Building links between artists and Louise forceLink { linkGet = { if (this.index == 0) { (1 .. playlist.size).mapIndexed { index, artist -> Link<Artist>(this, nodes[index], 270.0 - (nodes[index].domain.songs * 10.0)) } } else { listOf() } } iterations = 1 } // Add collisions to avoid overlapping of artists forceCollision { radiusGet = { if (this.index == 0) { 1.0 } else { 22.0 + (domain.songs * 2.5) } } iterations = 1 } // The whole dataset for this simulation is the "playlist" object domainObjects = playlist }
alisia avatar

Sketch created by

alisia