This commit is contained in:
simon 2019-12-03 11:06:08 -05:00
parent 433dafba89
commit 484d9f9a23
10 changed files with 91 additions and 55 deletions

8
jenkins/Jenkinsfile vendored
View File

@ -34,10 +34,10 @@ pipeline {
steps {
node('master') {
unstash 'webdist'
sshCommand remote: remote, command: "cd music-graph && rm -rf webroot/* deploy.sh"
sshPut remote: remote, from: 'webroot/', into: 'music-graph'
sshPut remote: remote, from: 'jenkins/deploy.sh', into: 'music-graph/'
sshCommand remote: remote, command: 'chmod +x music-graph/deploy.sh && ./music-graph/deploy.sh'
sshCommand remote: remote, command: "cd /srv/music-graph && rm -rf /srv/webroot/* deploy.sh"
sshPut remote: remote, from: 'webroot/', into: '/srv/music-graph'
sshPut remote: remote, from: 'jenkins/deploy.sh', into: '/srv/music-graph/'
sshCommand remote: remote, command: 'chmod +x /srv/music-graph/deploy.sh && /srv/music-graph/deploy.sh'
}
}
}

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
export MGROOT="music-graph"
export MGROOT="/srv/music-graph"
chmod 755 -R "${MGROOT}/webroot"

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="referrer" content="no-referrer">
<meta name="description" content="OSS real-time graph visualization of music data">
<title>music-graph v1.1</title>
<title>music-graph v1.2</title>
</head>
<body>
<div id="app"></div>

View File

@ -6657,7 +6657,8 @@
},
"js-yaml": {
"version": "3.7.0",
"resolved": "",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
"dev": true,
"requires": {
"argparse": "^1.0.7",

View File

@ -91,9 +91,12 @@ export function MusicGraph(data) {
.style('fill', 'none')
.call(d3.zoom()
.scaleExtent([1 / 10, 5])
.on('zoom', this.zoomed))
.on('zoom', this.zoomed)
)
.on('click', this.dismiss)
this.container = this.svg.append('g').attr('id', 'container')
this.container = this.svg.append('g')
.attr('id', 'container')
this.container.append('g').attr('id', 'links')
this.container.append('g').attr('id', 'nodes')
@ -175,7 +178,7 @@ export function MusicGraph(data) {
this.makeMenu = function (d) {
let items = []
let i = 0
if ((d.type === 'Group' || d.type === 'Artist')) {
if (d.type === 'Group' || d.type === 'Artist') {
items.push({
idx: i++,
icon: icons.guitar,
@ -188,6 +191,22 @@ export function MusicGraph(data) {
}
})
}
if ((d.type === 'Group' || d.type === 'Artist') &&
this._data.hoverArtist !== undefined && this._data.hoverArtist.id !== d.id) {
items.push({
idx: i++,
icon: icons.path,
title: 'Path to here',
fn: (d) => {
this.api.getPath(this._data.hoverArtist.mbid, d.mbid)
.then(data => {
if (data.newNodes.length > 0) {
this.addNodes(data.newNodes, data.relations)
}
})
}
})
}
if ((d.type === 'Group' || d.type === 'Artist')) {
items.push({
idx: i++,
@ -332,6 +351,7 @@ export function MusicGraph(data) {
}
this.nodeClick = (d) => {
this.dismiss()
if (d.type === 'Group' || d.type === 'Artist') {
// Toggle artistInfo
this.nodes.forEach(x => {
@ -347,45 +367,21 @@ export function MusicGraph(data) {
}
}
this.addNode = function (newNode, relations) {
// Convert {id, id} relation to {node, node}
if (this.nodeById.has(newNode.id)) {
// Node already exists, select it
this.addNodes = function (newNodes, relations, originId) {
// Update node map, ignore existing nodes
let nodesToAdd = []
// If we're adding a single node and it already exists, select it
if (newNodes.length === 1 && this.nodeById.has(newNodes[0].id)) {
this.nodes.forEach(x => {
x.hover = false
})
this._data.hoverArtist = this.nodeById.get(newNode.id)
this._data.hoverArtist = this.nodeById.get(newNodes[0].id)
this._data.hoverArtist.hover = true
this._update()
return
}
this.nodeById.set(newNode.id, newNode)
newNode.x = width / 2
newNode.y = height / 2
let linksToAdd = relations
.filter(rel => this.nodeById.has(rel.source) && this.nodeById.has(rel.target))
.map(({weight, source, target}) => ({
source: this.nodeById.get(source),
target: this.nodeById.get(target),
weight: weight
}))
// Update source/targetLinks
for (const {source, target} of linksToAdd) {
source.sourceLinks.add(target.id)
target.targetLinks.add(source.id)
}
this.nodes.push(newNode)
this.links.push(...linksToAdd)
this._update()
}
this.addNodes = function (newNodes, relations, originId) {
// Update node map, ignore existing nodes
let nodesToAdd = []
newNodes.forEach(d => {
if (this.nodeById.has(d.id)) {
return
@ -403,6 +399,9 @@ export function MusicGraph(data) {
centerNode.fx = null
centerNode.fy = null
}, 600)
} else {
d.x = width / 2
d.y = height / 2
}
nodesToAdd.push(d)
@ -499,7 +498,7 @@ export function MusicGraph(data) {
.force('link', d3.forceLink(this.links)
.id(d => d.id)
.strength(l => l.weight)
.distance(d => (1.12 / d.weight) * 40 * (this.graphSize))
.distance(d => (1.12 / d.weight) * 30 * (this.graphSize))
)
this.simulation.alphaTarget(0.03).restart()
@ -514,6 +513,7 @@ export function MusicGraph(data) {
.append('line')
.merge(this.link)
.classed('link', true)
.attr('stroke', d => this._getLinkColor(d))
// Add new nodes
this.node = this.container.select('#nodes')
@ -597,10 +597,14 @@ export function MusicGraph(data) {
return null
}
this._getLinkColor = function (node) {
return '#FF0000'
}
this.addArtistByMbid = function (mbid) {
this.api.getRelatedByMbid(mbid)
.then(data => {
this.addNode(data.node, data.relations)
this.addNodes([data.node], data.relations)
})
}
@ -610,7 +614,19 @@ export function MusicGraph(data) {
}
this.api.getRelatedByTag(tagid)
.then(data => {
this.addNode(data.node, data.relations)
// Force tag->artist direction
const relations = data.relations.map(rel => {
if (rel.source === data.node.id) {
return {
weight: rel.weight,
source: rel.target,
target: rel.source
}
} else {
return rel
}
})
this.addNodes([data.node], relations)
})
}

View File

@ -1,5 +1,5 @@
import * as d3 from 'd3'
import {genres} from './genres'
import {isGenreTag} from './genres'
const IGNORE_DATES_TAG = true
const ONLY_GENRE_TAGS = false
@ -54,6 +54,7 @@ const nodeUtils = {
export function MusicGraphApi(data) {
this.url = window.location.protocol + '//' + window.location.hostname + '/api'
// this.url = window.location.protocol + '//' + window.location.hostname + ':3030'
this._data = data
let loadWrapper = (fn) => {
@ -119,7 +120,7 @@ export function MusicGraphApi(data) {
this._filterTags = tags => {
if (ONLY_GENRE_TAGS) {
return tags.filter(tag => genres.has(tag.name))
return tags.filter(tag => isGenreTag(tag.name, tag.tagid))
} else if (IGNORE_DATES_TAG) {
return tags.filter(tag => isNaN(tag.name) && isNaN(tag.name.slice(0, -1)))
}
@ -215,7 +216,7 @@ export function MusicGraphApi(data) {
return {
source: rel.target,
target: rel.source,
weight: rel.weight
weight: Math.min(Math.max(rel.weight * 1.5, 0.2), 1)
}
})
}
@ -240,6 +241,16 @@ export function MusicGraphApi(data) {
})
})
this.getPath = loadWrapper((idFrom, idTo) => {
return d3.json(this.url + '/artist/path/' + idFrom + '/' + idTo)
.then((r) => {
return {
newNodes: r.artists.map(nodeUtils.fromRawDict),
relations: r.relations
}
})
})
this.autoComplete = loadWrapper((prefix) => {
prefix = prefix
.replace(/[^\w.\-!?& ]/g, '_').toUpperCase()

View File

@ -63,7 +63,7 @@
<script>
import AlbumCarousel from './AlbumCarousel'
import {genres} from '../genres'
import {isGenreTag} from '../genres'
export default {
name: 'ArtistInfo',
@ -95,8 +95,8 @@ export default {
this.artistInfo.releases = this.artistInfo.releases
.sort((a, b) => a.year - b.year)
.filter(r => r.labels.indexOf('Album') !== -1 || r.labels.indexOf('EP') !== -1)
this.artistInfo.tags = info.tags.sort((a, b) => b.weight - a.weight).splice(0, 6).map(t => {
t.type = genres.has(t.name) ? '' : 'info'
this.artistInfo.tags = info.tags.sort((a, b) => b.weight - a.weight).splice(0, 99).map(t => {
t.type = isGenreTag(t.name, t.tagid) ? '' : 'info'
return t
})

View File

@ -5,14 +5,14 @@
v-on:addArtist="onAddArtist($event)"
v-on:addTag="onAddTag($event)"
:api="api"
></InputBar>
/>
<ArtistInfo
v-bind:artist="hoverArtist"
v-on:addTag="onAddTag($event)"
:api="api"
/>
<canvas id="textMeasurementCanvas"></canvas>
<Watermark text="music-graph v1.1"/>
<canvas id="textMeasurementCanvas"/>
<Watermark text="music-graph v1.2"/>
<LoadingIndicator :loading="loading"/>
</div>
</template>

View File

@ -1,4 +1,11 @@
export const genres = new Set([
export function isGenreTag(name, id) {
// Spotify tag ids start at 10000000, we assume that all of them are
// genre tags
return genres.has(name) || id > 10000000
}
const genres = new Set([
'acid house',
'acid jazz',
'acid techno',

View File

@ -43,7 +43,8 @@ const icons = {
' 406.136718c-119.574219 0-216.851563-97.238281-216.851563-216.753906 0-97.101562 63.300781-180.390625 153.996094-207.527344-.210938 4.234376-.328125 8.472657-.328125 12.695313' +
' 0 40.6875 9.402344 79.925781 27.175781 115.242187h-62.707031v40.046876h87.792968c7.007813 9.191406 14.667969 17.984374 22.984376 26.296874 4.4375 4.433594 9.007812 8.683594' +
' 13.703124 12.75h-167.53125v40.042969h230.199219c32.671875 14.683594 68.378907 22.417969 105.277344 22.417969 4.75 0 9.515625-.144531 14.28125-.40625-26.75 91.328125-110.402344' +
' 155.195312-207.992187 155.195312zm-98.714844-137.160156h197.226562v40.046875h-197.226562zm0 0"/></svg>'
' 155.195312-207.992187 155.195312zm-98.714844-137.160156h197.226562v40.046875h-197.226562zm0 0"/></svg>',
path: '<svg width="31px" height="31px" transform="translate(-4,-6)" viewBox="-40 0 512 512"> <g fill-rule="evenodd"> <path d="m122.4375 81.371094c0-22.640625-18.421875-41.0625-41.066406-41.0625-22.640625 0-41.0625 18.421875-41.0625 41.0625 0 22.644531 18.421875 41.0625 41.0625 41.0625 22.644531.003906 41.066406-18.417969 41.066406-41.0625zm-62.128906 0c0-11.613282 9.449218-21.0625 21.0625-21.0625 11.617187 0 21.066406 9.449218 21.066406 21.0625 0 11.613281-9.449219 21.0625-21.066406 21.0625-11.613282.003906-21.0625-9.449219-21.0625-21.0625zm0 0"/> <path d="m357.363281 262.003906c-25.769531 0-47.671875 6.421875-60.910156 16.625l-35.863281-7.984375c-5.378906-1.199219-10.730469 2.195313-11.933594 7.585938-1.199219 5.390625 2.195312 10.734375 7.589844 11.933593l27.488281 6.121094c-.511719 1.996094-.785156 4.050782-.785156 6.148438 0 13.585937 11.121093 25.195312 28.933593 32.445312l-16.011718 16.472656c-3.847656 3.960938-3.757813 10.292969.203125 14.140626 1.941406 1.890624 4.457031 2.828124 6.96875 2.828124 2.605469 0 5.210937-1.011718 7.171875-3.027343l23.65625-24.339844c7.328125 1.238281 15.207031 1.910156 23.492187 1.910156 42.421875 0 74.414063-17.382812 74.414063-40.429687s-31.992188-40.429688-74.414063-40.429688zm0 60.859375c-32.710937 0-54.414062-12.296875-54.414062-20.429687 0-1.703125.960937-3.585938 2.761719-5.5.308593-.269532.601562-.554688.871093-.867188 7.464844-6.972656 25.941407-14.0625 50.78125-14.0625 32.710938 0 54.414063 12.296875 54.414063 20.429688 0 8.132812-21.703125 20.429687-54.414063 20.429687zm0 0"/> <path d="m238.449219 439.128906 16.011719-16.476562c3.847656-3.960938 3.757812-10.289063-.203126-14.140625-3.957031-3.847657-10.289062-3.757813-14.140624.203125l-23.65625 24.335937c-7.328126-1.238281-15.207032-1.910156-23.492188-1.910156-42.421875 0-74.414062 17.382813-74.414062 40.429687 0 23.046876 31.992187 40.429688 74.414062 40.429688s74.414062-17.382812 74.414062-40.429688c0-13.585937-11.121093-25.195312-28.933593-32.441406zm-45.480469 52.871094c-32.710938 0-54.414062-12.296875-54.414062-20.429688 0-8.132812 21.703124-20.429687 54.414062-20.429687s54.414062 12.296875 54.414062 20.429687c0 8.132813-21.703124 20.429688-54.414062 20.429688zm0 0"/> <path d="m178.144531 272.773438c.734375.164062 1.460938.242187 2.183594.242187 4.582031 0 8.714844-3.171875 9.75-7.828125 1.199219-5.390625-2.195313-10.734375-7.585937-11.933594l-27.492188-6.125c.515625-1.996094.785156-4.046875.785156-6.148437 0-18.738281-20.84375-31.394531-44.71875-37.085938l44.621094-89.371093c.070312-.144532.140625-.292969.207031-.441407 4.542969-10.339843 6.847657-21.34375 6.847657-32.710937 0-44.867188-36.5-81.371094-81.371094-81.371094-44.867188 0-81.371094 36.503906-81.371094 81.371094 0 11.363281 2.304688 22.367187 6.851562 32.710937.0625.148438.132813.296875.207032.441407l44.664062 89.460937c-23.9375 5.828125-44.765625 18.488281-44.765625 37 0 23.046875 31.992188 40.425781 74.414063 40.425781 25.769531 0 47.675781-6.421875 60.914062-16.621094zm-158.144531-191.402344c0-33.839844 27.53125-61.371094 61.371094-61.371094s61.371094 27.53125 61.371094 61.371094c0 8.5-1.703126 16.722656-5.0625 24.445312l-56.308594 112.78125-56.304688-112.78125c-3.363281-7.726562-5.066406-15.949218-5.066406-24.445312zm6.957031 159.613281c0-5.253906 13.023438-14.386719 33.972657-18.558594l11.496093 23.023438c1.691407 3.390625 5.15625 5.53125 8.945313 5.53125 3.789062 0 7.253906-2.140625 8.949218-5.53125l11.523438-23.085938c21.1875 4.019531 33.941406 13.128907 33.941406 18.617188 0 1.707031-.96875 3.597656-2.777344 5.519531-.304687.269531-.59375.550781-.863281.855469-7.464843 6.96875-25.941406 14.054687-50.773437 14.054687-32.710938 0-54.414063-12.292968-54.414063-20.425781zm0 0"/> <path d="m275.164062 377.003906c-5.507812 0-10 4.488282-10 10 0 5.507813 4.492188 10 10 10 5.507813 0 10-4.492187 10-10 0-5.511718-4.492187-10-10-10zm0 0"/><path d="m219.367188 281.707031c5.507812 0 10-4.492187 10-10 0-5.507812-4.492188-10-10-10-5.507813 0-10 4.492188-10 10 0 5.507813 4.492187 10 10 10zm0 0"/></g></svg>'
}