Skip to content

Commit

Permalink
feat(chord chart): allow adding text to fingers and barre chords
Browse files Browse the repository at this point in the history
  • Loading branch information
Voellmy Raphael authored and Voellmy Raphael committed May 2, 2020
1 parent 7ff62b0 commit 591db6f
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 78 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,17 @@ Here's an example of a customized chart:
```javascript
new SVGuitarChord('#some-selector')
.chord({
// array of [string, fret]
// array of [string, fret, text]
fingers: [
[1, 2],
[2, 3],
[1, 2, '2'],
[2, 3, '3'],
[3, 3],
[6, 'x']
],

// optional: barres for barre chords
barres: [
{ fromString: 5, toString: 1, fret: 1 },
{ fromString: 5, toString: 1, fret: 1, text: '1' },
],
})
.configure({
Expand Down Expand Up @@ -139,7 +139,17 @@ new SVGuitarChord('#some-selector')
* Color of a finger / nut
*/
nutColor: '#000',

/**
* The color of text inside nuts
*/
nutTextColor: '#FFF',

/**
* The size of text inside nuts
*/
nutTextSize: 22,

/**
* Height of a fret, relative to the space between two strings
*/
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { QuerySelector } from '@svgdotjs/svg.js'
export enum Alignment {
LEFT = 'left',
MIDDLE = 'middle',
RIGHT = 'right'
RIGHT = 'right',
}

export interface GraphcisElement {
Expand Down Expand Up @@ -41,7 +41,8 @@ export abstract class Renderer {
fontSize: number,
color: string,
fontFamily: string,
alignment: Alignment
alignment: Alignment,
plain?: boolean
): GraphcisElement

abstract circle(
Expand Down
7 changes: 6 additions & 1 deletion src/renderer/roughjs/roughjs-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ export class RoughJsRenderer extends Renderer {
fontSize: number,
color: string,
fontFamily: string,
alignment: Alignment
alignment: Alignment,
plain?: boolean
): GraphcisElement {
// Place the SVG namespace in a variable to easily reference it.
const txtElem = document.createElementNS('http://www.w3.org/2000/svg', 'text')
Expand All @@ -225,6 +226,10 @@ export class RoughJsRenderer extends Renderer {
txtElem.setAttributeNS(null, 'align', alignment)
txtElem.setAttributeNS(null, 'fill', color)

if (plain) {
txtElem.setAttributeNS(null, 'dominant-baseline', 'central')
}

txtElem.appendChild(document.createTextNode(text))

this.svgNode.appendChild(txtElem)
Expand Down
51 changes: 34 additions & 17 deletions src/renderer/svgjs/svg-js-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@ export class SvgJsRenderer extends Renderer {
}

background(color: string) {
this.svg
.rect()
.size('100%', '100%')
.fill(color)
this.svg.rect().size('100%', '100%').fill(color)
}

text(
Expand All @@ -62,17 +59,37 @@ export class SvgJsRenderer extends Renderer {
fontSize: number,
color: string,
fontFamily: string,
alignment: Alignment
alignment: Alignment,
plain?: boolean
) {
const element = this.svg
.text(text)
.move(x, y)
.font({
family: fontFamily,
size: fontSize,
anchor: alignment
})
.fill(color)
let element

if (plain) {
// create a text element centered at x,y. No SVG.js magic.
element = this.svg
.plain(text)
.attr({
x,
y,
})
.font({
family: fontFamily,
size: fontSize,
anchor: alignment,
'dominant-baseline': 'central',
})
.fill(color)
} else {
element = this.svg
.text(text)
.move(x, y)
.font({
family: fontFamily,
size: fontSize,
anchor: alignment,
})
.fill(color)
}

return this.boxToElement(element.bbox(), element.remove.bind(element))
}
Expand All @@ -91,7 +108,7 @@ export class SvgJsRenderer extends Renderer {
.fill(fill || 'none')
.stroke({
color: strokeColor,
width: strokeWidth
width: strokeWidth,
})

return this.boxToElement(element.bbox(), element.remove.bind(element))
Expand All @@ -113,7 +130,7 @@ export class SvgJsRenderer extends Renderer {
.fill(fill || 'none')
.stroke({
width: strokeWidth,
color: strokeColor
color: strokeColor,
})
.radius(radius || 0)

Expand All @@ -126,7 +143,7 @@ export class SvgJsRenderer extends Renderer {
height: box.height,
x: box.x,
y: box.y,
remove
remove,
}
}
}
96 changes: 76 additions & 20 deletions src/svguitar.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { QuerySelector } from '@svgdotjs/svg.js'
import { range } from './utils'
import { constants } from './constants'
import { SvgJsRenderer, RoughJsRenderer, Alignment, GraphcisElement, Renderer } from './renderer'
import { Alignment, GraphcisElement, Renderer, RoughJsRenderer, SvgJsRenderer } from './renderer'

// Chord diagram input types (compatible with Vexchords input, see https://github.com/0xfe/vexchords)
export type SilentString = 'x'
export type OpenString = 0
export type Finger = [number, number | OpenString | SilentString, string?]
export type Barre = { fromString: number; toString: number; fret: number }
export type Barre = { fromString: number; toString: number; fret: number; text?: string }
export type Chord = { fingers: Finger[]; barres: Barre[] }

/**
Expand All @@ -25,12 +25,12 @@ export const SILENT: SilentString = 'x'
*/
export enum FretLabelPosition {
LEFT = 'left',
RIGHT = 'right'
RIGHT = 'right',
}

export enum ChordStyle {
normal = 'normal',
handdrawn = 'handdrawn'
handdrawn = 'handdrawn',
}

export interface ChordSettings {
Expand Down Expand Up @@ -83,6 +83,16 @@ export interface ChordSettings {
*/
nutColor?: string

/**
* The color of text inside nuts
*/
nutTextColor?: string

/**
* The size of text inside nuts
*/
nutTextSize?: number

/**
* Height of a fret, relative to the space between two strings
*/
Expand Down Expand Up @@ -192,6 +202,8 @@ interface RequiredChordSettings {
fretLabelFontSize: number
fretLabelPosition: FretLabelPosition
nutSize: number
nutTextColor: string
nutTextSize: number
sidePadding: number
titleFontSize: number
titleBottomMargin: number
Expand All @@ -214,6 +226,8 @@ const defaultSettings: RequiredChordSettings = {
fretLabelFontSize: 38,
fretLabelPosition: FretLabelPosition.RIGHT,
nutSize: 0.65,
nutTextColor: '#FFF',
nutTextSize: 24,
sidePadding: 0.2,
titleFontSize: 48,
titleBottomMargin: 0,
Expand All @@ -223,7 +237,7 @@ const defaultSettings: RequiredChordSettings = {
topFretWidth: 10,
fretSize: 1.5,
barreChordRadius: 0.25,
fontFamily: 'Arial, "Helvetica Neue", Helvetica, sans-serif'
fontFamily: 'Arial, "Helvetica Neue", Helvetica, sans-serif',
}

export class SVGuitarChord {
Expand Down Expand Up @@ -295,7 +309,7 @@ export class SVGuitarChord {

return {
width: constants.width,
height: y
height: y,
}
}

Expand Down Expand Up @@ -471,7 +485,7 @@ export class SVGuitarChord {
const startX = constants.width * sidePadding
const stringsSpacing = this.stringSpacing()

return range(strings).map(i => startX + stringsSpacing * i)
return range(strings).map((i) => startX + stringsSpacing * i)
}

private stringSpacing(): number {
Expand All @@ -495,7 +509,7 @@ export class SVGuitarChord {
const frets = this.settings.frets || defaultSettings.frets
const fretSpacing = this.fretSpacing()

return range(frets, 1).map(i => startY + fretSpacing * i)
return range(frets, 1).map((i) => startY + fretSpacing * i)
}

private toArrayIndex(stringIndex: number): number {
Expand All @@ -518,7 +532,7 @@ export class SVGuitarChord {

const stroke = {
color,
width: strokeWidth
width: strokeWidth,
}

this._chord.fingers
Expand Down Expand Up @@ -567,47 +581,89 @@ export class SVGuitarChord {
const nutSize = relativeNutSize * stringSpacing
const nutColor = this.settings.nutColor || this.settings.color || defaultSettings.color
const fretColor = this.settings.fretColor || this.settings.color || defaultSettings.color
const stringColor = this.settings.stringColor || this.settings.color || defaultSettings.color
const barreChordRadius = this.settings.barreChordRadius || defaultSettings.barreChordRadius
const strokeWidth = this.settings.strokeWidth || defaultSettings.strokeWidth
const fontFamily = this.settings.fontFamily || defaultSettings.fontFamily
const nutTextColor = this.settings.nutTextColor || defaultSettings.nutTextColor
const nutTextSize = this.settings.nutTextSize || defaultSettings.nutTextSize

// draw frets
fretYPositions.forEach(fretY => {
fretYPositions.forEach((fretY) => {
this.renderer.line(startX, fretY, endX, fretY, strokeWidth, fretColor)
})

// draw strings
stringXPositions.forEach(stringX => {
stringXPositions.forEach((stringX) => {
this.renderer.line(stringX, y, stringX, y + height + strokeWidth / 2, strokeWidth, fretColor)
})

// draw fingers
this._chord.fingers
.filter(([_, value]) => value !== SILENT && value !== OPEN)
.map(([stringIndex, fretIndex]) => [this.toArrayIndex(stringIndex), fretIndex as number])
.forEach(([stringIndex, fretIndex]) => {
.map<[number, number, string | undefined]>(([stringIndex, fretIndex, text]) => [
this.toArrayIndex(stringIndex),
fretIndex as number,
text,
])
.forEach(([stringIndex, fretIndex, text]) => {
const nutCenterX = startX + stringIndex * stringSpacing
const nutCenterY = y + fretIndex * fretSpacing - fretSpacing / 2

this.renderer.circle(
startX - nutSize / 2 + stringIndex * stringSpacing,
y - fretSpacing / 2 - nutSize / 2 + fretIndex * fretSpacing,
nutCenterX - nutSize / 2,
nutCenterY - nutSize / 2,
nutSize,
0,
nutColor,
nutColor
)

// draw text on the nut
if (text) {
this.renderer.text(
text,
nutCenterX,
nutCenterY,
nutTextSize,
nutTextColor,
fontFamily,
Alignment.MIDDLE,
true
)
}
})

// draw barre chords
this._chord.barres.forEach(({ fret, fromString, toString }) => {
this._chord.barres.forEach(({ fret, fromString, toString, text }) => {
const barreCenterY = fretYPositions[fret - 1] - fretSpacing / 2
const fromStringX = stringXPositions[this.toArrayIndex(fromString)]
const toStringX = stringXPositions[this.toArrayIndex(toString)]
const distance = Math.abs(toString - fromString) * stringSpacing

this.renderer.rect(
stringXPositions[this.toArrayIndex(fromString)] - stringSpacing / 4,
fretYPositions[fret - 1] - fretSpacing / 2 - nutSize / 2,
Math.abs(toString - fromString) * stringSpacing + stringSpacing / 2,
fromStringX - stringSpacing / 4,
barreCenterY - nutSize / 2,
distance + stringSpacing / 2,
nutSize,
0,
nutColor,
nutColor,
nutSize * barreChordRadius
)

// draw text on the barre chord
if (text) {
this.renderer.text(
text,
fromStringX + distance / 2,
barreCenterY,
nutTextSize,
nutTextColor,
fontFamily,
Alignment.MIDDLE,
true
)
}
})

return y + height
Expand Down
Loading

0 comments on commit 591db6f

Please sign in to comment.