Label placement & style tweaks

This commit is contained in:
simon 2019-05-25 16:22:51 -04:00
parent 52c9b9f6ff
commit 0a1868c4e3
6 changed files with 220 additions and 87 deletions

View File

@ -1,6 +1,7 @@
import * as d3 from 'd3'
import icons from './icons'
import {MusicGraphApi} from './MusicGraphApi'
import {fitCaptionIntoCircle} from './graphGeometry'
// TODO: export somewhere else
const arc = function (radius, itemNumber, itemCount, width) {
@ -32,7 +33,7 @@ export function MusicGraph(data) {
this.simulation = d3.forceSimulation()
.force('charge', d3.forceManyBody())
.force('collide', d3.forceCollide()
.radius(50)
.radius(40)
.strength(1))
.force('center', d3.forceCenter(width / 2, height / 2))
@ -138,9 +139,9 @@ export function MusicGraph(data) {
n.targetLinks.has(d.id))
this.label.classed('selected', n =>
n.sourceLinks.has(d.id) ||
n.targetLinks.has(d.id) ||
n.id === d.id)
n.node.sourceLinks.has(d.id) ||
n.node.targetLinks.has(d.id) ||
n.node.id === d.id)
this.node.classed('hover', n => n.id === d.id)
@ -182,8 +183,8 @@ export function MusicGraph(data) {
fn: (d) => {
this.api.getRelatedByMbid(d.mbid)
.then(data => {
this.addNodes(data.newNodes, data.relations, d.id)
this.expandedNodes.add(d.id)
this.addNodes(data.newNodes, data.relations, d.id)
d.relatedExpanded = true
})
}
@ -408,12 +409,10 @@ export function MusicGraph(data) {
.force('link', d3.forceLink(this.links)
.id(d => d.id)
.strength(l => l.weight)
.distance(d => Math.min(
(1.2 / d.weight) * (94 * (this.expandedNodes.size + 1)))
)
.distance(d => (1.15 / d.weight) * (82 * (this.expandedNodes.size + 1)))
)
this.simulation.alphaTarget(0.01).restart()
this.simulation.alphaTarget(0.03).restart()
// Add new links
this.link = this.container.select('#links')
@ -451,14 +450,16 @@ export function MusicGraph(data) {
// Add new labels
this.label = this.container.select('#labels')
.selectAll('.label')
.data(this.nodes)
.data([].concat(...this.nodes.map(d => fitCaptionIntoCircle(d.name, d))))
this.label.exit().remove()
this.label = this.label
.enter()
.append('text')
.merge(this.label)
.text(d => d.name)
.classed('label', true)
.classed('release', d => d.node.type === 'Album' || d.node.type === 'EP' || d.node.type === 'Single')
.classed('tag', d => d.node.type === 'Tag')
.text(d => d.text)
}
this.setupKeyBindings = function () {
@ -514,8 +515,8 @@ export function MusicGraph(data) {
.attr('cx', d => d.x)
.attr('cy', d => d.y)
this.label
.attr('x', d => d.x)
.attr('y', d => d.y + 5)
.attr('x', d => Math.round(d.node.x))
.attr('y', d => d.node.y + d.baseline)
})
this._update()

View File

@ -48,17 +48,7 @@ const nodeUtils = {
}
},
radius: function (type) {
if (type === 'Group') {
return 35
} else if (type === 'Artist') {
return 25
} else if (type === 'Tag') {
return 20
} else if (type === 'Album') {
return 20
} else if (type === 'EP' || type === 'Single') {
return 15
}
return 35
}
}

View File

@ -3,6 +3,7 @@
<div id="mm"></div>
<InputBar v-on:query="onQuery($event)"></InputBar>
<ArtistInfo v-bind:artist="hoverArtist"/>
<canvas id="textMeasurementCanvas"></canvas>
</div>
</template>
@ -74,13 +75,12 @@ export default {
/* Link */
svg .link.selected {
stroke-width: 2;
stroke-opacity: 1;
}
svg .link {
stroke: orange;
stroke: #FFE082;
pointer-events: none;
stroke-opacity: 1;
stroke-opacity: 0.3;
stroke-width: 1;
}
@ -91,11 +91,11 @@ export default {
/* Node */
svg .node.selected {
stroke: red;
stroke: #7C4DFF;
}
svg .node.hover {
stroke: red;
stroke: #7C4DFF;
}
svg.hover .node:not(.selected):not(.hover) {
@ -104,11 +104,22 @@ export default {
svg .node {
fill: transparent;
stroke-width: 2;
}
/* Label */
svg .label {
text-anchor: middle;
font-family: Tahoma, sans-serif;
font-size: 11px;
}
svg .label.release {
fill: darkgrey;
}
svg .label.tag {
fill: darkgrey;
}
svg.hover .label:not(.selected) {
@ -116,27 +127,18 @@ export default {
}
body {
background: #E7EDEB;
background: #ffffff;
}
/* test */
#menu .menu-item {
cursor: pointer;
fill: orange;
fill: #FFB300;
}
#menu .menu-item.hover {
fill: darkorange;
}
#menu .menu-item.hover text {
font-weight: bold;
}
#menu .menu-item text {
text-anchor: middle;
fill: white;
fill: #FF8F00;
}
#menu .menu-icon {

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2002-2019 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import measureText from './textMeasurement'
let addShortenedNextWord = (line, word, measure) => {
const result = []
while (!(word.length <= 2)) {
word = word.substr(0, word.length - 2) + '\u2026'
if (measure(word) < line.remainingWidth) {
line.text += ` ${word}`
break
} else {
result.push(undefined)
}
}
return result
}
let noEmptyLines = function (lines) {
for (let line of Array.from(lines)) {
if (line.text.length === 0) {
return false
}
}
return true
}
export let fitCaptionIntoCircle = function (captionText, node) {
const fontFamily = 'Tahoma'
const fontSize = 11
const lineHeight = fontSize
const measure = text => measureText(text, fontFamily, fontSize)
const words = captionText.split(' ')
const emptyLine = function (lineCount, iLine) {
let baseline = (1 + iLine - lineCount / 2) * lineHeight
const containingHeight = iLine < lineCount / 2 ? baseline - lineHeight : baseline
const lineWidth =
Math.sqrt(node.radius * node.radius - containingHeight * containingHeight) * 2
return {
node,
text: '',
baseline,
remainingWidth: lineWidth
}
}
const fitOnFixedNumberOfLines = function (lineCount) {
const lines = []
let iWord = 0
for (
let iLine = 0, end = lineCount - 1, asc = end >= 0;
asc ? iLine <= end : iLine >= end;
asc ? iLine++ : iLine--
) {
const line = emptyLine(lineCount, iLine)
while (
iWord < words.length &&
measure(` ${words[iWord]}`) < line.remainingWidth) {
line.text += ` ${words[iWord]}`
line.remainingWidth -= measure(` ${words[iWord]}`)
iWord++
}
lines.push(line)
}
if (iWord < words.length) {
addShortenedNextWord(lines[lineCount - 1], words[iWord], measure)
}
return [lines, iWord]
}
let consumedWords = 0
const maxLines = (node.radius * 2) / fontSize
let lines = [emptyLine(1, 0)]
for (
let lineCount = 1, end = maxLines, asc = end >= 1;
asc ? lineCount <= end : lineCount >= end;
asc ? lineCount++ : lineCount--
) {
const [candidateLines, candidateWords] = Array.from(
fitOnFixedNumberOfLines(lineCount)
)
if (noEmptyLines(candidateLines)) {
[lines, consumedWords] = Array.from([candidateLines, candidateWords])
}
if (consumedWords >= words.length) {
return lines
}
}
return lines
}

View File

@ -1,50 +1,41 @@
const icons = {
expand: '<svg viewBox="0 0 55 55" width="30px" height="30px" transform="translate(-7,0)">\n' +
'<path d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17\n' +
'\ts-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4\n' +
'\tc0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562\n' +
'\tC8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829\n' +
'\tc1.106,0.86,2.44,1.436,3.898,1.619v10.16c-2.833,0.478-5,2.942-5,5.91c0,3.309,2.691,6,6,6s6-2.691,6-6c0-2.967-2.167-5.431-5-5.91\n' +
'\tv-10.16c1.458-0.183,2.792-0.759,3.898-1.619l7.669,7.669C41.215,39.576,41,40.26,41,41c0,2.206,1.794,4,4,4s4-1.794,4-4\n' +
'\ts-1.794-4-4-4c-0.74,0-1.424,0.215-2.019,0.567l-7.669-7.669C36.366,28.542,37,26.846,37,25s-0.634-3.542-1.688-4.897l9.665-9.665\n' +
'\tC46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z M11,9c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2\n' +
'\tS11,10.103,11,9z M6,51c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S8.206,51,6,51z M33,49c0,2.206-1.794,4-4,4s-4-1.794-4-4\n' +
'\ts1.794-4,4-4S33,46.794,33,49z M29,31c-3.309,0-6-2.691-6-6s2.691-6,6-6s6,2.691,6,6S32.309,31,29,31z M47,41c0,1.103-0.897,2-2,2\n' +
'\ts-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"/>\n' +
'</svg>',
release: '<svg viewBox="0 0 217.465 217.465" width="30px" height="30px" transform="translate(-1,-5)">\n' +
'<path d="M108.732,152.869c-24.337,0-44.137-19.8-44.137-44.137c0-24.337,19.8-44.137,44.137-44.137\n' +
'\tc24.337,0,44.137,19.8,44.137,44.137C152.869,133.07,133.07,152.869,108.732,152.869z M108.732,75.505\n' +
'\tc-18.322,0-33.227,14.906-33.227,33.228c0,18.322,14.906,33.227,33.227,33.227c18.322,0,33.227-14.906,33.227-33.227\n' +
'\tC141.96,90.411,127.054,75.505,108.732,75.505z M108.732,129.388c-11.408,0-20.655-9.248-20.655-20.656\n' +
'\tc0-11.408,9.248-20.655,20.655-20.655c11.408,0,20.655,9.248,20.655,20.655C129.388,120.14,120.14,129.388,108.732,129.388z\n' +
'\t M108.732,0C48.777,0,0,48.778,0,108.732s48.777,108.732,108.732,108.732s108.732-48.777,108.732-108.732S168.687,0,108.732,0z\n' +
'\t M108.732,46.79c-34.155,0-61.942,27.787-61.942,61.942c0,2.762-2.239,5-5,5s-5-2.238-5-5c0-39.669,32.273-71.942,71.942-71.942\n' +
'\tc2.761,0,5,2.238,5,5S111.494,46.79,108.732,46.79z M108.732,26.306c-45.45,0-82.427,36.977-82.427,82.427c0,2.762-2.239,5-5,5\n' +
'\ts-5-2.238-5-5c0-50.964,41.462-92.427,92.427-92.427c2.761,0,5,2.238,5,5S111.494,26.306,108.732,26.306z"/>\n' +
'</svg>',
guitar: '<svg width="30px" height="30px" viewBox="0 0 481.684 481.683" transform="translate(-3,-3)">' +
'<path d="M469.783,14.968c0.312-0.693,0.412-1.196,0.292-1.491c-2.701-6.424-4.66,2.016-24.221-8.874\n' +
'\t\t\tc-19.548-10.893-24.63,0.966-24.63,0.966s-19.411,36.914-39.833,51.327c-20.435,14.413-21.428,12.381-22.586,24.95\n' +
'\t\t\tc-1.167,12.565-5.438,16.646-5.438,16.646l0.309,0.331l-105.384,97.529c-0.1-6.39-6.159-6.073-6.159-6.073\n' +
'\t\t\tc-24.964-11.68-19.266-18.578-6.664-46.466c12.595-27.89-4.482-33.897-4.482-33.897s-33.654,44.781-56.961,44.24\n' +
'\t\t\tc-23.309-0.545-23.898,7.456-23.898,7.456s17.294,56.719-40.287,66.014c-21.516,3.468-37.612,8.636-49.333,13.74\n' +
'\t\t\tc-2.761,1.146-5.464,2.433-8.109,3.851c-13.604,7.062-18.905,13.101-18.905,13.101l0.167,0.156c-1.515,1.379-3.005,2.797-4.45,4.3\n' +
'\t\t\tc-43.687,45.3-35.432,124.143,18.456,176.11c53.884,51.965,132.977,57.362,176.664,12.066c3.65-3.787,6.869-7.786,9.744-11.941\n' +
'\t\t\tl0.024,0.031c15.018-16.967,19.787-55.803,19.787-55.803s4.685-49.344,35.374-43.112c30.688,6.243,30.824-13.168,30.824-13.168\n' +
'\t\t\ts-1.639-36.853,12.748-46.338c14.391-9.481,16.562-23.104-2.22-15.485c-18.791,7.622-30.068,14.375-47.557,0.377\n' +
'\t\t\tc-10.251-8.211-12.295-20.478-12.251-29.224l113.555-109.09c2.513-1.28,6.816-2.831,13.785-3.422\n' +
'\t\t\tc12.58-1.07,10.548-2.082,25.115-22.406c14.559-20.324,51.623-39.455,51.623-39.455s11.891-4.993,1.143-24.627\n' +
'\t\t\tC467.719,22.125,470.849,17.533,469.783,14.968z"/></svg>',
hash: '<svg viewBox="0 0 281.465 281.465" width="28px" height="28px" transform="translate(-4,0)">\n' +
'<path d="M273.661,114.318V67.035h-45.558L236.886,0h-47.69l-8.783,67.035h-60.084L129.113,0H81.425L72.64,67.035H7.804v47.283\n' +
'\th58.649l-6.904,52.791H7.804v47.289h45.559l-8.784,67.066h47.687l8.787-67.066h60.083l-8.786,67.066h47.691l8.783-67.066h64.836\n' +
'\tv-47.289h-58.647l6.901-52.791H273.661z M167.326,167.109h-60.084l6.9-52.791h60.082L167.326,167.109z"/>\n' +
'</svg>',
delete: '<svg width="28px" height="28px" viewBox="0 0 510 510" >\n' +
'\t\t<path d="M255,0C114.75,0,0,114.75,0,255s114.75,255,255,255s255-114.75,255-255S395.25,0,255,0z M382.5,346.8l-35.7,35.7\n' +
'\t\t\tL255,290.7l-91.8,91.8l-35.7-35.7l91.8-91.8l-91.8-91.8l35.7-35.7l91.8,91.8l91.8-91.8l35.7,35.7L290.7,255L382.5,346.8z"/>\n' +
'</svg>'
expand: '<svg viewBox="0 0 55 55" width="30px" height="30px" transform="translate(-7,-5)"><path d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17\n' +
's-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4\n' +
'c0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562\n' +
'C8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829\n' +
'c1.106,0.86,2.44,1.436,3.898,1.619v10.16c-2.833,0.478-5,2.942-5,5.91c0,3.309,2.691,6,6,6s6-2.691,6-6c0-2.967-2.167-5.431-5-5.91\n' +
'v-10.16c1.458-0.183,2.792-0.759,3.898-1.619l7.669,7.669C41.215,39.576,41,40.26,41,41c0,2.206,1.794,4,4,4s4-1.794,4-4\n' +
's-1.794-4-4-4c-0.74,0-1.424,0.215-2.019,0.567l-7.669-7.669C36.366,28.542,37,26.846,37,25s-0.634-3.542-1.688-4.897l9.665-9.665\n' +
'C46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z M11,9c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2\n' +
'S11,10.103,11,9z M6,51c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S8.206,51,6,51z M33,49c0,2.206-1.794,4-4,4s-4-1.794-4-4\n' +
's1.794-4,4-4S33,46.794,33,49z M29,31c-3.309,0-6-2.691-6-6s2.691-6,6-6s6,2.691,6,6S32.309,31,29,31z M47,41c0,1.103-0.897,2-2,2\n' +
's-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"/></svg>',
release: '<svg viewBox="0 0 217.465 217.465" width="30px" height="30px" transform="translate(-1,-5)"><path d="M108.732,152.869c-24.337,0-44.137-19.8-44.137-44.137c0-24.337,19.8-44.137,44.137-44.137\n' +
'c24.337,0,44.137,19.8,44.137,44.137C152.869,133.07,133.07,152.869,108.732,152.869z M108.732,75.505\n' +
'c-18.322,0-33.227,14.906-33.227,33.228c0,18.322,14.906,33.227,33.227,33.227c18.322,0,33.227-14.906,33.227-33.227\n' +
'C141.96,90.411,127.054,75.505,108.732,75.505z M108.732,129.388c-11.408,0-20.655-9.248-20.655-20.656\n' +
'c0-11.408,9.248-20.655,20.655-20.655c11.408,0,20.655,9.248,20.655,20.655C129.388,120.14,120.14,129.388,108.732,129.388z\n' +
'M108.732,0C48.777,0,0,48.778,0,108.732s48.777,108.732,108.732,108.732s108.732-48.777,108.732-108.732S168.687,0,108.732,0z\n' +
'M108.732,46.79c-34.155,0-61.942,27.787-61.942,61.942c0,2.762-2.239,5-5,5s-5-2.238-5-5c0-39.669,32.273-71.942,71.942-71.942\n' +
'c2.761,0,5,2.238,5,5S111.494,46.79,108.732,46.79z M108.732,26.306c-45.45,0-82.427,36.977-82.427,82.427c0,2.762-2.239,5-5,5\n' +
's-5-2.238-5-5c0-50.964,41.462-92.427,92.427-92.427c2.761,0,5,2.238,5,5S111.494,26.306,108.732,26.306z"/></svg>',
guitar: '<svg width="30px" height="30px" viewBox="0 0 481.684 481.683" transform="translate(-3,-3)"><path d="M469.783,14.968c0.312-0.693,0.412-1.196,0.292-1.491c-2.701-6.424-4.66,2.016-24.221-8.874\n' +
'c-19.548-10.893-24.63,0.966-24.63,0.966s-19.411,36.914-39.833,51.327c-20.435,14.413-21.428,12.381-22.586,24.95\n' +
'c-1.167,12.565-5.438,16.646-5.438,16.646l0.309,0.331l-105.384,97.529c-0.1-6.39-6.159-6.073-6.159-6.073\n' +
'c-24.964-11.68-19.266-18.578-6.664-46.466c12.595-27.89-4.482-33.897-4.482-33.897s-33.654,44.781-56.961,44.24\n' +
'c-23.309-0.545-23.898,7.456-23.898,7.456s17.294,56.719-40.287,66.014c-21.516,3.468-37.612,8.636-49.333,13.74\n' +
'c-2.761,1.146-5.464,2.433-8.109,3.851c-13.604,7.062-18.905,13.101-18.905,13.101l0.167,0.156c-1.515,1.379-3.005,2.797-4.45,4.3\n' +
'c-43.687,45.3-35.432,124.143,18.456,176.11c53.884,51.965,132.977,57.362,176.664,12.066c3.65-3.787,6.869-7.786,9.744-11.941\n' +
'l0.024,0.031c15.018-16.967,19.787-55.803,19.787-55.803s4.685-49.344,35.374-43.112c30.688,6.243,30.824-13.168,30.824-13.168\n' +
's-1.639-36.853,12.748-46.338c14.391-9.481,16.562-23.104-2.22-15.485c-18.791,7.622-30.068,14.375-47.557,0.377\n' +
'c-10.251-8.211-12.295-20.478-12.251-29.224l113.555-109.09c2.513-1.28,6.816-2.831,13.785-3.422\n' +
'c12.58-1.07,10.548-2.082,25.115-22.406c14.559-20.324,51.623-39.455,51.623-39.455s11.891-4.993,1.143-24.627\n' +
'C467.719,22.125,470.849,17.533,469.783,14.968z"/></svg>',
hash: '<svg viewBox="0 0 281.465 281.465" width="28px" height="28px" transform="translate(-4,0)"><path d="M273.661,114.318V67.035h-45.558L236.886,0h-47.69l-8.783,67.035h-60.084L129.113,0H81.425L72.64,67.035H7.804v47.283\n' +
'h58.649l-6.904,52.791H7.804v47.289h45.559l-8.784,67.066h47.687l8.787-67.066h60.083l-8.786,67.066h47.691l8.783-67.066h64.836\n' +
'v-47.289h-58.647l6.901-52.791H273.661z M167.326,167.109h-60.084l6.9-52.791h60.082L167.326,167.109z"/></svg>',
delete: '<svg width="28px" height="28px" viewBox="0 0 510 510" ><path d="M255,0C114.75,0,0,114.75,0,255s114.75,255,255,255s255-114.75,255-255S395.25,0,255,0z M382.5,346.8l-35.7,35.7\n' +
'L255,290.7l-91.8,91.8l-35.7-35.7l91.8-91.8l-91.8-91.8l35.7-35.7l91.8,91.8l91.8-91.8l35.7,35.7L290.7,255L382.5,346.8z"/></svg>'
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2002-2019 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import * as d3 from 'd3'
const measureUsingCanvas = function (text, font) {
const canvasSelection = d3.select('canvas#textMeasurementCanvas').data([this])
canvasSelection
.enter()
.append('canvas')
.attr('id', 'textMeasurementCanvas')
.style('display', 'none')
const canvas = canvasSelection.node()
const context = canvas.getContext('2d')
context.font = font
return context.measureText(text).width
}
export default function (text, fontFamily, fontSize) {
const font = `normal normal normal ${fontSize}px/normal ${fontFamily}`
return measureUsingCanvas(text, font)
}