From 0e428548a5fb1d80d9b71a5e38c0a3605ed9cc31 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 21 May 2019 12:33:10 -0400 Subject: [PATCH] Add input bar, add node menu --- music_graph/package-lock.json | 6 +- music_graph/package.json | 1 + music_graph/src/MusicGraph.js | 149 +++++++++++++++++-- music_graph/src/MusicGraphApi.js | 5 + music_graph/src/components/AlbumCarousel.vue | 63 ++++++++ music_graph/src/components/ArtistInfo.vue | 18 +-- music_graph/src/components/HelloWorld.vue | 58 +++++++- music_graph/src/components/ImageCarousel.vue | 51 ------- music_graph/src/components/InputBar.vue | 45 ++++++ music_graph/src/icons.js | 47 ++++++ 10 files changed, 363 insertions(+), 80 deletions(-) create mode 100644 music_graph/src/MusicGraphApi.js create mode 100644 music_graph/src/components/AlbumCarousel.vue delete mode 100644 music_graph/src/components/ImageCarousel.vue create mode 100644 music_graph/src/components/InputBar.vue create mode 100644 music_graph/src/icons.js diff --git a/music_graph/package-lock.json b/music_graph/package-lock.json index 833930e..bec02d9 100644 --- a/music_graph/package-lock.json +++ b/music_graph/package-lock.json @@ -5942,7 +5942,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", @@ -6172,8 +6173,7 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash.camelcase": { "version": "4.3.0", diff --git a/music_graph/package.json b/music_graph/package.json index 034e0f2..770641f 100644 --- a/music_graph/package.json +++ b/music_graph/package.json @@ -15,6 +15,7 @@ "d3-force": "^2.0.1", "d3-path": "^1.0.7", "element-ui": "^2.7.2", + "lodash": "^4.17.11", "vue": "^2.6.10", "vue-resource": "^1.5.1", "vue-router": "^3.0.2" diff --git a/music_graph/src/MusicGraph.js b/music_graph/src/MusicGraph.js index b7d4f93..aa9ac88 100644 --- a/music_graph/src/MusicGraph.js +++ b/music_graph/src/MusicGraph.js @@ -1,4 +1,5 @@ import * as d3 from 'd3' +import icons from './icons' export const nodeUtils = { getNodeType: function (labels) { @@ -13,6 +14,21 @@ export const nodeUtils = { } } +// TODO: export somewhere else +const arc = function (radius, itemNumber, itemCount, width) { + itemNumber = itemNumber - 1 + + const startAngle = ((2 * Math.PI) / itemCount) * itemNumber + const endAngle = startAngle + (2 * Math.PI) / itemCount + const innerRadius = Math.max(radius + 8, 20) + return d3.arc() + .innerRadius(innerRadius) + .outerRadius(innerRadius + width) + .startAngle(startAngle) + .endAngle(endAngle) + .padAngle(0.09) +} + export function MusicGraph(data) { const width = window.innerWidth - 7 const height = window.innerHeight - 7 @@ -24,7 +40,7 @@ export function MusicGraph(data) { this.links = [] this._originSet = false - this.svg = d3.select('body') + this.svg = d3.select('#mm') .append('svg') .attr('width', width) .attr('height', height) @@ -33,6 +49,20 @@ export function MusicGraph(data) { this.container.attr('transform', d3.event.transform) } + this.dismiss = () => { + this.menu.remove() + this.nodes.forEach(d => { + d.menu = null + }) + } + + this.svg.append('rect') + .attr('width', width) + .attr('height', height) + .classed('dismiss-rect', true) + .style('fill', 'none') + .on('mousedown', this.dismiss) + this.svg.append('rect') .attr('width', width) .attr('height', height) @@ -50,6 +80,8 @@ export function MusicGraph(data) { .attr('id', 'nodes') this.container.append('g') .attr('id', 'labels') + this.container.append('g') + .attr('id', 'menu') this.dragStarted = (d) => { if (!d3.event.active) { @@ -116,12 +148,62 @@ export function MusicGraph(data) { } this.nodeDbClick = (d) => { - if (this.expandedNodes.has(d.id)) { + if (d.menu) { return } - this.expandedNodes.add(d.id) - this.expandArtist(d.mbid) + this.svg.classed('menu-mode', true) + d.menu = true + + // TODO: Move this somewhere else V V V + d.fx = d.x + d.fy = d.y + const items = [ + {idx: 0, icon: icons.expand}, + {idx: 1, icon: icons.release}, + {idx: 2, icon: icons.hash}, + {idx: 3, icon: icons.guitar} + ] + + const tr = `translate(${d.x},${d.y})` + this.menu = this.container.select('#menu') + .selectAll('g') + .data(items) + .enter() + .append('g') + .classed('menu-item', true) + .attr('transform', tr) + + this.menu + .append('path') + .attr('d', item => arc(35, item.idx, items.length, 1)()) + .on('mouseover', d => { + this.menu.classed('hover', item => item.idx === d.idx) + }) + .on('mouseout', () => this.menu.classed('hover', false)) + .transition() + .duration(200) + .attr('d', item => arc(35, item.idx, items.length, 30)()) + + const angleOffset = items.length === 3 ? 3.72 : 3.927 // Don't ask + this.menu + .append('g') + .html(d => d.icon) + .classed('menu-icon', true) + .attr('transform', d => + `translate(${40 * Math.cos(2 * Math.PI * d.idx / items.length + angleOffset) - 10}, + ${40 * Math.sin(2 * Math.PI * d.idx / items.length + angleOffset) - 10})` + ) + .transition() + .duration(250) + .attr('transform', d => + `translate( + ${57 * Math.cos(2 * Math.PI * d.idx / items.length + angleOffset) - 10}, + ${57 * Math.sin(2 * Math.PI * d.idx / items.length + angleOffset) - 10})` + ) + // ^ ^ ^ + + d3.event.preventDefault() } this.simulation = d3.forceSimulation() @@ -147,9 +229,35 @@ export function MusicGraph(data) { .attr('y', d => d.y) }) - /** - * Add nodes to the graph - */ + this.addNode = function (newNode, relations) { + // Convert {id, id} relation to {node, node} + if (this.nodeById.has(newNode.id)) { + 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 = [] @@ -191,7 +299,7 @@ export function MusicGraph(data) { this.nodes.push(...nodesToAdd) this.links.push(...linksToAdd) - if (!this._originSet) { + if (!this._originSet && originId) { this._setOrigin() this._originSet = true } @@ -238,8 +346,8 @@ export function MusicGraph(data) { (1.2 / d.weight) * (94 * this.expandedNodes.size)) ) ) - this.simulation - .restart() + + this.simulation.alphaTarget(0.01).restart() // Add new links this.link = this.container.select('#links') @@ -269,6 +377,7 @@ export function MusicGraph(data) { .on('mouseover', this.nodeHover) .on('mouseout', this.nodeOut) .on('dblclick', this.nodeDbClick) + .on('contextmenu', this.nodeDbClick) this.node = nodeEnter.merge(this.node) // Add new labels @@ -323,7 +432,7 @@ export function MusicGraph(data) { this.expandArtist = function (mbid) { // todo use http client - d3.json('https://mm.simon987.net/api/artist/related/' + mbid) + d3.json('http://localhost:3030/artist/related/' + mbid) .then((r) => { this.originArtist = r.artists.find(a => a.mbid === mbid) @@ -343,6 +452,24 @@ export function MusicGraph(data) { }) } + this.addArtistByName = function (name) { + // todo use http client + d3.json('http://localhost:3030/artist/related_by_name/' + name) + .then((r) => { + const node = r.artists.find(a => a.name === name) + + this.addNode({ + id: node.id, + mbid: node.mbid, + name: node.name, + listeners: node.listeners, + type: nodeUtils.getNodeType(node.labels), + sourceLinks: new Set(), + targetLinks: new Set() + }, r.relations) + }) + } + this._update() this.setupKeyBindings() } diff --git a/music_graph/src/MusicGraphApi.js b/music_graph/src/MusicGraphApi.js new file mode 100644 index 0000000..5a48329 --- /dev/null +++ b/music_graph/src/MusicGraphApi.js @@ -0,0 +1,5 @@ + +export default function MusicGraphApi() { + let a = 0 + console.log(a) +} diff --git a/music_graph/src/components/AlbumCarousel.vue b/music_graph/src/components/AlbumCarousel.vue new file mode 100644 index 0000000..6ee1f96 --- /dev/null +++ b/music_graph/src/components/AlbumCarousel.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/music_graph/src/components/ArtistInfo.vue b/music_graph/src/components/ArtistInfo.vue index f4cb4f6..ed3d8e7 100644 --- a/music_graph/src/components/ArtistInfo.vue +++ b/music_graph/src/components/ArtistInfo.vue @@ -4,38 +4,38 @@ {{artist.name}}
- + Listeners: {{artist.listeners}}
@@ -34,6 +45,8 @@ export default { -moz-user-select: none; -ms-user-select: none; user-select: none; + position: fixed; + top: 0; } /* Pan mode */ @@ -49,6 +62,10 @@ export default { pointer-events: all; } + svg.menu-mode .dismiss-rect { + pointer-events: all; + } + svg.pan-mode { box-sizing: border-box; border: 5px red solid; @@ -92,7 +109,6 @@ export default { /* Label */ svg .label { text-anchor: middle; - pointer-events: none; } svg.hover .label:not(.selected) { @@ -102,4 +118,34 @@ export default { body { background: #E7EDEB; } + + /* test */ + + #menu .menu-item { + cursor: pointer; + fill: orange; + } + + #menu .menu-item.hover { + fill: darkorange; + } + + #menu .menu-item.hover text { + font-weight: bold; + } + + #menu .menu-item text { + text-anchor: middle; + fill: white; + } + + #menu .menu-icon { + fill: black; + stroke: black; + pointer-events: none; + } + + text { + pointer-events: none; + } diff --git a/music_graph/src/components/ImageCarousel.vue b/music_graph/src/components/ImageCarousel.vue deleted file mode 100644 index 131d8aa..0000000 --- a/music_graph/src/components/ImageCarousel.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/music_graph/src/components/InputBar.vue b/music_graph/src/components/InputBar.vue new file mode 100644 index 0000000..503750d --- /dev/null +++ b/music_graph/src/components/InputBar.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/music_graph/src/icons.js b/music_graph/src/icons.js new file mode 100644 index 0000000..3ef15e2 --- /dev/null +++ b/music_graph/src/icons.js @@ -0,0 +1,47 @@ +const icons = { + expand: '\n' + + '\n' + + '', + release: '\n' + + '\n' + + '', + guitar: '' + + '', + hash: '\n' + + '\n' + + '' + +} + +export default icons